mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-09 03:36:17 +00:00
Merge branch 'master' into nullable3
This commit is contained in:
@@ -30,7 +30,6 @@ using Emby.Server.Implementations.Configuration;
|
||||
using Emby.Server.Implementations.Cryptography;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Emby.Server.Implementations.Devices;
|
||||
using Emby.Server.Implementations.Diagnostics;
|
||||
using Emby.Server.Implementations.Dto;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Emby.Server.Implementations.HttpServer.Security;
|
||||
@@ -86,7 +85,6 @@ using MediaBrowser.MediaEncoding.BdInfo;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
@@ -337,8 +335,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
internal IImageEncoder ImageEncoder { get; private set; }
|
||||
|
||||
protected IProcessFactory ProcessFactory { get; private set; }
|
||||
|
||||
protected readonly IXmlSerializer XmlSerializer;
|
||||
|
||||
protected ISocketFactory SocketFactory { get; private set; }
|
||||
@@ -680,9 +676,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton(XmlSerializer);
|
||||
|
||||
ProcessFactory = new ProcessFactory();
|
||||
serviceCollection.AddSingleton(ProcessFactory);
|
||||
|
||||
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
||||
|
||||
var cryptoProvider = new CryptographyProvider();
|
||||
@@ -743,7 +736,6 @@ namespace Emby.Server.Implementations
|
||||
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
|
||||
ServerConfigurationManager,
|
||||
FileSystemManager,
|
||||
ProcessFactory,
|
||||
LocalizationManager,
|
||||
() => SubtitleEncoder,
|
||||
startupConfig,
|
||||
@@ -857,8 +849,7 @@ namespace Emby.Server.Implementations
|
||||
FileSystemManager,
|
||||
MediaEncoder,
|
||||
HttpClient,
|
||||
MediaSourceManager,
|
||||
ProcessFactory);
|
||||
MediaSourceManager);
|
||||
serviceCollection.AddSingleton(SubtitleEncoder);
|
||||
|
||||
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
|
||||
@@ -1015,48 +1006,12 @@ namespace Emby.Server.Implementations
|
||||
AuthenticatedAttribute.AuthService = AuthService;
|
||||
}
|
||||
|
||||
private async void PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> args)
|
||||
{
|
||||
string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
|
||||
var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
|
||||
.Select(Assembly.LoadFrom)
|
||||
.SelectMany(x => x.ExportedTypes)
|
||||
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
|
||||
.ToArray();
|
||||
|
||||
int oldLen = _allConcreteTypes.Length;
|
||||
Array.Resize(ref _allConcreteTypes, oldLen + types.Length);
|
||||
types.CopyTo(_allConcreteTypes, oldLen);
|
||||
|
||||
var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin)))
|
||||
.Select(CreateInstanceSafe)
|
||||
.Where(x => x != null)
|
||||
.Cast<IPlugin>()
|
||||
.Select(LoadPlugin)
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
|
||||
oldLen = _plugins.Length;
|
||||
Array.Resize(ref _plugins, oldLen + plugins.Length);
|
||||
plugins.CopyTo(_plugins, oldLen);
|
||||
|
||||
var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint)))
|
||||
.Select(CreateInstanceSafe)
|
||||
.Where(x => x != null)
|
||||
.Cast<IServerEntryPoint>()
|
||||
.ToList();
|
||||
|
||||
await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false);
|
||||
await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the parts.
|
||||
/// </summary>
|
||||
public void FindParts()
|
||||
{
|
||||
InstallationManager = ServiceProvider.GetService<IInstallationManager>();
|
||||
InstallationManager.PluginInstalled += PluginInstalled;
|
||||
|
||||
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
|
||||
{
|
||||
@@ -1714,15 +1669,17 @@ namespace Emby.Server.Implementations
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
var process = ProcessFactory.Create(new ProcessOptions
|
||||
var process = new Process
|
||||
{
|
||||
FileName = url,
|
||||
EnableRaisingEvents = true,
|
||||
UseShellExecute = true,
|
||||
ErrorDialog = false
|
||||
});
|
||||
|
||||
process.Exited += ProcessExited;
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true,
|
||||
ErrorDialog = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
process.Exited += (sender, args) => ((Process)sender).Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1735,11 +1692,6 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
((IProcess)sender).Dispose();
|
||||
}
|
||||
|
||||
public virtual void EnableLoopback(string appName)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using MediaBrowser.Providers.Music;
|
||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||
|
||||
@@ -17,6 +18,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
{ HostWebClientKey, bool.TrueString },
|
||||
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
||||
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" },
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
||||
|
||||
@@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
public static void TryBind(this IStatement statement, string name, byte[] value)
|
||||
public static void TryBind(this IStatement statement, string name, ReadOnlySpan<byte> value)
|
||||
{
|
||||
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
||||
{
|
||||
|
||||
@@ -3315,7 +3315,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
|
||||
if (!char.IsLetter(str[i]) && !char.IsNumber(str[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -3339,7 +3339,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return IsAlphaNumeric(value);
|
||||
}
|
||||
|
||||
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
|
||||
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement)
|
||||
{
|
||||
if (query.IsResumable ?? false)
|
||||
{
|
||||
@@ -3351,27 +3351,27 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (query.IsHD.HasValue)
|
||||
{
|
||||
var threshold = 1200;
|
||||
const int Threshold = 1200;
|
||||
if (query.IsHD.Value)
|
||||
{
|
||||
minWidth = threshold;
|
||||
minWidth = Threshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxWidth = threshold - 1;
|
||||
maxWidth = Threshold - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (query.Is4K.HasValue)
|
||||
{
|
||||
var threshold = 3800;
|
||||
const int Threshold = 3800;
|
||||
if (query.Is4K.Value)
|
||||
{
|
||||
minWidth = threshold;
|
||||
minWidth = Threshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxWidth = threshold - 1;
|
||||
maxWidth = Threshold - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3380,93 +3380,61 @@ namespace Emby.Server.Implementations.Data
|
||||
if (minWidth.HasValue)
|
||||
{
|
||||
whereClauses.Add("Width>=@MinWidth");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinWidth", minWidth);
|
||||
}
|
||||
statement?.TryBind("@MinWidth", minWidth);
|
||||
}
|
||||
|
||||
if (query.MinHeight.HasValue)
|
||||
{
|
||||
whereClauses.Add("Height>=@MinHeight");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinHeight", query.MinHeight);
|
||||
}
|
||||
statement?.TryBind("@MinHeight", query.MinHeight);
|
||||
}
|
||||
|
||||
if (maxWidth.HasValue)
|
||||
{
|
||||
whereClauses.Add("Width<=@MaxWidth");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxWidth", maxWidth);
|
||||
}
|
||||
statement?.TryBind("@MaxWidth", maxWidth);
|
||||
}
|
||||
|
||||
if (query.MaxHeight.HasValue)
|
||||
{
|
||||
whereClauses.Add("Height<=@MaxHeight");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxHeight", query.MaxHeight);
|
||||
}
|
||||
statement?.TryBind("@MaxHeight", query.MaxHeight);
|
||||
}
|
||||
|
||||
if (query.IsLocked.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsLocked=@IsLocked");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsLocked", query.IsLocked);
|
||||
}
|
||||
statement?.TryBind("@IsLocked", query.IsLocked);
|
||||
}
|
||||
|
||||
var tags = query.Tags.ToList();
|
||||
var excludeTags = query.ExcludeTags.ToList();
|
||||
|
||||
if (query.IsMovie ?? false)
|
||||
if (query.IsMovie == true)
|
||||
{
|
||||
var alternateTypes = new List<string>();
|
||||
if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
|
||||
if (query.IncludeItemTypes.Length == 0
|
||||
|| query.IncludeItemTypes.Contains(nameof(Movie))
|
||||
|| query.IncludeItemTypes.Contains(nameof(Trailer)))
|
||||
{
|
||||
alternateTypes.Add(typeof(Movie).FullName);
|
||||
}
|
||||
if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
|
||||
{
|
||||
alternateTypes.Add(typeof(Trailer).FullName);
|
||||
}
|
||||
|
||||
var programAttribtues = new List<string>();
|
||||
if (alternateTypes.Count == 0)
|
||||
{
|
||||
programAttribtues.Add("IsMovie=@IsMovie");
|
||||
whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
||||
}
|
||||
else
|
||||
{
|
||||
programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
||||
whereClauses.Add("IsMovie=@IsMovie");
|
||||
}
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsMovie", true);
|
||||
}
|
||||
|
||||
whereClauses.Add("(" + string.Join(" OR ", programAttribtues) + ")");
|
||||
statement?.TryBind("@IsMovie", true);
|
||||
}
|
||||
else if (query.IsMovie.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsMovie=@IsMovie");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsMovie", query.IsMovie);
|
||||
}
|
||||
statement?.TryBind("@IsMovie", query.IsMovie);
|
||||
}
|
||||
|
||||
if (query.IsSeries.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsSeries=@IsSeries");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsSeries", query.IsSeries);
|
||||
}
|
||||
statement?.TryBind("@IsSeries", query.IsSeries);
|
||||
}
|
||||
|
||||
if (query.IsSports.HasValue)
|
||||
@@ -3518,10 +3486,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.IsFolder.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsFolder=@IsFolder");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFolder", query.IsFolder);
|
||||
}
|
||||
statement?.TryBind("@IsFolder", query.IsFolder);
|
||||
}
|
||||
|
||||
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
|
||||
@@ -3532,10 +3497,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (excludeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type<>@type");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@type", excludeTypes[0]);
|
||||
}
|
||||
statement?.TryBind("@type", excludeTypes[0]);
|
||||
}
|
||||
else if (excludeTypes.Length > 1)
|
||||
{
|
||||
@@ -3546,10 +3508,7 @@ namespace Emby.Server.Implementations.Data
|
||||
else if (includeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type=@type");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@type", includeTypes[0]);
|
||||
}
|
||||
statement?.TryBind("@type", includeTypes[0]);
|
||||
}
|
||||
else if (includeTypes.Length > 1)
|
||||
{
|
||||
@@ -3560,10 +3519,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.ChannelIds.Length == 1)
|
||||
{
|
||||
whereClauses.Add("ChannelId=@ChannelId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else if (query.ChannelIds.Length > 1)
|
||||
{
|
||||
@@ -3574,98 +3530,65 @@ namespace Emby.Server.Implementations.Data
|
||||
if (!query.ParentId.Equals(Guid.Empty))
|
||||
{
|
||||
whereClauses.Add("ParentId=@ParentId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentId", query.ParentId);
|
||||
}
|
||||
statement?.TryBind("@ParentId", query.ParentId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Path))
|
||||
{
|
||||
whereClauses.Add("Path=@Path");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@Path", GetPathToSave(query.Path));
|
||||
}
|
||||
statement?.TryBind("@Path", GetPathToSave(query.Path));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
|
||||
{
|
||||
whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
|
||||
}
|
||||
statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
|
||||
}
|
||||
|
||||
if (query.MinCommunityRating.HasValue)
|
||||
{
|
||||
whereClauses.Add("CommunityRating>=@MinCommunityRating");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
|
||||
}
|
||||
statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
|
||||
}
|
||||
|
||||
if (query.MinIndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("IndexNumber>=@MinIndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateCreated.HasValue)
|
||||
{
|
||||
whereClauses.Add("DateCreated>=@MinDateCreated");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateCreated", query.MinDateCreated.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateLastSaved.HasValue)
|
||||
{
|
||||
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateLastSavedForUser.HasValue)
|
||||
{
|
||||
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
|
||||
}
|
||||
|
||||
if (query.IndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("IndexNumber=@IndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IndexNumber", query.IndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
|
||||
}
|
||||
if (query.ParentIndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
|
||||
}
|
||||
if (query.ParentIndexNumberNotEquals.HasValue)
|
||||
{
|
||||
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
|
||||
}
|
||||
statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
|
||||
}
|
||||
|
||||
var minEndDate = query.MinEndDate;
|
||||
@@ -3686,73 +3609,59 @@ namespace Emby.Server.Implementations.Data
|
||||
if (minEndDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("EndDate>=@MinEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinEndDate", minEndDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinEndDate", minEndDate.Value);
|
||||
}
|
||||
|
||||
if (maxEndDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("EndDate<=@MaxEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxEndDate", maxEndDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxEndDate", maxEndDate.Value);
|
||||
}
|
||||
|
||||
if (query.MinStartDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("StartDate>=@MinStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinStartDate", query.MinStartDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinStartDate", query.MinStartDate.Value);
|
||||
}
|
||||
|
||||
if (query.MaxStartDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("StartDate<=@MaxStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxStartDate", query.MaxStartDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value);
|
||||
}
|
||||
|
||||
if (query.MinPremiereDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("PremiereDate>=@MinPremiereDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
|
||||
}
|
||||
|
||||
if (query.MaxPremiereDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("PremiereDate<=@MaxPremiereDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
|
||||
}
|
||||
|
||||
if (query.TrailerTypes.Length > 0)
|
||||
var trailerTypes = query.TrailerTypes;
|
||||
int trailerTypesLen = trailerTypes.Length;
|
||||
if (trailerTypesLen > 0)
|
||||
{
|
||||
var clauses = new List<string>();
|
||||
var index = 0;
|
||||
foreach (var type in query.TrailerTypes)
|
||||
const string Or = " OR ";
|
||||
StringBuilder clause = new StringBuilder("(", trailerTypesLen * 32);
|
||||
for (int i = 0; i < trailerTypesLen; i++)
|
||||
{
|
||||
var paramName = "@TrailerTypes" + index;
|
||||
|
||||
clauses.Add("TrailerTypes like " + paramName);
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, "%" + type + "%");
|
||||
}
|
||||
index++;
|
||||
var paramName = "@TrailerTypes" + i;
|
||||
clause.Append("TrailerTypes like ")
|
||||
.Append(paramName)
|
||||
.Append(Or);
|
||||
statement?.TryBind(paramName, "%" + trailerTypes[i] + "%");
|
||||
}
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
|
||||
// Remove last " OR "
|
||||
clause.Length -= Or.Length;
|
||||
clause.Append(')');
|
||||
|
||||
whereClauses.Add(clause.ToString());
|
||||
}
|
||||
|
||||
if (query.IsAiring.HasValue)
|
||||
@@ -3760,24 +3669,15 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.IsAiring.Value)
|
||||
{
|
||||
whereClauses.Add("StartDate<=@MaxStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxStartDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@MaxStartDate", DateTime.UtcNow);
|
||||
|
||||
whereClauses.Add("EndDate>=@MinEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinEndDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@MinEndDate", DateTime.UtcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsAiringDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@IsAiringDate", DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3792,13 +3692,10 @@ namespace Emby.Server.Implementations.Data
|
||||
var paramName = "@PersonId" + index;
|
||||
|
||||
clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))");
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, personId.ToByteArray());
|
||||
}
|
||||
statement?.TryBind(paramName, personId.ToByteArray());
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
@@ -3806,47 +3703,31 @@ namespace Emby.Server.Implementations.Data
|
||||
if (!string.IsNullOrWhiteSpace(query.Person))
|
||||
{
|
||||
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@PersonName", query.Person);
|
||||
}
|
||||
statement?.TryBind("@PersonName", query.Person);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.MinSortName))
|
||||
{
|
||||
whereClauses.Add("SortName>=@MinSortName");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinSortName", query.MinSortName);
|
||||
}
|
||||
statement?.TryBind("@MinSortName", query.MinSortName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
|
||||
{
|
||||
whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
|
||||
}
|
||||
statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.ExternalId))
|
||||
{
|
||||
whereClauses.Add("ExternalId=@ExternalId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ExternalId", query.ExternalId);
|
||||
}
|
||||
statement?.TryBind("@ExternalId", query.ExternalId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Name))
|
||||
{
|
||||
whereClauses.Add("CleanName=@Name");
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@Name", GetCleanValue(query.Name));
|
||||
}
|
||||
statement?.TryBind("@Name", GetCleanValue(query.Name));
|
||||
}
|
||||
|
||||
// These are the same, for now
|
||||
@@ -3865,28 +3746,21 @@ namespace Emby.Server.Implementations.Data
|
||||
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
|
||||
{
|
||||
whereClauses.Add("SortName like @NameStartsWith");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameStartsWith", query.NameStartsWith + "%");
|
||||
}
|
||||
statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
|
||||
{
|
||||
whereClauses.Add("SortName >= @NameStartsWithOrGreater");
|
||||
// lowercase this because SortName is stored as lowercase
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
|
||||
}
|
||||
statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.NameLessThan))
|
||||
{
|
||||
whereClauses.Add("SortName < @NameLessThan");
|
||||
// lowercase this because SortName is stored as lowercase
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
|
||||
}
|
||||
statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (query.ImageTypes.Length > 0)
|
||||
@@ -3902,18 +3776,12 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.IsLiked.Value)
|
||||
{
|
||||
whereClauses.Add("rating>=@UserRating");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("(rating is null or rating<@UserRating)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3927,10 +3795,8 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
|
||||
}
|
||||
|
||||
if (query.IsFavorite.HasValue)
|
||||
@@ -3943,10 +3809,8 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||
}
|
||||
|
||||
if (EnableJoinUserData(query))
|
||||
@@ -3975,10 +3839,8 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
whereClauses.Add("(played is null or played=@IsPlayed)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsPlayed", query.IsPlayed.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsPlayed", query.IsPlayed.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4010,6 +3872,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
@@ -4029,6 +3892,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
@@ -4762,18 +4626,22 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
list.Add(typeof(Person).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(Genre).Name, query))
|
||||
{
|
||||
list.Add(typeof(Genre).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(MusicGenre).Name, query))
|
||||
{
|
||||
list.Add(typeof(MusicGenre).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(MusicArtist).Name, query))
|
||||
{
|
||||
list.Add(typeof(MusicArtist).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(Studio).Name, query))
|
||||
{
|
||||
list.Add(typeof(Studio).Name);
|
||||
@@ -4847,7 +4715,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly Type[] KnownTypes =
|
||||
private static readonly Type[] _knownTypes =
|
||||
{
|
||||
typeof(LiveTvProgram),
|
||||
typeof(LiveTvChannel),
|
||||
@@ -4916,7 +4784,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var t in KnownTypes)
|
||||
foreach (var t in _knownTypes)
|
||||
{
|
||||
dict[t.Name] = new[] { t.FullName };
|
||||
}
|
||||
@@ -4928,7 +4796,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
|
||||
// Not crazy about having this all the way down here, but at least it's in one place
|
||||
readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
||||
private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
||||
|
||||
private string[] MapIncludeItemTypes(string value)
|
||||
{
|
||||
@@ -4945,7 +4813,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void DeleteItem(Guid id, CancellationToken cancellationToken)
|
||||
public void DeleteItem(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
@@ -4981,7 +4849,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteWithSingleParam(IDatabaseConnection db, string query, byte[] value)
|
||||
private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan<byte> value)
|
||||
{
|
||||
using (var statement = PrepareStatement(db, query))
|
||||
{
|
||||
@@ -5541,6 +5409,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
GetWhereClauses(typeSubQuery, null);
|
||||
}
|
||||
|
||||
BindSimilarParams(query, statement);
|
||||
BindSearchParams(query, statement);
|
||||
GetWhereClauses(innerQuery, statement);
|
||||
@@ -5582,7 +5451,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
|
||||
var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.ToLookup(i => i);
|
||||
.ToLookup(x => x);
|
||||
|
||||
foreach (var type in allTypes)
|
||||
{
|
||||
@@ -5673,30 +5542,26 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 100;
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
|
||||
while (startIndex < values.Count)
|
||||
{
|
||||
var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
|
||||
|
||||
var endIndex = Math.Min(values.Count, startIndex + limit);
|
||||
var isSubsequentRow = false;
|
||||
var endIndex = Math.Min(values.Count, startIndex + Limit);
|
||||
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (isSubsequentRow)
|
||||
{
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
insertText.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
|
||||
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),",
|
||||
i);
|
||||
isSubsequentRow = true;
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
{
|
||||
statement.TryBind("@ItemId", idBlob);
|
||||
@@ -5724,7 +5589,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5759,28 +5624,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
private void InsertPeople(byte[] idBlob, List<PersonInfo> people, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 100;
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
var listIndex = 0;
|
||||
|
||||
while (startIndex < people.Count)
|
||||
{
|
||||
var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
|
||||
|
||||
var endIndex = Math.Min(people.Count, startIndex + limit);
|
||||
var isSubsequentRow = false;
|
||||
|
||||
var endIndex = Math.Min(people.Count, startIndex + Limit);
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (isSubsequentRow)
|
||||
{
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture));
|
||||
isSubsequentRow = true;
|
||||
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
{
|
||||
statement.TryBind("@ItemId", idBlob);
|
||||
@@ -5804,16 +5664,17 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader)
|
||||
{
|
||||
var item = new PersonInfo();
|
||||
|
||||
item.ItemId = reader.GetGuid(0);
|
||||
item.Name = reader.GetString(1);
|
||||
var item = new PersonInfo
|
||||
{
|
||||
ItemId = reader.GetGuid(0),
|
||||
Name = reader.GetString(1)
|
||||
};
|
||||
|
||||
if (!reader.IsDBNull(2))
|
||||
{
|
||||
@@ -5920,20 +5781,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 10;
|
||||
var startIndex = 0;
|
||||
var limit = 10;
|
||||
|
||||
while (startIndex < streams.Count)
|
||||
{
|
||||
var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns)));
|
||||
var insertText = new StringBuilder("insert into mediastreams (");
|
||||
foreach (var column in _mediaStreamSaveColumns)
|
||||
{
|
||||
insertText.Append(column).Append(',');
|
||||
}
|
||||
|
||||
var endIndex = Math.Min(streams.Count, startIndex + limit);
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
insertText.Append(") values ");
|
||||
|
||||
var endIndex = Math.Min(streams.Count, startIndex + Limit);
|
||||
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (i != startIndex)
|
||||
{
|
||||
insertText.Append(",");
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
var index = i.ToString(CultureInfo.InvariantCulture);
|
||||
@@ -5941,11 +5810,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
foreach (var column in _mediaStreamSaveColumns.Skip(1))
|
||||
{
|
||||
insertText.Append("@" + column + index + ",");
|
||||
insertText.Append('@').Append(column).Append(index).Append(',');
|
||||
}
|
||||
|
||||
insertText.Length -= 1; // Remove the last comma
|
||||
|
||||
insertText.Append(")");
|
||||
insertText.Append(')');
|
||||
}
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
@@ -6007,7 +5877,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6024,7 +5894,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
Index = reader[1].ToInt()
|
||||
};
|
||||
|
||||
item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader[2].ToString(), true);
|
||||
item.Type = Enum.Parse<MediaStreamType>(reader[2].ToString(), true);
|
||||
|
||||
if (reader[3].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
|
||||
namespace Emby.Server.Implementations.Diagnostics
|
||||
{
|
||||
public class CommonProcess : IProcess
|
||||
{
|
||||
private readonly Process _process;
|
||||
|
||||
private bool _disposed = false;
|
||||
private bool _hasExited;
|
||||
|
||||
public CommonProcess(ProcessOptions options)
|
||||
{
|
||||
StartInfo = options;
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = options.Arguments,
|
||||
FileName = options.FileName,
|
||||
WorkingDirectory = options.WorkingDirectory,
|
||||
UseShellExecute = options.UseShellExecute,
|
||||
CreateNoWindow = options.CreateNoWindow,
|
||||
RedirectStandardError = options.RedirectStandardError,
|
||||
RedirectStandardInput = options.RedirectStandardInput,
|
||||
RedirectStandardOutput = options.RedirectStandardOutput,
|
||||
ErrorDialog = options.ErrorDialog
|
||||
};
|
||||
|
||||
|
||||
if (options.IsHidden)
|
||||
{
|
||||
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
}
|
||||
|
||||
_process = new Process
|
||||
{
|
||||
StartInfo = startInfo
|
||||
};
|
||||
|
||||
if (options.EnableRaisingEvents)
|
||||
{
|
||||
_process.EnableRaisingEvents = true;
|
||||
_process.Exited += OnProcessExited;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler Exited;
|
||||
|
||||
public ProcessOptions StartInfo { get; }
|
||||
|
||||
public StreamWriter StandardInput => _process.StandardInput;
|
||||
|
||||
public StreamReader StandardError => _process.StandardError;
|
||||
|
||||
public StreamReader StandardOutput => _process.StandardOutput;
|
||||
|
||||
public int ExitCode => _process.ExitCode;
|
||||
|
||||
private bool HasExited
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasExited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_hasExited = _process.HasExited;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
_hasExited = true;
|
||||
}
|
||||
|
||||
return _hasExited;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_process.Start();
|
||||
}
|
||||
|
||||
public void Kill()
|
||||
{
|
||||
_process.Kill();
|
||||
}
|
||||
|
||||
public bool WaitForExit(int timeMs)
|
||||
{
|
||||
return _process.WaitForExit(timeMs);
|
||||
}
|
||||
|
||||
public Task<bool> WaitForExitAsync(int timeMs)
|
||||
{
|
||||
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
||||
|
||||
if (HasExited)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
timeMs = Math.Max(0, timeMs);
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
var cancellationToken = new CancellationTokenSource(timeMs).Token;
|
||||
|
||||
_process.Exited += (sender, args) => tcs.TrySetResult(true);
|
||||
|
||||
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_process?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void OnProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
_hasExited = true;
|
||||
Exited?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
|
||||
namespace Emby.Server.Implementations.Diagnostics
|
||||
{
|
||||
public class ProcessFactory : IProcessFactory
|
||||
{
|
||||
public IProcess Create(ProcessOptions options)
|
||||
{
|
||||
return new CommonProcess(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1056,30 +1056,19 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (options.ContainsField(ItemFields.SpecialFeatureCount))
|
||||
{
|
||||
if (allExtras == null)
|
||||
{
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
}
|
||||
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value));
|
||||
}
|
||||
|
||||
if (options.ContainsField(ItemFields.LocalTrailerCount))
|
||||
{
|
||||
int trailerCount = 0;
|
||||
if (allExtras == null)
|
||||
{
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
}
|
||||
|
||||
trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer);
|
||||
allExtras ??= item.GetExtras().ToArray();
|
||||
dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
|
||||
|
||||
if (item is IHasTrailers hasTrailers)
|
||||
{
|
||||
trailerCount += hasTrailers.GetTrailerCount();
|
||||
dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
|
||||
}
|
||||
|
||||
dto.LocalTrailerCount = trailerCount;
|
||||
}
|
||||
|
||||
// Add EpisodeInfo
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.25.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -23,6 +23,7 @@ using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ServiceStack.Text.Jsv;
|
||||
|
||||
@@ -48,6 +49,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private readonly string _baseUrlPrefix;
|
||||
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
|
||||
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
|
||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||
private bool _disposed = false;
|
||||
|
||||
@@ -61,7 +64,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
IXmlSerializer xmlSerializer,
|
||||
IHttpListener socketListener,
|
||||
ILocalizationManager localizationManager,
|
||||
ServiceController serviceController)
|
||||
ServiceController serviceController,
|
||||
IHostEnvironment hostEnvironment)
|
||||
{
|
||||
_appHost = applicationHost;
|
||||
_logger = logger;
|
||||
@@ -75,6 +79,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
ServiceController = serviceController;
|
||||
|
||||
_socketListener.WebSocketConnected = OnWebSocketConnected;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
|
||||
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
||||
|
||||
@@ -234,7 +239,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace)
|
||||
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, string urlToLog)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -242,11 +247,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (logExceptionStackTrace)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing request");
|
||||
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Error processing request: {Message}", ex.Message);
|
||||
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
|
||||
}
|
||||
|
||||
var httpRes = httpReq.Response;
|
||||
@@ -266,7 +271,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
catch (Exception errorEx)
|
||||
{
|
||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
|
||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response). URL: {Url}", urlToLog);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,7 +456,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
var stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
var httpRes = httpReq.Response;
|
||||
string urlToLog = null;
|
||||
string urlToLog = GetUrlToLog(urlString);
|
||||
string remoteIp = httpReq.RemoteIp;
|
||||
|
||||
try
|
||||
@@ -497,8 +502,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return;
|
||||
}
|
||||
|
||||
urlToLog = GetUrlToLog(urlString);
|
||||
|
||||
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|
||||
@@ -530,22 +533,25 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
else
|
||||
{
|
||||
await ErrorHandler(new FileNotFoundException(), httpReq, false).ConfigureAwait(false);
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
|
||||
{
|
||||
await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
|
||||
}
|
||||
catch (SecurityException ex)
|
||||
{
|
||||
await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
|
||||
// Do not handle exceptions manually when in development mode
|
||||
// The framework-defined development exception page will be returned instead
|
||||
if (_hostEnvironment.IsDevelopment())
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
await ErrorHandler(ex, httpReq, logException).ConfigureAwait(false);
|
||||
bool ignoreStackTrace =
|
||||
ex is SocketException
|
||||
|| ex is IOException
|
||||
|| ex is OperationCanceledException
|
||||
|| ex is SecurityException
|
||||
|| ex is FileNotFoundException;
|
||||
await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -3,33 +3,38 @@ namespace Emby.Server.Implementations
|
||||
public interface IStartupOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// --ffmpeg
|
||||
/// Gets the value of the --ffmpeg command line option.
|
||||
/// </summary>
|
||||
string FFmpegPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --service
|
||||
/// Gets the value of the --service command line option.
|
||||
/// </summary>
|
||||
bool IsService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --noautorunwebapp
|
||||
/// Gets the value of the --noautorunwebapp command line option.
|
||||
/// </summary>
|
||||
bool NoAutoRunWebApp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --package-name
|
||||
/// Gets the value of the --package-name command line option.
|
||||
/// </summary>
|
||||
string PackageName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --restartpath
|
||||
/// Gets the value of the --restartpath command line option.
|
||||
/// </summary>
|
||||
string RestartPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --restartargs
|
||||
/// Gets the value of the --restartargs command line option.
|
||||
/// </summary>
|
||||
string RestartArgs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the --plugin-manifest-url command line option.
|
||||
/// </summary>
|
||||
string PluginManifestUrl { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,10 +437,10 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
item.SetParent(null);
|
||||
|
||||
ItemRepository.DeleteItem(item.Id, CancellationToken.None);
|
||||
ItemRepository.DeleteItem(item.Id);
|
||||
foreach (var child in children)
|
||||
{
|
||||
ItemRepository.DeleteItem(child.Id, CancellationToken.None);
|
||||
ItemRepository.DeleteItem(child.Id);
|
||||
}
|
||||
|
||||
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
|
||||
@@ -2609,14 +2609,12 @@ namespace Emby.Server.Implementations.Library
|
||||
}).OrderBy(i => i.Path);
|
||||
}
|
||||
|
||||
private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" };
|
||||
|
||||
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
var namingOptions = GetNamingOptions();
|
||||
|
||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||
.Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
.Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
|
||||
.ToList();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
@@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
|
||||
@@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
ILibraryManager libraryManager,
|
||||
ILibraryMonitor libraryMonitor,
|
||||
IProviderManager providerManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IProcessFactory processFactory)
|
||||
IMediaEncoder mediaEncoder)
|
||||
{
|
||||
Current = this;
|
||||
|
||||
@@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_libraryMonitor = libraryMonitor;
|
||||
_providerManager = providerManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_processFactory = processFactory;
|
||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
@@ -1662,7 +1659,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||
{
|
||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
|
||||
}
|
||||
|
||||
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
||||
@@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
try
|
||||
{
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
var process = new Process
|
||||
{
|
||||
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
||||
CreateNoWindow = true,
|
||||
EnableRaisingEvents = true,
|
||||
ErrorDialog = false,
|
||||
FileName = options.RecordingPostProcessor,
|
||||
IsHidden = true,
|
||||
UseShellExecute = false
|
||||
});
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
||||
CreateNoWindow = true,
|
||||
ErrorDialog = false,
|
||||
FileName = options.RecordingPostProcessor,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
UseShellExecute = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
@@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
private void Process_Exited(object sender, EventArgs e)
|
||||
{
|
||||
using (var process = (IProcess)sender)
|
||||
using (var process = (Process)sender)
|
||||
{
|
||||
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
|
||||
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
@@ -13,7 +14,6 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
@@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
private bool _hasExited;
|
||||
private Stream _logFileStream;
|
||||
private string _targetPath;
|
||||
private IProcess _process;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
private Process _process;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly IServerConfigurationManager _config;
|
||||
@@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
IMediaEncoder mediaEncoder,
|
||||
IServerApplicationPaths appPaths,
|
||||
IJsonSerializer json,
|
||||
IProcessFactory processFactory,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_appPaths = appPaths;
|
||||
_json = json;
|
||||
_processFactory = processFactory;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
@@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_targetPath = targetFile;
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
@@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
||||
|
||||
IsHidden = true,
|
||||
ErrorDialog = false,
|
||||
EnableRaisingEvents = true
|
||||
});
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
};
|
||||
|
||||
_process = process;
|
||||
|
||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
||||
var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
|
||||
_logger.LogInformation(commandLineLogMessage);
|
||||
|
||||
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
||||
@@ -109,16 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
||||
|
||||
process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
|
||||
_process = new Process
|
||||
{
|
||||
StartInfo = processStartInfo,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
|
||||
|
||||
process.Start();
|
||||
_process.Start();
|
||||
|
||||
cancellationToken.Register(Stop);
|
||||
|
||||
onStarted();
|
||||
|
||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
|
||||
StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||
|
||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
||||
|
||||
@@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
/// <summary>
|
||||
/// Processes the exited.
|
||||
/// </summary>
|
||||
private void OnFfMpegProcessExited(IProcess process, string inputFile)
|
||||
private void OnFfMpegProcessExited(Process process, string inputFile)
|
||||
{
|
||||
_hasExited = true;
|
||||
|
||||
_logFileStream?.Dispose();
|
||||
_logFileStream = null;
|
||||
|
||||
var exitCode = process.ExitCode;
|
||||
|
||||
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
||||
|
||||
if (exitCode == 0)
|
||||
using (process)
|
||||
{
|
||||
_taskCompletionSource.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_taskCompletionSource.TrySetException(
|
||||
new Exception(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Recording for {0} failed. Exit code {1}",
|
||||
_targetPath,
|
||||
exitCode)));
|
||||
_hasExited = true;
|
||||
|
||||
_logFileStream?.Dispose();
|
||||
_logFileStream = null;
|
||||
|
||||
var exitCode = process.ExitCode;
|
||||
|
||||
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
_taskCompletionSource.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_taskCompletionSource.TrySetException(
|
||||
new Exception(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Recording for {0} failed. Exit code {1}",
|
||||
_targetPath,
|
||||
exitCode)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,5 +102,17 @@
|
||||
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
|
||||
"TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.",
|
||||
"TaskRefreshChapterImages": "استخراج صور الفصل",
|
||||
"TasksApplicationCategory": "تطبيق"
|
||||
"TasksApplicationCategory": "تطبيق",
|
||||
"TaskDownloadMissingSubtitlesDescription": "ابحث في الإنترنت على الترجمات المفقودة إستنادا على الميتاداتا.",
|
||||
"TaskDownloadMissingSubtitles": "تحميل الترجمات المفقودة",
|
||||
"TaskRefreshChannelsDescription": "تحديث معلومات قنوات الإنترنت.",
|
||||
"TaskRefreshChannels": "إعادة تحديث القنوات",
|
||||
"TaskCleanTranscodeDescription": "حذف ملفات الترميز الأقدم من يوم واحد.",
|
||||
"TaskCleanTranscode": "حذف سجلات الترميز",
|
||||
"TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
|
||||
"TaskUpdatePlugins": "تحديث الإضافات",
|
||||
"TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
|
||||
"TaskRefreshPeople": "إعادة تحميل الأشخاص",
|
||||
"TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.",
|
||||
"TaskCleanLogs": "حذف دليل السجل"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Albums": "Album",
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
||||
"Application": "Applikation",
|
||||
"Artists": "Kunstnere",
|
||||
@@ -92,5 +92,21 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiration.",
|
||||
"TaskDownloadMissingSubtitles": "Download manglende undertekster",
|
||||
"TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
|
||||
"TaskUpdatePlugins": "Opdater Plugins",
|
||||
"TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
|
||||
"TaskCleanLogs": "Ryd Log Mappe",
|
||||
"TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdatere metadata.",
|
||||
"TaskRefreshLibrary": "Scan Medie Bibliotek",
|
||||
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
|
||||
"TaskCleanCache": "Ryd Cache Mappe",
|
||||
"TasksChannelsCategory": "Internet Kanaler",
|
||||
"TasksApplicationCategory": "Applikation",
|
||||
"TasksLibraryCategory": "Bibliotek",
|
||||
"TasksMaintenanceCategory": "Vedligeholdelse",
|
||||
"TaskRefreshChapterImages": "Udtræk Kapitel billeder",
|
||||
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler."
|
||||
}
|
||||
|
||||
@@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
|
||||
"TaskDownloadMissingSubtitles": "Download missing subtitles",
|
||||
"TaskRefreshChannelsDescription": "Refreshes internet channel information.",
|
||||
"TaskRefreshChannels": "Refresh Channels",
|
||||
"TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
|
||||
"TaskCleanTranscode": "Clean Transcode Directory",
|
||||
"TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
|
||||
"TaskUpdatePlugins": "Update Plugins",
|
||||
"TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
|
||||
"TaskRefreshPeople": "Refresh People",
|
||||
"TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
|
||||
"TaskCleanLogs": "Clean Log Directory",
|
||||
"TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
|
||||
"TaskRefreshLibrary": "Scan Media Library",
|
||||
"TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
|
||||
"TaskRefreshChapterImages": "Extract Chapter Images",
|
||||
"TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
|
||||
"TaskCleanCache": "Clean Cache Directory",
|
||||
"TasksChannelsCategory": "Internet Channels",
|
||||
"TasksApplicationCategory": "Application",
|
||||
"TasksLibraryCategory": "Library",
|
||||
"TasksMaintenanceCategory": "Maintenance"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artistas de álbum",
|
||||
"HeaderCameraUploads": "Subidas de cámara",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderContinueWatching": "Seguir viendo",
|
||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Collections": "Colecciones",
|
||||
"Artists": "Artistas",
|
||||
"DeviceOnlineWithName": "{0} está conectado",
|
||||
"DeviceOfflineWithName": "{0} ha desconectado",
|
||||
"DeviceOfflineWithName": "{0} se ha desconectado",
|
||||
"ChapterNameValue": "Capítulo {0}",
|
||||
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"HeaderFavoriteEpisodes": "قسمتهای مورد علاقه",
|
||||
"HeaderFavoriteShows": "سریالهای مورد علاقه",
|
||||
"HeaderFavoriteSongs": "آهنگهای مورد علاقه",
|
||||
"HeaderLiveTV": "پخش زنده تلویزیون",
|
||||
"HeaderLiveTV": "تلویزیون زنده",
|
||||
"HeaderNextUp": "قسمت بعدی",
|
||||
"HeaderRecordingGroups": "گروههای ضبط",
|
||||
"HomeVideos": "ویدیوهای خانگی",
|
||||
@@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
|
||||
"ValueHasBeenAddedToLibrary": "{0} به کتابخانهی رسانهی شما افزوده شد",
|
||||
"ValueSpecialEpisodeName": "ویژه - {0}",
|
||||
"VersionNumber": "نسخه {0}"
|
||||
"VersionNumber": "نسخه {0}",
|
||||
"TaskCleanTranscodeDescription": "فایلهای کدگذاری که قدیمیتر از یک روز هستند را حذف میکند.",
|
||||
"TaskCleanTranscode": "پاکسازی مسیر کد گذاری",
|
||||
"TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونههایی که برای به روز رسانی خودکار پیکربندی شدهاند.",
|
||||
"TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویسهای ناموجود در اینترنت بر اساس پیکربندی ابردادهها.",
|
||||
"TaskDownloadMissingSubtitles": "دانلود زیرنویسهای ناموجود",
|
||||
"TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی میکند.",
|
||||
"TaskRefreshChannels": "تازه سازی کانالها",
|
||||
"TaskUpdatePlugins": "به روز رسانی افزونهها",
|
||||
"TaskRefreshPeopleDescription": "ابردادهها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.",
|
||||
"TaskRefreshPeople": "تازه سازی افراد",
|
||||
"TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.",
|
||||
"TaskCleanLogs": "پاکسازی مسیر واقعه نگار",
|
||||
"TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن میکند و ابردادهها را تازه سازی میکند.",
|
||||
"TaskRefreshLibrary": "اسکن کتابخانه رسانه",
|
||||
"TaskRefreshChapterImagesDescription": "عکسهای کوچک برای ویدیوهایی که سکانس دارند ایجاد میکند.",
|
||||
"TaskRefreshChapterImages": "استخراج عکسهای سکانس",
|
||||
"TaskCleanCacheDescription": "فایلهای حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف میشوند.",
|
||||
"TaskCleanCache": "پاکسازی مسیر حافظه موقت",
|
||||
"TasksChannelsCategory": "کانالهای داخلی",
|
||||
"TasksApplicationCategory": "برنامه",
|
||||
"TasksLibraryCategory": "کتابخانه",
|
||||
"TasksMaintenanceCategory": "تعمیر"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"HeaderLiveTV": "TV-lähetykset",
|
||||
"HeaderLiveTV": "Suorat lähetykset",
|
||||
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
||||
"NameSeasonUnknown": "Tuntematon Kausi",
|
||||
"NameSeasonNumber": "Kausi {0}",
|
||||
@@ -19,12 +19,12 @@
|
||||
"ItemAddedWithName": "{0} lisättiin kirjastoon",
|
||||
"Inherit": "Periytyä",
|
||||
"HomeVideos": "Kotivideot",
|
||||
"HeaderRecordingGroups": "Nauhoitusryhmät",
|
||||
"HeaderRecordingGroups": "Nauhoiteryhmät",
|
||||
"HeaderNextUp": "Seuraavaksi",
|
||||
"HeaderFavoriteSongs": "Lempikappaleet",
|
||||
"HeaderFavoriteShows": "Lempisarjat",
|
||||
"HeaderFavoriteEpisodes": "Lempijaksot",
|
||||
"HeaderCameraUploads": "Kameralataukset",
|
||||
"HeaderCameraUploads": "Kamerasta Lähetetyt",
|
||||
"HeaderFavoriteArtists": "Lempiartistit",
|
||||
"HeaderFavoriteAlbums": "Lempialbumit",
|
||||
"HeaderContinueWatching": "Jatka katsomista",
|
||||
@@ -63,10 +63,10 @@
|
||||
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
|
||||
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
|
||||
"UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
|
||||
"UserLockedOutWithName": "Käyttäjä {0} kirjautui ulos",
|
||||
"UserDownloadingItemWithValues": "{0} latautumassa {1}",
|
||||
"UserDeletedWithName": "Poistettiin käyttäjä {0}",
|
||||
"UserCreatedWithName": "Luotiin käyttäjä {0}",
|
||||
"UserLockedOutWithName": "Käyttäjä {0} lukittu",
|
||||
"UserDownloadingItemWithValues": "{0} lataa {1}",
|
||||
"UserDeletedWithName": "Käyttäjä {0} poistettu",
|
||||
"UserCreatedWithName": "Käyttäjä {0} luotu",
|
||||
"TvShows": "TV-Ohjelmat",
|
||||
"Sync": "Synkronoi",
|
||||
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
|
||||
@@ -74,22 +74,44 @@
|
||||
"Songs": "Kappaleet",
|
||||
"Shows": "Ohjelmat",
|
||||
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
|
||||
"ProviderValue": "Palveluntarjoaja: {0}",
|
||||
"ProviderValue": "Tarjoaja: {0}",
|
||||
"Plugin": "Liitännäinen",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty",
|
||||
"NotificationOptionVideoPlayback": "Videon toistaminen aloitettu",
|
||||
"NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
|
||||
"NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
|
||||
"NotificationOptionVideoPlayback": "Videon toisto aloitettu",
|
||||
"NotificationOptionUserLockedOut": "Käyttäjä lukittu",
|
||||
"NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
|
||||
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
|
||||
"NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
|
||||
"NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu",
|
||||
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
||||
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
||||
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
||||
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
|
||||
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
|
||||
"NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty",
|
||||
"NotificationOptionAudioPlayback": "Audion toisto aloitettu",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla"
|
||||
"NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
|
||||
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
|
||||
"NotificationOptionAudioPlayback": "Toistetaan ääntä",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla",
|
||||
"TasksMaintenanceCategory": "Ylläpito",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
|
||||
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
|
||||
"TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
|
||||
"TaskRefreshChannels": "Päivitä kanavat",
|
||||
"TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
|
||||
"TaskCleanTranscode": "Puhdista transkoodaushakemisto",
|
||||
"TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
|
||||
"TaskUpdatePlugins": "Päivitä liitännäiset",
|
||||
"TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
|
||||
"TaskRefreshPeople": "Päivitä henkilöt",
|
||||
"TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
|
||||
"TaskCleanLogs": "Puhdista lokihakemisto",
|
||||
"TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
|
||||
"TaskRefreshLibrary": "Skannaa mediakirjasto",
|
||||
"TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
|
||||
"TaskRefreshChapterImages": "Eristä lukujen kuvat",
|
||||
"TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
|
||||
"TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
|
||||
"TasksChannelsCategory": "Internet kanavat",
|
||||
"TasksApplicationCategory": "Sovellus",
|
||||
"TasksLibraryCategory": "Kirjasto"
|
||||
}
|
||||
|
||||
@@ -90,5 +90,13 @@
|
||||
"Artists": "Artista",
|
||||
"Application": "Aplikasyon",
|
||||
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
|
||||
"Albums": "Albums"
|
||||
"Albums": "Albums",
|
||||
"TaskRefreshLibrary": "Suriin ang nasa librerya",
|
||||
"TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata",
|
||||
"TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",
|
||||
"TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.",
|
||||
"TasksChannelsCategory": "Palabas sa internet",
|
||||
"TasksLibraryCategory": "Librerya",
|
||||
"TasksMaintenanceCategory": "Pagpapanatili",
|
||||
"HomeVideos": "Sariling pelikula"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
||||
"Books": "Livres",
|
||||
"CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}",
|
||||
"CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}",
|
||||
"Channels": "Chaînes",
|
||||
"ChapterNameValue": "Chapitre {0}",
|
||||
"Collections": "Collections",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"ScheduledTaskFailedWithName": "{0} sikertelen",
|
||||
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
||||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||
"Shows": "Műsorok",
|
||||
"Shows": "Sorozatok",
|
||||
"Songs": "Dalok",
|
||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
|
||||
@@ -109,5 +109,8 @@
|
||||
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
||||
"TaskUpdatePlugins": "プラグインの更新",
|
||||
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
|
||||
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ"
|
||||
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
|
||||
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
|
||||
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
||||
"TaskRefreshChapterImages": "チャプター画像を抽出する"
|
||||
}
|
||||
|
||||
61
Emby.Server.Implementations/Localization/Core/mr.json
Normal file
61
Emby.Server.Implementations/Localization/Core/mr.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"Books": "पुस्तकं",
|
||||
"Artists": "संगीतकार",
|
||||
"Albums": "अल्बम",
|
||||
"Playlists": "प्लेलिस्ट",
|
||||
"HeaderAlbumArtists": "अल्बम संगीतकार",
|
||||
"Folders": "फोल्डर",
|
||||
"HeaderFavoriteEpisodes": "आवडते भाग",
|
||||
"HeaderFavoriteSongs": "आवडती गाणी",
|
||||
"Movies": "चित्रपट",
|
||||
"HeaderFavoriteArtists": "आवडते संगीतकार",
|
||||
"Shows": "कार्यक्रम",
|
||||
"HeaderFavoriteAlbums": "आवडते अल्बम",
|
||||
"Channels": "वाहिन्या",
|
||||
"ValueSpecialEpisodeName": "विशेष - {0}",
|
||||
"HeaderFavoriteShows": "आवडते कार्यक्रम",
|
||||
"Favorites": "आवडीचे",
|
||||
"HeaderNextUp": "यानंतर",
|
||||
"Songs": "गाणी",
|
||||
"HeaderLiveTV": "लाइव्ह टीव्ही",
|
||||
"Genres": "जाँनरे",
|
||||
"Photos": "चित्र",
|
||||
"TaskDownloadMissingSubtitles": "नसलेले सबटायटल डाउनलोड करा",
|
||||
"TaskCleanTranscodeDescription": "एक दिवसापेक्षा जुन्या ट्रान्सकोड फायली काढून टाका.",
|
||||
"TaskCleanTranscode": "ट्रान्सकोड डिरेक्टरी साफ करून टाका",
|
||||
"TaskUpdatePlugins": "प्लगइन अपडेट करा",
|
||||
"TaskCleanLogs": "लॉग डिरेक्टरी साफ करून टाका",
|
||||
"TaskCleanCache": "कॅश डिरेक्टरी साफ करून टाका",
|
||||
"TasksChannelsCategory": "इंटरनेट वाहिन्या",
|
||||
"TasksApplicationCategory": "अॅप्लिकेशन",
|
||||
"TasksLibraryCategory": "संग्रहालय",
|
||||
"VersionNumber": "आवृत्ती {0}",
|
||||
"UserPasswordChangedWithName": "{0} या प्रयोक्त्याचे पासवर्ड बदलण्यात आले आहे",
|
||||
"UserOnlineFromDevice": "{0} हे {1} येथून ऑनलाइन आहेत",
|
||||
"UserDeletedWithName": "प्रयोक्ता {0} काढून टाकण्यात आले आहे",
|
||||
"UserCreatedWithName": "प्रयोक्ता {0} बनवण्यात आले आहे",
|
||||
"User": "प्रयोक्ता",
|
||||
"TvShows": "टीव्ही कार्यक्रम",
|
||||
"StartupEmbyServerIsLoading": "जेलिफिन सर्व्हर लोड होत आहे. कृपया थोड्या वेळात पुन्हा प्रयत्न करा.",
|
||||
"Plugin": "प्लगइन",
|
||||
"NotificationOptionCameraImageUploaded": "कॅमेरा चित्र अपलोड केले आहे",
|
||||
"NotificationOptionApplicationUpdateInstalled": "अॅप्लिकेशन अपडेट इन्स्टॉल केले आहे",
|
||||
"NotificationOptionApplicationUpdateAvailable": "अॅप्लिकेशन अपडेट उपलब्ध आहे",
|
||||
"NewVersionIsAvailable": "जेलिफिन सर्व्हरची एक नवीन आवृत्ती डाउनलोड करण्यास उपलब्ध आहे.",
|
||||
"NameSeasonUnknown": "अज्ञात सीझन",
|
||||
"NameSeasonNumber": "सीझन {0}",
|
||||
"MusicVideos": "संगीत व्हिडीयो",
|
||||
"Music": "संगीत",
|
||||
"MessageApplicationUpdatedTo": "जेलिफिन सर्व्हर अपडेट होऊन {0} आवृत्तीवर पोहोचला आहे",
|
||||
"MessageApplicationUpdated": "जेलिफिन सर्व्हर अपडेट केला गेला आहे",
|
||||
"Latest": "नवीनतम",
|
||||
"LabelIpAddressValue": "आयपी पत्ता: {0}",
|
||||
"ItemRemovedWithName": "{0} हे संग्रहालयातून काढून टाकण्यात आले",
|
||||
"ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले",
|
||||
"HomeVideos": "घरचे व्हिडीयो",
|
||||
"HeaderRecordingGroups": "रेकॉर्डिंग गट",
|
||||
"HeaderCameraUploads": "कॅमेरा अपलोड",
|
||||
"CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
|
||||
"Application": "अॅप्लिकेशन",
|
||||
"AppDeviceValues": "अॅप: {0}, यंत्र: {1}"
|
||||
}
|
||||
@@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
|
||||
"ValueSpecialEpisodeName": "Speciaal - {0}",
|
||||
"VersionNumber": "Versie {0}"
|
||||
"VersionNumber": "Versie {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar missende ondertitels gebaseerd op metadata configuratie.",
|
||||
"TaskDownloadMissingSubtitles": "Download missende ondertitels",
|
||||
"TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
|
||||
"TaskRefreshChannels": "Vernieuw Kanalen",
|
||||
"TaskCleanTranscodeDescription": "Verwijder transcode bestanden ouder dan 1 dag.",
|
||||
"TaskCleanLogs": "Log Folder Opschonen",
|
||||
"TaskCleanTranscode": "Transcode Folder Opschonen",
|
||||
"TaskUpdatePluginsDescription": "Download en installeert updates voor plugins waar automatisch updaten aan staat.",
|
||||
"TaskUpdatePlugins": "Update Plugins",
|
||||
"TaskRefreshPeopleDescription": "Update metadata for acteurs en regisseurs in de media bibliotheek.",
|
||||
"TaskRefreshPeople": "Vernieuw Personen",
|
||||
"TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
|
||||
"TaskRefreshLibraryDescription": "Scant de media bibliotheek voor nieuwe bestanden en vernieuwt de metadata.",
|
||||
"TaskRefreshLibrary": "Scan Media Bibliotheek",
|
||||
"TaskRefreshChapterImagesDescription": "Maakt thumbnails aan voor videos met hoofdstukken.",
|
||||
"TaskRefreshChapterImages": "Hoofdstukafbeeldingen Uitpakken",
|
||||
"TaskCleanCacheDescription": "Verwijder gecachte bestanden die het systeem niet langer nodig heeft.",
|
||||
"TaskCleanCache": "Cache Folder Opschonen",
|
||||
"TasksChannelsCategory": "Internet Kanalen",
|
||||
"TasksApplicationCategory": "Applicatie",
|
||||
"TasksLibraryCategory": "Bibliotheek",
|
||||
"TasksMaintenanceCategory": "Onderhoud"
|
||||
}
|
||||
|
||||
@@ -91,5 +91,9 @@
|
||||
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
||||
"Application": "Aplicação",
|
||||
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}"
|
||||
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}",
|
||||
"TaskCleanCache": "Limpar Diretório de Cache",
|
||||
"TasksApplicationCategory": "Aplicação",
|
||||
"TasksLibraryCategory": "Biblioteca",
|
||||
"TasksMaintenanceCategory": "Manutenção"
|
||||
}
|
||||
|
||||
@@ -92,5 +92,26 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserad på metadatas konfiguration.",
|
||||
"TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
|
||||
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
|
||||
"TaskRefreshChannels": "Uppdatera kanaler",
|
||||
"TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
|
||||
"TaskCleanTranscode": "Töm transkodningskatalog",
|
||||
"TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
|
||||
"TaskUpdatePlugins": "Uppdatera insticksprogram",
|
||||
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
|
||||
"TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.",
|
||||
"TaskCleanLogs": "Töm loggkatalog",
|
||||
"TaskRefreshLibraryDescription": "Söker igenom ditt mediabibliotek efter nya filer och förnyar metadata.",
|
||||
"TaskRefreshLibrary": "Genomsök mediabibliotek",
|
||||
"TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.",
|
||||
"TaskRefreshChapterImages": "Extrahera kapitelbilder",
|
||||
"TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.",
|
||||
"TaskCleanCache": "Rensa cachekatalog",
|
||||
"TasksChannelsCategory": "Internetkanaler",
|
||||
"TasksApplicationCategory": "Applikation",
|
||||
"TasksLibraryCategory": "Bibliotek",
|
||||
"TasksMaintenanceCategory": "Underhåll"
|
||||
}
|
||||
|
||||
@@ -92,5 +92,10 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
|
||||
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
|
||||
"ValueSpecialEpisodeName": "Özel - {0}",
|
||||
"VersionNumber": "Versiyon {0}"
|
||||
"VersionNumber": "Versiyon {0}",
|
||||
"TaskCleanCache": "Geçici dosya klasörünü temizle",
|
||||
"TasksChannelsCategory": "İnternet kanalları",
|
||||
"TasksApplicationCategory": "Yazılım",
|
||||
"TasksLibraryCategory": "Kütüphane",
|
||||
"TasksMaintenanceCategory": "Onarım"
|
||||
}
|
||||
|
||||
117
Emby.Server.Implementations/Localization/Core/ur_PK.json
Normal file
117
Emby.Server.Implementations/Localization/Core/ur_PK.json
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"HeaderFavoriteAlbums": "پسندیدہ البمز",
|
||||
"HeaderNextUp": "اگلا",
|
||||
"HeaderFavoriteArtists": "پسندیدہ فنکار",
|
||||
"HeaderAlbumArtists": "البم کے فنکار",
|
||||
"Movies": "فلمیں",
|
||||
"HeaderFavoriteEpisodes": "پسندیدہ اقساط",
|
||||
"Collections": "مجموعہ",
|
||||
"Folders": "فولڈرز",
|
||||
"HeaderLiveTV": "براہ راست ٹی وی",
|
||||
"Channels": "چینل",
|
||||
"HeaderContinueWatching": "دیکھنا جاری رکھیں",
|
||||
"Playlists": "پلے لسٹس",
|
||||
"ValueSpecialEpisodeName": "خاص - {0}",
|
||||
"Shows": "شوز",
|
||||
"Genres": "انواع",
|
||||
"Artists": "فنکار",
|
||||
"Sync": "مطابقت",
|
||||
"Photos": "تصوریں",
|
||||
"Albums": "البم",
|
||||
"Favorites": "پسندیدہ",
|
||||
"Songs": "گانے",
|
||||
"Books": "کتابیں",
|
||||
"HeaderFavoriteSongs": "پسندیدہ گانے",
|
||||
"HeaderFavoriteShows": "پسندیدہ شوز",
|
||||
"TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔",
|
||||
"TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں",
|
||||
"TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔",
|
||||
"TaskRefreshChannels": "چینلز ریفریش کریں",
|
||||
"TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔",
|
||||
"TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں",
|
||||
"TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔",
|
||||
"TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں",
|
||||
"TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔",
|
||||
"TaskRefreshPeople": "لوگوں کو تروتازہ کریں",
|
||||
"TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔",
|
||||
"TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں",
|
||||
"TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.",
|
||||
"TaskRefreshLibrary": "اسکین میڈیا لائبریری",
|
||||
"TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔",
|
||||
"TaskRefreshChapterImages": "باب کی تصاویر نکالیں",
|
||||
"TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔",
|
||||
"TaskCleanCache": "کیش ڈائرکٹری کلیر کریں",
|
||||
"TasksChannelsCategory": "انٹرنیٹ چینلز",
|
||||
"TasksApplicationCategory": "پروگرام",
|
||||
"TasksLibraryCategory": "لآیبریری",
|
||||
"TasksMaintenanceCategory": "مرمت",
|
||||
"VersionNumber": "ورژن {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے",
|
||||
"UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے",
|
||||
"UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے",
|
||||
"UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے",
|
||||
"UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے",
|
||||
"UserOnlineFromDevice": "{0} آن لائن ہے {1} سے",
|
||||
"UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}",
|
||||
"UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے",
|
||||
"UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}",
|
||||
"UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے",
|
||||
"UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے",
|
||||
"User": "صارف",
|
||||
"TvShows": "ٹی وی کے پروگرام",
|
||||
"System": "نظام",
|
||||
"SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے",
|
||||
"StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔",
|
||||
"ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے",
|
||||
"ScheduledTaskStartedWithName": "{0} شروع",
|
||||
"ScheduledTaskFailedWithName": "{0} ناکام",
|
||||
"ProviderValue": "فراہم کرنے والا: {0}",
|
||||
"PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی",
|
||||
"PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا",
|
||||
"PluginInstalledWithName": "{0} انسٹال کیا گیا تھا",
|
||||
"Plugin": "پلگن",
|
||||
"NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا",
|
||||
"NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا",
|
||||
"NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا",
|
||||
"NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی",
|
||||
"NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے",
|
||||
"NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال",
|
||||
"NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا",
|
||||
"NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا",
|
||||
"NotificationOptionPluginError": "پلگ ان کی ناکامی",
|
||||
"NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا",
|
||||
"NotificationOptionInstallationFailed": "تنصیب کی ناکامی",
|
||||
"NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی",
|
||||
"NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا",
|
||||
"NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا",
|
||||
"NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے",
|
||||
"NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے",
|
||||
"NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔",
|
||||
"NameSeasonUnknown": "نامعلوم باب",
|
||||
"NameSeasonNumber": "باب {0}",
|
||||
"NameInstallFailed": "{0} تنصیب ناکام ہوگئی",
|
||||
"MusicVideos": "موسیقی ویڈیو",
|
||||
"Music": "موسیقی",
|
||||
"MixedContent": "مخلوط مواد",
|
||||
"MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے",
|
||||
"MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}",
|
||||
"MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے",
|
||||
"Latest": "تازہ ترین",
|
||||
"LabelRunningTimeValue": "چلانے کی مدت",
|
||||
"LabelIpAddressValue": "ای پی پتے {0}",
|
||||
"ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے",
|
||||
"ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے",
|
||||
"Inherit": "وراثت میں",
|
||||
"HomeVideos": "ہوم ویڈیو",
|
||||
"HeaderRecordingGroups": "ریکارڈنگ گروپس",
|
||||
"HeaderCameraUploads": "کیمرہ اپلوڈز",
|
||||
"FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
|
||||
"DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
|
||||
"DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",
|
||||
"ChapterNameValue": "باب",
|
||||
"AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے",
|
||||
"CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
|
||||
"Application": "پروگرام",
|
||||
"AppDeviceValues": "پروگرام:{0}, آلہ:{1}"
|
||||
}
|
||||
@@ -55,9 +55,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
progress.Report(0);
|
||||
|
||||
var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken);
|
||||
var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList();
|
||||
|
||||
progress.Report(10);
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,6 +20,7 @@ using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Updates
|
||||
@@ -27,6 +30,11 @@ namespace Emby.Server.Implementations.Updates
|
||||
/// </summary>
|
||||
public class InstallationManager : IInstallationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The key for a setting that specifies a URL for the plugin repository JSON manifest.
|
||||
/// </summary>
|
||||
public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
|
||||
|
||||
/// <summary>
|
||||
/// The _logger.
|
||||
/// </summary>
|
||||
@@ -44,6 +52,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
private readonly IApplicationHost _applicationHost;
|
||||
|
||||
private readonly IZipClient _zipClient;
|
||||
private readonly IConfiguration _appConfig;
|
||||
|
||||
private readonly object _currentInstallationsLock = new object();
|
||||
|
||||
@@ -65,7 +74,8 @@ namespace Emby.Server.Implementations.Updates
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
IZipClient zipClient)
|
||||
IZipClient zipClient,
|
||||
IConfiguration appConfig)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
@@ -83,6 +93,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_zipClient = zipClient;
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -112,19 +123,43 @@ namespace Emby.Server.Implementations.Updates
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var response = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
|
||||
CancellationToken = cancellationToken,
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
CacheLength = TimeSpan.FromMinutes(3)
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (Stream stream = response.Content)
|
||||
var manifestUrl = _appConfig.GetValue<string>(PluginManifestUrlKey);
|
||||
|
||||
try
|
||||
{
|
||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(
|
||||
stream).ConfigureAwait(false);
|
||||
using (var response = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = manifestUrl,
|
||||
CancellationToken = cancellationToken,
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
CacheLength = TimeSpan.FromMinutes(3)
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (Stream stream = response.Content)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
|
||||
}
|
||||
catch (SerializationException ex)
|
||||
{
|
||||
const string LogTemplate =
|
||||
"Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " +
|
||||
"have specified a custom plugin repository manifest URL with --plugin-manifest-url or " +
|
||||
PluginManifestUrlKey + ", please ensure that it is correct.";
|
||||
_logger.LogError(ex, LogTemplate, manifestUrl);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UriFormatException ex)
|
||||
{
|
||||
const string LogTemplate =
|
||||
"The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " +
|
||||
"Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey;
|
||||
_logger.LogError(ex, LogTemplate, manifestUrl);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,16 +224,17 @@ namespace Emby.Server.Implementations.Updates
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
|
||||
return GetAvailablePluginUpdates(catalog);
|
||||
}
|
||||
|
||||
var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
|
||||
|
||||
// Figure out what needs to be installed
|
||||
private IEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
|
||||
{
|
||||
foreach (var plugin in _applicationHost.Plugins)
|
||||
{
|
||||
var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel);
|
||||
var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version, _applicationHost.SystemUpdateLevel);
|
||||
var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
|
||||
if (version != null
|
||||
&& !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
|
||||
|
||||
Reference in New Issue
Block a user