Merge branch 'dev' into project-updates
This commit is contained in:
commit
a06a5c8d18
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,5 +1,7 @@
|
||||||
!*
|
!*
|
||||||
|
|
||||||
|
.directory
|
||||||
|
|
||||||
#################
|
#################
|
||||||
## Eclipse
|
## Eclipse
|
||||||
#################
|
#################
|
||||||
|
|
|
@ -9,15 +9,16 @@
|
||||||
- [bfayers](https://github.com/bfayers)
|
- [bfayers](https://github.com/bfayers)
|
||||||
- [Bond_009](https://github.com/Bond-009)
|
- [Bond_009](https://github.com/Bond-009)
|
||||||
- [AnthonyLavado](https://github.com/anthonylavado)
|
- [AnthonyLavado](https://github.com/anthonylavado)
|
||||||
|
- [sparky8251](https://github.com/sparky8251)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
- [LukePulverenti](https://github.com/LukePulverenti)
|
- [LukePulverenti](https://github.com/LukePulverenti)
|
||||||
- [ebr11](https://github.com/ebr11)
|
- [ebr11](https://github.com/ebr11)
|
||||||
- [lalmanzar](https://github.com/lalmanzar)
|
- [lalmanzar](https://github.com/lalmanzar)
|
||||||
- [schneifu](https://github.com/schneifu)
|
- [schneifu](https://github.com/schneifu)
|
||||||
- [Mark2xv](https://github.com/Mark2xv)
|
- [Mark2xv](https://github.com/Mark2xv)
|
||||||
- [ScottRapsey](https://github.com/ScottRapsey)
|
- [ScottRapsey](https://github.com/ScottRapsey)
|
||||||
- [skynet600](https://github.com/skynet600)
|
- [skynet600](https://github.com/skynet600)
|
||||||
- [Cheesegeezer](https://githum.com/Cheesegeezer)
|
- [Cheesegeezer](https://githum.com/Cheesegeezer)
|
||||||
- [Radeon](https://github.com/radeonorama)
|
- [Radeon](https://github.com/radeonorama)
|
||||||
|
|
|
@ -368,7 +368,6 @@ namespace Emby.Server.Implementations
|
||||||
protected IAuthService AuthService { get; private set; }
|
protected IAuthService AuthService { get; private set; }
|
||||||
|
|
||||||
public StartupOptions StartupOptions { get; private set; }
|
public StartupOptions StartupOptions { get; private set; }
|
||||||
protected readonly string ReleaseAssetFilename;
|
|
||||||
|
|
||||||
internal IPowerManagement PowerManagement { get; private set; }
|
internal IPowerManagement PowerManagement { get; private set; }
|
||||||
internal IImageEncoder ImageEncoder { get; private set; }
|
internal IImageEncoder ImageEncoder { get; private set; }
|
||||||
|
@ -393,7 +392,6 @@ namespace Emby.Server.Implementations
|
||||||
StartupOptions options,
|
StartupOptions options,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IPowerManagement powerManagement,
|
IPowerManagement powerManagement,
|
||||||
string releaseAssetFilename,
|
|
||||||
IEnvironmentInfo environmentInfo,
|
IEnvironmentInfo environmentInfo,
|
||||||
IImageEncoder imageEncoder,
|
IImageEncoder imageEncoder,
|
||||||
ISystemEvents systemEvents,
|
ISystemEvents systemEvents,
|
||||||
|
@ -419,7 +417,6 @@ namespace Emby.Server.Implementations
|
||||||
Logger = LoggerFactory.CreateLogger("App");
|
Logger = LoggerFactory.CreateLogger("App");
|
||||||
|
|
||||||
StartupOptions = options;
|
StartupOptions = options;
|
||||||
ReleaseAssetFilename = releaseAssetFilename;
|
|
||||||
PowerManagement = powerManagement;
|
PowerManagement = powerManagement;
|
||||||
|
|
||||||
ImageEncoder = imageEncoder;
|
ImageEncoder = imageEncoder;
|
||||||
|
@ -1240,7 +1237,7 @@ namespace Emby.Server.Implementations
|
||||||
HttpClient,
|
HttpClient,
|
||||||
ZipClient,
|
ZipClient,
|
||||||
ProcessFactory,
|
ProcessFactory,
|
||||||
5000, false,
|
5000,
|
||||||
EnvironmentInfo);
|
EnvironmentInfo);
|
||||||
|
|
||||||
MediaEncoder = mediaEncoder;
|
MediaEncoder = mediaEncoder;
|
||||||
|
@ -2286,56 +2283,6 @@ namespace Emby.Server.Implementations
|
||||||
Plugins = list.ToArray();
|
Plugins = list.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks for update.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <param name="progress">The progress.</param>
|
|
||||||
/// <returns>Task{CheckForUpdateResult}.</returns>
|
|
||||||
public async Task<CheckForUpdateResult> CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress<double> progress)
|
|
||||||
{
|
|
||||||
var updateLevel = SystemUpdateLevel;
|
|
||||||
var cacheLength = updateLevel == PackageVersionClass.Release ?
|
|
||||||
TimeSpan.FromHours(12) :
|
|
||||||
TimeSpan.FromMinutes(5);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var result = await new GithubUpdater(HttpClient, JsonSerializer).CheckForUpdateResult("MediaBrowser",
|
|
||||||
"Emby.Releases",
|
|
||||||
ApplicationVersion,
|
|
||||||
updateLevel,
|
|
||||||
ReleaseAssetFilename,
|
|
||||||
"MBServer",
|
|
||||||
UpdateTargetFileName,
|
|
||||||
cacheLength,
|
|
||||||
cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
HasUpdateAvailable = result.IsUpdateAvailable;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (HttpException ex)
|
|
||||||
{
|
|
||||||
// users are overreacting to this occasionally failing
|
|
||||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.Forbidden)
|
|
||||||
{
|
|
||||||
HasUpdateAvailable = false;
|
|
||||||
return new CheckForUpdateResult
|
|
||||||
{
|
|
||||||
IsUpdateAvailable = false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual string UpdateTargetFileName
|
|
||||||
{
|
|
||||||
get { return "Mbserver.zip"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the application.
|
/// Updates the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -901,8 +901,8 @@ namespace Emby.Server.Implementations.Channels
|
||||||
private T GetItemById<T>(string idString, string channelName, out bool isNew)
|
private T GetItemById<T>(string idString, string channelName, out bool isNew)
|
||||||
where T : BaseItem, new()
|
where T : BaseItem, new()
|
||||||
{
|
{
|
||||||
var id = GetIdToHash(idString, channelName).GetMBId(typeof(T));
|
var id = _libraryManager.GetNewItemId(GetIdToHash(idString, channelName), typeof(T));
|
||||||
|
|
||||||
T item = null;
|
T item = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
@ -145,7 +145,8 @@ namespace Emby.Server.Implementations.Devices
|
||||||
HasUser = true
|
HasUser = true
|
||||||
|
|
||||||
}).Items;
|
}).Items;
|
||||||
|
|
||||||
|
// TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
|
||||||
if (query.SupportsSync.HasValue)
|
if (query.SupportsSync.HasValue)
|
||||||
{
|
{
|
||||||
var val = query.SupportsSync.Value;
|
var val = query.SupportsSync.Value;
|
||||||
|
|
|
@ -3380,26 +3380,6 @@ namespace SharpCifs.Smb
|
||||||
SetAttributes(GetAttributes() & ~AttrReadonly);
|
SetAttributes(GetAttributes() & ~AttrReadonly);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a
|
|
||||||
/// <see cref="System.Uri">System.Uri</see>
|
|
||||||
/// for this <code>SmbFile</code>. The
|
|
||||||
/// <code>URL</code> may be used as any other <code>URL</code> might to
|
|
||||||
/// access an SMB resource. Currently only retrieving data and information
|
|
||||||
/// is supported (i.e. no <tt>doOutput</tt>).
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// A new <code>
|
|
||||||
/// <see cref="System.Uri">System.Uri</see>
|
|
||||||
/// </code> for this <code>SmbFile</code>
|
|
||||||
/// </returns>
|
|
||||||
/// <exception cref="System.UriFormatException">System.UriFormatException</exception>
|
|
||||||
[Obsolete(@"Use getURL() instead")]
|
|
||||||
public virtual Uri ToUrl()
|
|
||||||
{
|
|
||||||
return Url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes a hashCode for this file based on the URL string and IP
|
/// Computes a hashCode for this file based on the URL string and IP
|
||||||
/// address if the server.
|
/// address if the server.
|
||||||
|
|
|
@ -189,7 +189,7 @@ namespace System.Net
|
||||||
internal
|
internal
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
IPNetwork(BigInteger ipaddress, AddressFamily family, byte cidr)
|
IPNetwork(BigInteger ipaddress, AddressFamily family, byte cidr)
|
||||||
{
|
{
|
||||||
|
|
||||||
int maxCidr = family == Sockets.AddressFamily.InterNetwork ? 32 : 128;
|
int maxCidr = family == Sockets.AddressFamily.InterNetwork ? 32 : 128;
|
||||||
|
@ -1164,18 +1164,6 @@ namespace System.Net
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static Contains is deprecated, please use instance Contains.")]
|
|
||||||
public static bool Contains(IPNetwork network, IPAddress ipaddress)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (network == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("network");
|
|
||||||
}
|
|
||||||
|
|
||||||
return network.Contains(ipaddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// return true is network2 is fully contained in network
|
/// return true is network2 is fully contained in network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1201,18 +1189,6 @@ namespace System.Net
|
||||||
return contains;
|
return contains;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static Contains is deprecated, please use instance Contains.")]
|
|
||||||
public static bool Contains(IPNetwork network, IPNetwork network2)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (network == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("network");
|
|
||||||
}
|
|
||||||
|
|
||||||
return network.Contains(network2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region overlap
|
#region overlap
|
||||||
|
@ -1245,18 +1221,6 @@ namespace System.Net
|
||||||
return overlap;
|
return overlap;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static Overlap is deprecated, please use instance Overlap.")]
|
|
||||||
public static bool Overlap(IPNetwork network, IPNetwork network2)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (network == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("network");
|
|
||||||
}
|
|
||||||
|
|
||||||
return network.Overlap(network2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ToString
|
#region ToString
|
||||||
|
@ -1341,18 +1305,6 @@ namespace System.Net
|
||||||
|| IPNetwork.IANA_CBLK_RESERVED1.Contains(this);
|
|| IPNetwork.IANA_CBLK_RESERVED1.Contains(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static IsIANAReserved is deprecated, please use instance IsIANAReserved.")]
|
|
||||||
public static bool IsIANAReserved(IPNetwork ipnetwork)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (ipnetwork == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("ipnetwork");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipnetwork.IsIANAReserved();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Subnet
|
#region Subnet
|
||||||
|
@ -1371,16 +1323,6 @@ namespace System.Net
|
||||||
return ipnetworkCollection;
|
return ipnetworkCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static Subnet is deprecated, please use instance Subnet.")]
|
|
||||||
public static IPNetworkCollection Subnet(IPNetwork network, byte cidr)
|
|
||||||
{
|
|
||||||
if (network == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("network");
|
|
||||||
}
|
|
||||||
return network.Subnet(cidr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subnet a network into multiple nets of cidr mask
|
/// Subnet a network into multiple nets of cidr mask
|
||||||
/// Subnet 192.168.0.0/24 into cidr 25 gives 192.168.0.0/25, 192.168.0.128/25
|
/// Subnet 192.168.0.0/24 into cidr 25 gives 192.168.0.0/25, 192.168.0.128/25
|
||||||
|
@ -1402,16 +1344,6 @@ namespace System.Net
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static TrySubnet is deprecated, please use instance TrySubnet.")]
|
|
||||||
public static bool TrySubnet(IPNetwork network, byte cidr, out IPNetworkCollection ipnetworkCollection)
|
|
||||||
{
|
|
||||||
if (network == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("network");
|
|
||||||
}
|
|
||||||
return network.TrySubnet(cidr, out ipnetworkCollection);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if TRAVISCI
|
#if TRAVISCI
|
||||||
public
|
public
|
||||||
#else
|
#else
|
||||||
|
@ -1476,12 +1408,6 @@ namespace System.Net
|
||||||
return supernet;
|
return supernet;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static Supernet is deprecated, please use instance Supernet.")]
|
|
||||||
public static IPNetwork Supernet(IPNetwork network, IPNetwork network2)
|
|
||||||
{
|
|
||||||
return network.Supernet(network2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try to supernet two consecutive cidr equal subnet into a single one
|
/// Try to supernet two consecutive cidr equal subnet into a single one
|
||||||
/// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23
|
/// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23
|
||||||
|
@ -1500,16 +1426,6 @@ namespace System.Net
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static TrySupernet is deprecated, please use instance TrySupernet.")]
|
|
||||||
public static bool TrySupernet(IPNetwork network, IPNetwork network2, out IPNetwork supernet)
|
|
||||||
{
|
|
||||||
if (network == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("network");
|
|
||||||
}
|
|
||||||
return network.TrySupernet(network2, out supernet);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if TRAVISCI
|
#if TRAVISCI
|
||||||
public
|
public
|
||||||
#else
|
#else
|
||||||
|
@ -1920,18 +1836,6 @@ namespace System.Net
|
||||||
return sw.ToString();
|
return sw.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("static Print is deprecated, please use instance Print.")]
|
|
||||||
public static string Print(IPNetwork ipnetwork)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (ipnetwork == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("ipnetwork");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipnetwork.Print();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region TryGuessCidr
|
#region TryGuessCidr
|
||||||
|
@ -2018,12 +1922,6 @@ namespace System.Net
|
||||||
|
|
||||||
#region ListIPAddress
|
#region ListIPAddress
|
||||||
|
|
||||||
[Obsolete("static ListIPAddress is deprecated, please use instance ListIPAddress.")]
|
|
||||||
public static IPAddressCollection ListIPAddress(IPNetwork ipnetwork)
|
|
||||||
{
|
|
||||||
return ipnetwork.ListIPAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPAddressCollection ListIPAddress()
|
public IPAddressCollection ListIPAddress()
|
||||||
{
|
{
|
||||||
return new IPAddressCollection(this);
|
return new IPAddressCollection(this);
|
||||||
|
@ -2167,4 +2065,4 @@ namespace System.Net
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
using MediaBrowser.Common;
|
|
||||||
using MediaBrowser.Common.Updates;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Common.Progress;
|
|
||||||
using MediaBrowser.Model.Tasks;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.ScheduledTasks
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Plugin Update Task
|
|
||||||
/// </summary>
|
|
||||||
public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The _logger
|
|
||||||
/// </summary>
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
private readonly IInstallationManager _installationManager;
|
|
||||||
|
|
||||||
private readonly IApplicationHost _appHost;
|
|
||||||
|
|
||||||
public PluginUpdateTask(ILogger logger, IInstallationManager installationManager, IApplicationHost appHost)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_installationManager = installationManager;
|
|
||||||
_appHost = appHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates the triggers that define when the task will run
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
|
||||||
{
|
|
||||||
return new[] {
|
|
||||||
|
|
||||||
// At startup
|
|
||||||
new TaskTriggerInfo {Type = TaskTriggerInfo.TriggerStartup},
|
|
||||||
|
|
||||||
// Every so often
|
|
||||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Key
|
|
||||||
{
|
|
||||||
get { return "PluginUpdates"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update installed plugins
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <param name="progress">The progress.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
|
||||||
{
|
|
||||||
progress.Report(0);
|
|
||||||
|
|
||||||
var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, true, cancellationToken).ConfigureAwait(false)).ToList();
|
|
||||||
|
|
||||||
progress.Report(10);
|
|
||||||
|
|
||||||
var numComplete = 0;
|
|
||||||
|
|
||||||
foreach (var package in packagesToInstall)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _installationManager.InstallPackage(package, true, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// InstallPackage has it's own inner cancellation token, so only throw this if it's ours
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (HttpException ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error downloading {name}", package.name);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error updating {name}", package.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update progress
|
|
||||||
lock (progress)
|
|
||||||
{
|
|
||||||
numComplete++;
|
|
||||||
double percent = numComplete;
|
|
||||||
percent /= packagesToInstall.Count;
|
|
||||||
|
|
||||||
progress.Report(90 * percent + 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.Report(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the task
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get { return "Check for plugin updates"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The description.</value>
|
|
||||||
public string Description
|
|
||||||
{
|
|
||||||
get { return "Downloads and installs updates for plugins that are configured to update automatically."; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Category
|
|
||||||
{
|
|
||||||
get { return "Application"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsHidden => true;
|
|
||||||
|
|
||||||
public bool IsEnabled => true;
|
|
||||||
|
|
||||||
public bool IsLogged => true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
using MediaBrowser.Common;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Common.Progress;
|
|
||||||
using MediaBrowser.Model.Tasks;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.ScheduledTasks
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Plugin Update Task
|
|
||||||
/// </summary>
|
|
||||||
public class SystemUpdateTask : IScheduledTask
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The _app host
|
|
||||||
/// </summary>
|
|
||||||
private readonly IApplicationHost _appHost;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the configuration manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The configuration manager.</value>
|
|
||||||
private IConfigurationManager ConfigurationManager { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the logger.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The logger.</value>
|
|
||||||
private ILogger Logger { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="SystemUpdateTask" /> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="appHost">The app host.</param>
|
|
||||||
/// <param name="configurationManager">The configuration manager.</param>
|
|
||||||
/// <param name="logger">The logger.</param>
|
|
||||||
public SystemUpdateTask(IApplicationHost appHost, IConfigurationManager configurationManager, ILogger logger)
|
|
||||||
{
|
|
||||||
_appHost = appHost;
|
|
||||||
ConfigurationManager = configurationManager;
|
|
||||||
Logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates the triggers that define when the task will run
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
|
||||||
{
|
|
||||||
return new[] {
|
|
||||||
|
|
||||||
// At startup
|
|
||||||
new TaskTriggerInfo {Type = TaskTriggerInfo.TriggerStartup},
|
|
||||||
|
|
||||||
// Every so often
|
|
||||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the task to be executed
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <param name="progress">The progress.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
|
||||||
{
|
|
||||||
// Create a progress object for the update check
|
|
||||||
var updateInfo = await _appHost.CheckForApplicationUpdate(cancellationToken, new SimpleProgress<double>()).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!updateInfo.IsUpdateAvailable)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("No application update available.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
if (!_appHost.CanSelfUpdate) return;
|
|
||||||
|
|
||||||
if (ConfigurationManager.CommonConfiguration.EnableAutoUpdate)
|
|
||||||
{
|
|
||||||
Logger.LogInformation("Update Revision {0} available. Updating...", updateInfo.AvailableVersion);
|
|
||||||
|
|
||||||
await _appHost.UpdateApplication(updateInfo.Package, cancellationToken, progress).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogInformation("A new version of " + _appHost.Name + " is available.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the task
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get { return "Check for application updates"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The description.</value>
|
|
||||||
public string Description
|
|
||||||
{
|
|
||||||
get { return "Downloads and installs application updates."; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the category.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The category.</value>
|
|
||||||
public string Category
|
|
||||||
{
|
|
||||||
get { return "Application"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Key
|
|
||||||
{
|
|
||||||
get { return "SystemUpdateTask"; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,8 +11,8 @@ namespace Jellyfin.Server
|
||||||
{
|
{
|
||||||
public class CoreAppHost : ApplicationHost
|
public class CoreAppHost : ApplicationHost
|
||||||
{
|
{
|
||||||
public CoreAppHost(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, StartupOptions options, IFileSystem fileSystem, IPowerManagement powerManagement, string releaseAssetFilename, IEnvironmentInfo environmentInfo, MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder, ISystemEvents systemEvents, MediaBrowser.Common.Net.INetworkManager networkManager)
|
public CoreAppHost(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, StartupOptions options, IFileSystem fileSystem, IPowerManagement powerManagement, IEnvironmentInfo environmentInfo, MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder, ISystemEvents systemEvents, MediaBrowser.Common.Net.INetworkManager networkManager)
|
||||||
: base(applicationPaths, loggerFactory, options, fileSystem, powerManagement, releaseAssetFilename, environmentInfo, imageEncoder, systemEvents, networkManager)
|
: base(applicationPaths, loggerFactory, options, fileSystem, powerManagement, environmentInfo, imageEncoder, systemEvents, networkManager)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,6 @@ namespace Jellyfin.Server
|
||||||
options,
|
options,
|
||||||
fileSystem,
|
fileSystem,
|
||||||
new PowerManagement(),
|
new PowerManagement(),
|
||||||
"embyserver-mono_{version}.zip",
|
|
||||||
environmentInfo,
|
environmentInfo,
|
||||||
new NullImageEncoder(),
|
new NullImageEncoder(),
|
||||||
new SystemEvents(_loggerFactory.CreateLogger("SystemEvents")),
|
new SystemEvents(_loggerFactory.CreateLogger("SystemEvents")),
|
||||||
|
|
|
@ -34,25 +34,5 @@ namespace MediaBrowser.Common.Extensions
|
||||||
{
|
{
|
||||||
return CryptographyProvider.GetMD5(str);
|
return CryptographyProvider.GetMD5(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the MB id.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="str">The STR.</param>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
/// <returns>Guid.</returns>
|
|
||||||
/// <exception cref="System.ArgumentNullException">type</exception>
|
|
||||||
[Obsolete("Use LibraryManager.GetNewItemId")]
|
|
||||||
public static Guid GetMBId(this string str, Type type)
|
|
||||||
{
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("type");
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = type.FullName + str.ToLower();
|
|
||||||
|
|
||||||
return key.GetMD5();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,12 +85,6 @@ namespace MediaBrowser.Common
|
||||||
/// <returns>IEnumerable{``0}.</returns>
|
/// <returns>IEnumerable{``0}.</returns>
|
||||||
IEnumerable<T> GetExports<T>(bool manageLiftime = true);
|
IEnumerable<T> GetExports<T>(bool manageLiftime = true);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks for update.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Task{CheckForUpdateResult}.</returns>
|
|
||||||
Task<CheckForUpdateResult> CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress<double> progress);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the application.
|
/// Updates the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,274 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using MediaBrowser.Model.Updates;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Updates
|
|
||||||
{
|
|
||||||
public class GithubUpdater
|
|
||||||
{
|
|
||||||
private readonly IHttpClient _httpClient;
|
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
|
||||||
|
|
||||||
public GithubUpdater(IHttpClient httpClient, IJsonSerializer jsonSerializer)
|
|
||||||
{
|
|
||||||
_httpClient = httpClient;
|
|
||||||
_jsonSerializer = jsonSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CheckForUpdateResult> CheckForUpdateResult(string organzation, string repository, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename, TimeSpan cacheLength, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
|
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
|
||||||
{
|
|
||||||
Url = url,
|
|
||||||
EnableKeepAlive = false,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
UserAgent = "Emby/3.0",
|
|
||||||
BufferContent = false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cacheLength.Ticks > 0)
|
|
||||||
{
|
|
||||||
options.CacheMode = CacheMode.Unconditional;
|
|
||||||
options.CacheLength = cacheLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
|
|
||||||
using (var stream = response.Content)
|
|
||||||
{
|
|
||||||
var obj = await _jsonSerializer.DeserializeFromStreamAsync<RootObject[]>(stream).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return CheckForUpdateResult(obj, minVersion, updateLevel, assetFilename, packageName, targetFilename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CheckForUpdateResult CheckForUpdateResult(RootObject[] obj, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename)
|
|
||||||
{
|
|
||||||
if (updateLevel == PackageVersionClass.Release)
|
|
||||||
{
|
|
||||||
// Technically all we need to do is check that it's not pre-release
|
|
||||||
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
|
|
||||||
obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
|
|
||||||
}
|
|
||||||
else if (updateLevel == PackageVersionClass.Beta)
|
|
||||||
{
|
|
||||||
obj = obj.Where(i => i.prerelease && i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase)).ToArray();
|
|
||||||
}
|
|
||||||
else if (updateLevel == PackageVersionClass.Dev)
|
|
||||||
{
|
|
||||||
obj = obj.Where(i => !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) || i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
var availableUpdate = obj
|
|
||||||
.Select(i => CheckForUpdateResult(i, minVersion, assetFilename, packageName, targetFilename))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.OrderByDescending(i => Version.Parse(i.AvailableVersion))
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
return availableUpdate ?? new CheckForUpdateResult
|
|
||||||
{
|
|
||||||
IsUpdateAvailable = false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool MatchesUpdateLevel(RootObject i, PackageVersionClass updateLevel)
|
|
||||||
{
|
|
||||||
if (updateLevel == PackageVersionClass.Beta)
|
|
||||||
{
|
|
||||||
return i.prerelease && i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
if (updateLevel == PackageVersionClass.Dev)
|
|
||||||
{
|
|
||||||
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Technically all we need to do is check that it's not pre-release
|
|
||||||
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
|
|
||||||
return !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<RootObject>> GetLatestReleases(string organzation, string repository, string assetFilename, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var list = new List<RootObject>();
|
|
||||||
|
|
||||||
var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
|
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
|
||||||
{
|
|
||||||
Url = url,
|
|
||||||
EnableKeepAlive = false,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
UserAgent = "Emby/3.0",
|
|
||||||
BufferContent = false
|
|
||||||
};
|
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
|
|
||||||
using (var stream = response.Content)
|
|
||||||
{
|
|
||||||
var obj = await _jsonSerializer.DeserializeFromStreamAsync<RootObject[]>(stream).ConfigureAwait(false);
|
|
||||||
|
|
||||||
obj = obj.Where(i => (i.assets ?? new List<Asset>()).Any(a => IsAsset(a, assetFilename, i.tag_name))).ToArray();
|
|
||||||
|
|
||||||
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Release)).OrderByDescending(GetVersion).Take(1));
|
|
||||||
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Beta)).OrderByDescending(GetVersion).Take(1));
|
|
||||||
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Dev)).OrderByDescending(GetVersion).Take(1));
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Version GetVersion(RootObject obj)
|
|
||||||
{
|
|
||||||
Version version;
|
|
||||||
if (!Version.TryParse(obj.tag_name, out version))
|
|
||||||
{
|
|
||||||
return new Version(1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CheckForUpdateResult CheckForUpdateResult(RootObject obj, Version minVersion, string assetFilename, string packageName, string targetFilename)
|
|
||||||
{
|
|
||||||
Version version;
|
|
||||||
var versionString = obj.tag_name;
|
|
||||||
if (!Version.TryParse(versionString, out version))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version < minVersion)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var asset = (obj.assets ?? new List<Asset>()).FirstOrDefault(i => IsAsset(i, assetFilename, versionString));
|
|
||||||
|
|
||||||
if (asset == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CheckForUpdateResult
|
|
||||||
{
|
|
||||||
AvailableVersion = version.ToString(),
|
|
||||||
IsUpdateAvailable = version > minVersion,
|
|
||||||
Package = new PackageVersionInfo
|
|
||||||
{
|
|
||||||
classification = obj.prerelease ?
|
|
||||||
(obj.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase) ? PackageVersionClass.Dev : PackageVersionClass.Beta) :
|
|
||||||
PackageVersionClass.Release,
|
|
||||||
name = packageName,
|
|
||||||
sourceUrl = asset.browser_download_url,
|
|
||||||
targetFilename = targetFilename,
|
|
||||||
versionStr = version.ToString(),
|
|
||||||
requiredVersionStr = "1.0.0",
|
|
||||||
description = obj.body,
|
|
||||||
infoUrl = obj.html_url
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsAsset(Asset asset, string assetFilename, string version)
|
|
||||||
{
|
|
||||||
var downloadFilename = Path.GetFileName(asset.browser_download_url) ?? string.Empty;
|
|
||||||
|
|
||||||
assetFilename = assetFilename.Replace("{version}", version);
|
|
||||||
|
|
||||||
if (downloadFilename.IndexOf(assetFilename, StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(assetFilename, downloadFilename, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Uploader
|
|
||||||
{
|
|
||||||
public string login { get; set; }
|
|
||||||
public int id { get; set; }
|
|
||||||
public string avatar_url { get; set; }
|
|
||||||
public string gravatar_id { get; set; }
|
|
||||||
public string url { get; set; }
|
|
||||||
public string html_url { get; set; }
|
|
||||||
public string followers_url { get; set; }
|
|
||||||
public string following_url { get; set; }
|
|
||||||
public string gists_url { get; set; }
|
|
||||||
public string starred_url { get; set; }
|
|
||||||
public string subscriptions_url { get; set; }
|
|
||||||
public string organizations_url { get; set; }
|
|
||||||
public string repos_url { get; set; }
|
|
||||||
public string events_url { get; set; }
|
|
||||||
public string received_events_url { get; set; }
|
|
||||||
public string type { get; set; }
|
|
||||||
public bool site_admin { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Asset
|
|
||||||
{
|
|
||||||
public string url { get; set; }
|
|
||||||
public int id { get; set; }
|
|
||||||
public string name { get; set; }
|
|
||||||
public object label { get; set; }
|
|
||||||
public Uploader uploader { get; set; }
|
|
||||||
public string content_type { get; set; }
|
|
||||||
public string state { get; set; }
|
|
||||||
public int size { get; set; }
|
|
||||||
public int download_count { get; set; }
|
|
||||||
public string created_at { get; set; }
|
|
||||||
public string updated_at { get; set; }
|
|
||||||
public string browser_download_url { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Author
|
|
||||||
{
|
|
||||||
public string login { get; set; }
|
|
||||||
public int id { get; set; }
|
|
||||||
public string avatar_url { get; set; }
|
|
||||||
public string gravatar_id { get; set; }
|
|
||||||
public string url { get; set; }
|
|
||||||
public string html_url { get; set; }
|
|
||||||
public string followers_url { get; set; }
|
|
||||||
public string following_url { get; set; }
|
|
||||||
public string gists_url { get; set; }
|
|
||||||
public string starred_url { get; set; }
|
|
||||||
public string subscriptions_url { get; set; }
|
|
||||||
public string organizations_url { get; set; }
|
|
||||||
public string repos_url { get; set; }
|
|
||||||
public string events_url { get; set; }
|
|
||||||
public string received_events_url { get; set; }
|
|
||||||
public string type { get; set; }
|
|
||||||
public bool site_admin { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RootObject
|
|
||||||
{
|
|
||||||
public string url { get; set; }
|
|
||||||
public string assets_url { get; set; }
|
|
||||||
public string upload_url { get; set; }
|
|
||||||
public string html_url { get; set; }
|
|
||||||
public int id { get; set; }
|
|
||||||
public string tag_name { get; set; }
|
|
||||||
public string target_commitish { get; set; }
|
|
||||||
public string name { get; set; }
|
|
||||||
public bool draft { get; set; }
|
|
||||||
public Author author { get; set; }
|
|
||||||
public bool prerelease { get; set; }
|
|
||||||
public string created_at { get; set; }
|
|
||||||
public string published_at { get; set; }
|
|
||||||
public List<Asset> assets { get; set; }
|
|
||||||
public string tarball_url { get; set; }
|
|
||||||
public string zipball_url { get; set; }
|
|
||||||
public string body { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
using MediaBrowser.Model.Diagnostics;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -18,21 +19,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
_processFactory = processFactory;
|
_processFactory = processFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tuple<List<string>, List<string>> Validate(string encoderPath)
|
public (IEnumerable<string> decoders, IEnumerable<string> encoders) Validate(string encoderPath)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Validating media encoder at {0}", encoderPath);
|
_logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath);
|
||||||
|
|
||||||
var decoders = GetDecoders(encoderPath);
|
var decoders = GetCodecs(encoderPath, Codec.Decoder);
|
||||||
var encoders = GetEncoders(encoderPath);
|
var encoders = GetCodecs(encoderPath, Codec.Encoder);
|
||||||
|
|
||||||
_logger.LogInformation("Encoder validation complete");
|
_logger.LogInformation("Encoder validation complete");
|
||||||
|
|
||||||
return new Tuple<List<string>, List<string>>(decoders, encoders);
|
return (decoders, encoders);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ValidateVersion(string encoderAppPath, bool logOutput)
|
public bool ValidateVersion(string encoderAppPath, bool logOutput)
|
||||||
{
|
{
|
||||||
string output = string.Empty;
|
string output = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
output = GetProcessOutput(encoderAppPath, "-version");
|
output = GetProcessOutput(encoderAppPath, "-version");
|
||||||
|
@ -71,20 +72,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> GetDecoders(string encoderAppPath)
|
private static readonly string[] requiredDecoders = new[]
|
||||||
{
|
|
||||||
string output = string.Empty;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
output = GetProcessOutput(encoderAppPath, "-decoders");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error detecting available decoders");
|
|
||||||
}
|
|
||||||
|
|
||||||
var found = new List<string>();
|
|
||||||
var required = new[]
|
|
||||||
{
|
{
|
||||||
"mpeg2video",
|
"mpeg2video",
|
||||||
"h264_qsv",
|
"h264_qsv",
|
||||||
|
@ -101,33 +89,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
"hevc"
|
"hevc"
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var codec in required)
|
private static readonly string[] requiredEncoders = new[]
|
||||||
{
|
|
||||||
var srch = " " + codec + " ";
|
|
||||||
|
|
||||||
if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Decoder available: " + codec);
|
|
||||||
found.Add(codec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> GetEncoders(string encoderAppPath)
|
|
||||||
{
|
|
||||||
string output = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
output = GetProcessOutput(encoderAppPath, "-encoders");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
var found = new List<string>();
|
|
||||||
var required = new[]
|
|
||||||
{
|
{
|
||||||
"libx264",
|
"libx264",
|
||||||
"libx265",
|
"libx265",
|
||||||
|
@ -151,32 +113,46 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
"ac3"
|
"ac3"
|
||||||
};
|
};
|
||||||
|
|
||||||
output = output ?? string.Empty;
|
private enum Codec
|
||||||
|
{
|
||||||
|
Encoder,
|
||||||
|
Decoder
|
||||||
|
}
|
||||||
|
|
||||||
var index = 0;
|
private IEnumerable<string> GetCodecs(string encoderAppPath, Codec codec)
|
||||||
|
{
|
||||||
foreach (var codec in required)
|
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
|
||||||
|
string output = null;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var srch = " " + codec + " ";
|
output = GetProcessOutput(encoderAppPath, "-" + codecstr);
|
||||||
|
|
||||||
if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
|
||||||
if (index < required.Length - 1)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Encoder available: " + codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
found.Add(codec);
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error detecting available {Codec}", codecstr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(output))
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var required = codec == Codec.Encoder ? requiredEncoders : requiredDecoders;
|
||||||
|
|
||||||
|
var found = Regex
|
||||||
|
.Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
|
||||||
|
.Cast<Match>()
|
||||||
|
.Select(x => x.Groups["codec"].Value)
|
||||||
|
.Where(x => required.Contains(x));
|
||||||
|
|
||||||
|
_logger.LogInformation("Available {Codec}: {Codecs}", codecstr, found);
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetProcessOutput(string path, string arguments)
|
private string GetProcessOutput(string path, string arguments)
|
||||||
{
|
{
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
IProcess process = _processFactory.Create(new ProcessOptions
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
|
@ -187,7 +163,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
RedirectStandardOutput = true
|
RedirectStandardOutput = true
|
||||||
});
|
});
|
||||||
|
|
||||||
_logger.LogInformation("Running {path} {arguments}", path, arguments);
|
_logger.LogInformation("Running {Path} {Arguments}", path, arguments);
|
||||||
|
|
||||||
using (process)
|
using (process)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Common.Progress;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Encoder
|
|
||||||
{
|
|
||||||
public class FontConfigLoader
|
|
||||||
{
|
|
||||||
private readonly IHttpClient _httpClient;
|
|
||||||
private readonly IApplicationPaths _appPaths;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IZipClient _zipClient;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
private readonly string[] _fontUrls =
|
|
||||||
{
|
|
||||||
"https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z"
|
|
||||||
};
|
|
||||||
|
|
||||||
public FontConfigLoader(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, IZipClient zipClient, IFileSystem fileSystem)
|
|
||||||
{
|
|
||||||
_httpClient = httpClient;
|
|
||||||
_appPaths = appPaths;
|
|
||||||
_logger = logger;
|
|
||||||
_zipClient = zipClient;
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extracts the fonts.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="targetPath">The target path.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
public async Task DownloadFonts(string targetPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var fontsDirectory = Path.Combine(targetPath, "fonts");
|
|
||||||
|
|
||||||
_fileSystem.CreateDirectory(fontsDirectory);
|
|
||||||
|
|
||||||
const string fontFilename = "ARIALUNI.TTF";
|
|
||||||
|
|
||||||
var fontFile = Path.Combine(fontsDirectory, fontFilename);
|
|
||||||
|
|
||||||
if (_fileSystem.FileExists(fontFile))
|
|
||||||
{
|
|
||||||
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Kick this off, but no need to wait on it
|
|
||||||
var task = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await DownloadFontFile(fontsDirectory, fontFilename, new SimpleProgress<double>()).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (HttpException ex)
|
|
||||||
{
|
|
||||||
// Don't let the server crash because of this
|
|
||||||
_logger.LogError(ex, "Error downloading ffmpeg font files");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Don't let the server crash because of this
|
|
||||||
_logger.LogError(ex, "Error writing ffmpeg font files");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Downloads the font file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fontsDirectory">The fonts directory.</param>
|
|
||||||
/// <param name="fontFilename">The font filename.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress)
|
|
||||||
{
|
|
||||||
var existingFile = _fileSystem
|
|
||||||
.GetFilePaths(_appPaths.ProgramDataPath, true)
|
|
||||||
.FirstOrDefault(i => string.Equals(fontFilename, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (existingFile != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
// Log this, but don't let it fail the operation
|
|
||||||
_logger.LogError(ex, "Error copying file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string tempFile = null;
|
|
||||||
|
|
||||||
foreach (var url in _fontUrls)
|
|
||||||
{
|
|
||||||
progress.Report(0);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
|
|
||||||
{
|
|
||||||
Url = url,
|
|
||||||
Progress = progress
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// The core can function without the font file, so handle this
|
|
||||||
_logger.LogError(ex, "Failed to download ffmpeg font file from {url}", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(tempFile))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Extract7zArchive(tempFile, fontsDirectory);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_fileSystem.DeleteFile(tempFile);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
// Log this, but don't let it fail the operation
|
|
||||||
_logger.LogError(ex, "Error deleting temp file {path}", tempFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void Extract7zArchive(string archivePath, string targetPath)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Extracting {ArchivePath} to {TargetPath}", archivePath, targetPath);
|
|
||||||
|
|
||||||
_zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the font config file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fontsDirectory">The fonts directory.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private async Task WriteFontConfigFile(string fontsDirectory)
|
|
||||||
{
|
|
||||||
const string fontConfigFilename = "fonts.conf";
|
|
||||||
var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
|
|
||||||
|
|
||||||
if (!_fileSystem.FileExists(fontConfigFile))
|
|
||||||
{
|
|
||||||
var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(contents);
|
|
||||||
|
|
||||||
using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileOpenMode.Create, FileAccessMode.Write,
|
|
||||||
FileShareMode.Read, true))
|
|
||||||
{
|
|
||||||
await fileStream.WriteAsync(bytes, 0, bytes.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -70,13 +70,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
private readonly string _originalFFMpegPath;
|
private readonly string _originalFFMpegPath;
|
||||||
private readonly string _originalFFProbePath;
|
private readonly string _originalFFProbePath;
|
||||||
private readonly int DefaultImageExtractionTimeoutMs;
|
private readonly int DefaultImageExtractionTimeoutMs;
|
||||||
private readonly bool EnableEncoderFontFile;
|
|
||||||
|
|
||||||
private readonly IEnvironmentInfo _environmentInfo;
|
private readonly IEnvironmentInfo _environmentInfo;
|
||||||
|
|
||||||
public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IProcessFactory processFactory,
|
public MediaEncoder(ILogger logger,
|
||||||
|
IJsonSerializer jsonSerializer,
|
||||||
|
string ffMpegPath,
|
||||||
|
string ffProbePath,
|
||||||
|
bool hasExternalEncoder,
|
||||||
|
IServerConfigurationManager configurationManager,
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
ILiveTvManager liveTvManager,
|
||||||
|
IIsoManager isoManager,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
IChannelManager channelManager,
|
||||||
|
ISessionManager sessionManager,
|
||||||
|
Func<ISubtitleEncoder> subtitleEncoder,
|
||||||
|
Func<IMediaSourceManager> mediaSourceManager,
|
||||||
|
IHttpClient httpClient,
|
||||||
|
IZipClient zipClient,
|
||||||
|
IProcessFactory processFactory,
|
||||||
int defaultImageExtractionTimeoutMs,
|
int defaultImageExtractionTimeoutMs,
|
||||||
bool enableEncoderFontFile, IEnvironmentInfo environmentInfo)
|
IEnvironmentInfo environmentInfo)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
|
@ -93,7 +107,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
_zipClient = zipClient;
|
_zipClient = zipClient;
|
||||||
_processFactory = processFactory;
|
_processFactory = processFactory;
|
||||||
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
|
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
|
||||||
EnableEncoderFontFile = enableEncoderFontFile;
|
|
||||||
_environmentInfo = environmentInfo;
|
_environmentInfo = environmentInfo;
|
||||||
FFProbePath = ffProbePath;
|
FFProbePath = ffProbePath;
|
||||||
FFMpegPath = ffMpegPath;
|
FFMpegPath = ffMpegPath;
|
||||||
|
@ -175,18 +188,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
{
|
{
|
||||||
var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
|
var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
|
||||||
|
|
||||||
SetAvailableDecoders(result.Item1);
|
SetAvailableDecoders(result.decoders);
|
||||||
SetAvailableEncoders(result.Item2);
|
SetAvailableEncoders(result.encoders);
|
||||||
|
|
||||||
if (EnableEncoderFontFile)
|
|
||||||
{
|
|
||||||
var directory = FileSystem.GetDirectoryName(FFMpegPath);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(directory) && FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory))
|
|
||||||
{
|
|
||||||
new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, FileSystem).DownloadFonts(directory).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,14 +404,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> _encoders = new List<string>();
|
private List<string> _encoders = new List<string>();
|
||||||
public void SetAvailableEncoders(List<string> list)
|
public void SetAvailableEncoders(IEnumerable<string> list)
|
||||||
{
|
{
|
||||||
_encoders = list.ToList();
|
_encoders = list.ToList();
|
||||||
//_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
|
//_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> _decoders = new List<string>();
|
private List<string> _decoders = new List<string>();
|
||||||
public void SetAvailableDecoders(List<string> list)
|
public void SetAvailableDecoders(IEnumerable<string> list)
|
||||||
{
|
{
|
||||||
_decoders = list.ToList();
|
_decoders = list.ToList();
|
||||||
//_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
|
//_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Devices
|
namespace MediaBrowser.Model.Devices
|
||||||
{
|
{
|
||||||
public class DeviceQuery
|
public class DeviceQuery
|
||||||
|
@ -17,6 +19,6 @@ namespace MediaBrowser.Model.Devices
|
||||||
/// Gets or sets the user identifier.
|
/// Gets or sets the user identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The user identifier.</value>
|
/// <value>The user identifier.</value>
|
||||||
public string UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user