Merge branch 'dev' into project-updates

This commit is contained in:
Anthony Lavado 2019-01-02 14:02:48 -05:00 committed by GitHub
commit a06a5c8d18
18 changed files with 85 additions and 1025 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
!*
.directory
#################
## Eclipse
#################

View File

@ -9,15 +9,16 @@
- [bfayers](https://github.com/bfayers)
- [Bond_009](https://github.com/Bond-009)
- [AnthonyLavado](https://github.com/anthonylavado)
- [sparky8251](https://github.com/sparky8251)
# Emby Contributors
- [LukePulverenti](https://github.com/LukePulverenti)
- [ebr11](https://github.com/ebr11)
- [lalmanzar](https://github.com/lalmanzar)
- [schneifu](https://github.com/schneifu)
- [Mark2xv](https://github.com/Mark2xv)
- [ScottRapsey](https://github.com/ScottRapsey)
- [LukePulverenti](https://github.com/LukePulverenti)
- [ebr11](https://github.com/ebr11)
- [lalmanzar](https://github.com/lalmanzar)
- [schneifu](https://github.com/schneifu)
- [Mark2xv](https://github.com/Mark2xv)
- [ScottRapsey](https://github.com/ScottRapsey)
- [skynet600](https://github.com/skynet600)
- [Cheesegeezer](https://githum.com/Cheesegeezer)
- [Radeon](https://github.com/radeonorama)

View File

@ -368,7 +368,6 @@ namespace Emby.Server.Implementations
protected IAuthService AuthService { get; private set; }
public StartupOptions StartupOptions { get; private set; }
protected readonly string ReleaseAssetFilename;
internal IPowerManagement PowerManagement { get; private set; }
internal IImageEncoder ImageEncoder { get; private set; }
@ -393,7 +392,6 @@ namespace Emby.Server.Implementations
StartupOptions options,
IFileSystem fileSystem,
IPowerManagement powerManagement,
string releaseAssetFilename,
IEnvironmentInfo environmentInfo,
IImageEncoder imageEncoder,
ISystemEvents systemEvents,
@ -419,7 +417,6 @@ namespace Emby.Server.Implementations
Logger = LoggerFactory.CreateLogger("App");
StartupOptions = options;
ReleaseAssetFilename = releaseAssetFilename;
PowerManagement = powerManagement;
ImageEncoder = imageEncoder;
@ -1240,7 +1237,7 @@ namespace Emby.Server.Implementations
HttpClient,
ZipClient,
ProcessFactory,
5000, false,
5000,
EnvironmentInfo);
MediaEncoder = mediaEncoder;
@ -2286,56 +2283,6 @@ namespace Emby.Server.Implementations
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>
/// Updates the application.
/// </summary>

View File

@ -901,8 +901,8 @@ namespace Emby.Server.Implementations.Channels
private T GetItemById<T>(string idString, string channelName, out bool isNew)
where T : BaseItem, new()
{
var id = GetIdToHash(idString, channelName).GetMBId(typeof(T));
var id = _libraryManager.GetNewItemId(GetIdToHash(idString, channelName), typeof(T));
T item = null;
try

View File

@ -145,7 +145,8 @@ namespace Emby.Server.Implementations.Devices
HasUser = true
}).Items;
// TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
if (query.SupportsSync.HasValue)
{
var val = query.SupportsSync.Value;

View File

@ -3380,26 +3380,6 @@ namespace SharpCifs.Smb
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>
/// Computes a hashCode for this file based on the URL string and IP
/// address if the server.

View File

@ -189,7 +189,7 @@ namespace System.Net
internal
#endif
IPNetwork(BigInteger ipaddress, AddressFamily family, byte cidr)
IPNetwork(BigInteger ipaddress, AddressFamily family, byte cidr)
{
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>
/// return true is network2 is fully contained in network
/// </summary>
@ -1201,18 +1189,6 @@ namespace System.Net
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
#region overlap
@ -1245,18 +1221,6 @@ namespace System.Net
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
#region ToString
@ -1341,18 +1305,6 @@ namespace System.Net
|| 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
#region Subnet
@ -1371,16 +1323,6 @@ namespace System.Net
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>
/// 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
@ -1402,16 +1344,6 @@ namespace System.Net
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
public
#else
@ -1476,12 +1408,6 @@ namespace System.Net
return supernet;
}
[Obsolete("static Supernet is deprecated, please use instance Supernet.")]
public static IPNetwork Supernet(IPNetwork network, IPNetwork network2)
{
return network.Supernet(network2);
}
/// <summary>
/// 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
@ -1500,16 +1426,6 @@ namespace System.Net
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
public
#else
@ -1920,18 +1836,6 @@ namespace System.Net
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
#region TryGuessCidr
@ -2018,12 +1922,6 @@ namespace System.Net
#region ListIPAddress
[Obsolete("static ListIPAddress is deprecated, please use instance ListIPAddress.")]
public static IPAddressCollection ListIPAddress(IPNetwork ipnetwork)
{
return ipnetwork.ListIPAddress();
}
public IPAddressCollection ListIPAddress()
{
return new IPAddressCollection(this);
@ -2167,4 +2065,4 @@ namespace System.Net
#endregion
}
}
}

View File

@ -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;
}
}

View File

@ -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"; }
}
}
}

View File

@ -11,8 +11,8 @@ namespace Jellyfin.Server
{
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)
: base(applicationPaths, loggerFactory, options, fileSystem, powerManagement, releaseAssetFilename, environmentInfo, imageEncoder, systemEvents, 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, environmentInfo, imageEncoder, systemEvents, networkManager)
{
}

View File

@ -73,7 +73,6 @@ namespace Jellyfin.Server
options,
fileSystem,
new PowerManagement(),
"embyserver-mono_{version}.zip",
environmentInfo,
new NullImageEncoder(),
new SystemEvents(_loggerFactory.CreateLogger("SystemEvents")),

View File

@ -34,25 +34,5 @@ namespace MediaBrowser.Common.Extensions
{
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();
}
}
}

View File

@ -85,12 +85,6 @@ namespace MediaBrowser.Common
/// <returns>IEnumerable{``0}.</returns>
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>
/// Updates the application.
/// </summary>

View File

@ -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; }
}
}
}

View File

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using MediaBrowser.Model.Diagnostics;
using Microsoft.Extensions.Logging;
@ -18,21 +19,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
_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 encoders = GetEncoders(encoderPath);
var decoders = GetCodecs(encoderPath, Codec.Decoder);
var encoders = GetCodecs(encoderPath, Codec.Encoder);
_logger.LogInformation("Encoder validation complete");
return new Tuple<List<string>, List<string>>(decoders, encoders);
return (decoders, encoders);
}
public bool ValidateVersion(string encoderAppPath, bool logOutput)
{
string output = string.Empty;
string output = null;
try
{
output = GetProcessOutput(encoderAppPath, "-version");
@ -71,20 +72,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return true;
}
private List<string> GetDecoders(string encoderAppPath)
{
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[]
private static readonly string[] requiredDecoders = new[]
{
"mpeg2video",
"h264_qsv",
@ -101,33 +89,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"hevc"
};
foreach (var codec in required)
{
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[]
private static readonly string[] requiredEncoders = new[]
{
"libx264",
"libx265",
@ -151,32 +113,46 @@ namespace MediaBrowser.MediaEncoding.Encoder
"ac3"
};
output = output ?? string.Empty;
private enum Codec
{
Encoder,
Decoder
}
var index = 0;
foreach (var codec in required)
private IEnumerable<string> GetCodecs(string encoderAppPath, Codec codec)
{
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
string output = null;
try
{
var srch = " " + codec + " ";
if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
{
if (index < required.Length - 1)
{
_logger.LogInformation("Encoder available: " + codec);
}
found.Add(codec);
}
index++;
output = GetProcessOutput(encoderAppPath, "-" + codecstr);
}
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;
}
private string GetProcessOutput(string path, string arguments)
{
var process = _processFactory.Create(new ProcessOptions
IProcess process = _processFactory.Create(new ProcessOptions
{
CreateNoWindow = true,
UseShellExecute = false,
@ -187,7 +163,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
RedirectStandardOutput = true
});
_logger.LogInformation("Running {path} {arguments}", path, arguments);
_logger.LogInformation("Running {Path} {Arguments}", path, arguments);
using (process)
{

View File

@ -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);
}
}
}
}
}

View File

@ -70,13 +70,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly string _originalFFMpegPath;
private readonly string _originalFFProbePath;
private readonly int DefaultImageExtractionTimeoutMs;
private readonly bool EnableEncoderFontFile;
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,
bool enableEncoderFontFile, IEnvironmentInfo environmentInfo)
IEnvironmentInfo environmentInfo)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
@ -93,7 +107,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
_zipClient = zipClient;
_processFactory = processFactory;
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
EnableEncoderFontFile = enableEncoderFontFile;
_environmentInfo = environmentInfo;
FFProbePath = ffProbePath;
FFMpegPath = ffMpegPath;
@ -175,18 +188,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
SetAvailableDecoders(result.Item1);
SetAvailableEncoders(result.Item2);
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);
}
}
SetAvailableDecoders(result.decoders);
SetAvailableEncoders(result.encoders);
}
}
@ -401,14 +404,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
private List<string> _encoders = new List<string>();
public void SetAvailableEncoders(List<string> list)
public void SetAvailableEncoders(IEnumerable<string> list)
{
_encoders = list.ToList();
//_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
}
private List<string> _decoders = new List<string>();
public void SetAvailableDecoders(List<string> list)
public void SetAvailableDecoders(IEnumerable<string> list)
{
_decoders = list.ToList();
//_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));

View File

@ -1,4 +1,6 @@

using System;
namespace MediaBrowser.Model.Devices
{
public class DeviceQuery
@ -17,6 +19,6 @@ namespace MediaBrowser.Model.Devices
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
public Guid UserId { get; set; }
}
}