Merge remote-tracking branch 'upstream/master' into register-services-correctly

This commit is contained in:
Mark Monteiro
2020-04-20 20:11:33 -04:00
70 changed files with 742 additions and 885 deletions

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -27,6 +25,9 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
/// <summary>
/// Entry point for the activity logger.
/// </summary>
public sealed class ActivityLogEntryPoint : IServerEntryPoint
{
private readonly ILogger _logger;
@@ -42,16 +43,15 @@ namespace Emby.Server.Implementations.Activity
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
/// </summary>
/// <param name="logger"></param>
/// <param name="sessionManager"></param>
/// <param name="deviceManager"></param>
/// <param name="taskManager"></param>
/// <param name="activityManager"></param>
/// <param name="localization"></param>
/// <param name="installationManager"></param>
/// <param name="subManager"></param>
/// <param name="userManager"></param>
/// <param name="appHost"></param>
/// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param>
/// <param name="deviceManager">The device manager.</param>
/// <param name="taskManager">The task manager.</param>
/// <param name="activityManager">The activity manager.</param>
/// <param name="localization">The localization manager.</param>
/// <param name="installationManager">The installation manager.</param>
/// <param name="subManager">The subtitle manager.</param>
/// <param name="userManager">The user manager.</param>
public ActivityLogEntryPoint(
ILogger<ActivityLogEntryPoint> logger,
ISessionManager sessionManager,
@@ -74,6 +74,7 @@ namespace Emby.Server.Implementations.Activity
_userManager = userManager;
}
/// <inheritdoc />
public Task RunAsync()
{
_taskManager.TaskCompleted += OnTaskCompleted;
@@ -168,7 +169,12 @@ namespace Emby.Server.Implementations.Activity
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
user.Name,
GetItemName(item),
e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType),
UserId = user.Id
});
@@ -416,7 +422,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -428,8 +434,8 @@ namespace Emby.Server.Implementations.Activity
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
e.Argument.Item2.versionStr),
Overview = e.Argument.Item2.description
e.Argument.Item2.version),
Overview = e.Argument.Item2.changelog
});
}
@@ -445,7 +451,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -457,7 +463,7 @@ namespace Emby.Server.Implementations.Activity
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
e.Argument.versionStr)
e.Argument.version)
});
}
@@ -485,8 +491,8 @@ namespace Emby.Server.Implementations.Activity
var result = e.Result;
var task = e.Task;
var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
if (activityTask != null && !activityTask.IsLogged)
if (task.ScheduledTask is IConfigurableScheduledTask activityTask
&& !activityTask.IsLogged)
{
return;
}
@@ -560,7 +566,7 @@ namespace Emby.Server.Implementations.Activity
/// <summary>
/// Constructs a user-friendly string for this TimeSpan instance.
/// </summary>
public static string ToUserFriendlyString(TimeSpan span)
private static string ToUserFriendlyString(TimeSpan span)
{
const int DaysInYear = 365;
const int DaysInMonth = 30;

View File

@@ -11,22 +11,17 @@ namespace Emby.Server.Implementations.Activity
{
public class ActivityManager : IActivityManager
{
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
private readonly IActivityRepository _repo;
private readonly ILogger _logger;
private readonly IUserManager _userManager;
public ActivityManager(
ILogger<ActivityManager> logger,
IActivityRepository repo,
IUserManager userManager)
public ActivityManager(IActivityRepository repo, IUserManager userManager)
{
_logger = logger;
_repo = repo;
_userManager = userManager;
}
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
public void Create(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;

View File

@@ -17,7 +17,8 @@ namespace Emby.Server.Implementations.Activity
{
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
private readonly IFileSystem _fileSystem;
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
@@ -76,8 +77,6 @@ namespace Emby.Server.Implementations.Activity
}
}
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
public void Create(ActivityLogEntry entry)
{
if (entry == null)
@@ -87,32 +86,34 @@ namespace Emby.Server.Implementations.Activity
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
connection.RunInTransaction(
db =>
{
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
if (entry.UserId.Equals(Guid.Empty))
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@Name", entry.Name);
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
statement.MoveNext();
}
}, TransactionMode);
if (entry.UserId.Equals(Guid.Empty))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}
},
TransactionMode);
}
}
@@ -125,33 +126,35 @@ namespace Emby.Server.Implementations.Activity
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
connection.RunInTransaction(
db =>
{
statement.TryBind("@Id", entry.Id);
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
if (entry.UserId.Equals(Guid.Empty))
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@Id", entry.Id);
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
statement.MoveNext();
}
}, TransactionMode);
if (entry.UserId.Equals(Guid.Empty))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}
},
TransactionMode);
}
}
@@ -164,6 +167,7 @@ namespace Emby.Server.Implementations.Activity
{
whereClauses.Add("DateCreated>=@DateCreated");
}
if (hasUserId.HasValue)
{
if (hasUserId.Value)
@@ -204,7 +208,7 @@ namespace Emby.Server.Implementations.Activity
if (limit.HasValue)
{
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
}
var statementTexts = new[]
@@ -304,7 +308,7 @@ namespace Emby.Server.Implementations.Activity
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
}
return info;

View File

@@ -213,19 +213,6 @@ namespace Emby.Server.Implementations
/// <value>The configuration manager.</value>
protected IConfigurationManager ConfigurationManager { get; set; }
/// <inheritdoc />
public PackageVersionClass SystemUpdateLevel
{
get
{
#if BETA
return PackageVersionClass.Beta;
#else
return PackageVersionClass.Release;
#endif
}
}
/// <summary>
/// Gets or sets the service provider.
/// </summary>
@@ -1170,7 +1157,6 @@ namespace Emby.Server.Implementations
SupportsLibraryMonitor = true,
EncoderLocation = _mediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture,
SystemUpdateLevel = SystemUpdateLevel,
PackageName = _startupOptions.PackageName
};
}

View File

@@ -50,6 +50,7 @@ namespace Emby.Server.Implementations.Archiving
}
}
/// <inheritdoc />
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var reader = ZipReader.Open(source))
@@ -66,6 +67,7 @@ namespace Emby.Server.Implementations.Archiving
}
}
/// <inheritdoc />
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var reader = GZipReader.Open(source))
@@ -82,6 +84,7 @@ namespace Emby.Server.Implementations.Archiving
}
}
/// <inheritdoc />
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
{
using (var reader = GZipReader.Open(source))

View File

@@ -20,6 +20,8 @@ namespace Emby.Server.Implementations.Channels
_channelManager = channelManager;
}
public string Name => "Channel Image Provider";
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return GetChannel(item).GetSupportedChannelImages();
@@ -32,8 +34,6 @@ namespace Emby.Server.Implementations.Channels
return channel.GetChannelImage(type, cancellationToken);
}
public string Name => "Channel Image Provider";
public bool Supports(BaseItem item)
{
return item is Channel;

View File

@@ -31,8 +31,6 @@ namespace Emby.Server.Implementations.Channels
{
public class ChannelManager : IChannelManager
{
internal IChannel[] Channels { get; private set; }
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly IDtoService _dtoService;
@@ -43,11 +41,16 @@ namespace Emby.Server.Implementations.Channels
private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager;
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
public ChannelManager(
IUserManager userManager,
IDtoService dtoService,
ILibraryManager libraryManager,
ILoggerFactory loggerFactory,
ILogger<ChannelManager> logger,
IServerConfigurationManager config,
IFileSystem fileSystem,
IUserDataManager userDataManager,
@@ -57,7 +60,7 @@ namespace Emby.Server.Implementations.Channels
_userManager = userManager;
_dtoService = dtoService;
_libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger(nameof(ChannelManager));
_logger = logger;
_config = config;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
@@ -65,6 +68,8 @@ namespace Emby.Server.Implementations.Channels
_providerManager = providerManager;
}
internal IChannel[] Channels { get; private set; }
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
public void AddParts(IEnumerable<IChannel> channels)
@@ -85,8 +90,7 @@ namespace Emby.Server.Implementations.Channels
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
var supportsDelete = channel as ISupportsDelete;
return supportsDelete != null && supportsDelete.CanDelete(item);
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
}
public bool EnableMediaProbe(BaseItem item)
@@ -146,15 +150,13 @@ namespace Emby.Server.Implementations.Channels
{
try
{
var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
}
catch
{
return false;
}
}).ToList();
}
@@ -171,7 +173,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
@@ -188,9 +189,9 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
if (query.IsFavorite.HasValue)
{
var val = query.IsFavorite.Value;
@@ -215,7 +216,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
@@ -226,6 +226,7 @@ namespace Emby.Server.Implementations.Channels
{
all = all.Skip(query.StartIndex.Value).ToList();
}
if (query.Limit.HasValue)
{
all = all.Take(query.Limit.Value).ToList();
@@ -256,11 +257,9 @@ namespace Emby.Server.Implementations.Channels
var internalResult = GetChannelsInternal(query);
var dtoOptions = new DtoOptions()
{
};
var dtoOptions = new DtoOptions();
//TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
// TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
var result = new QueryResult<BaseItemDto>
@@ -341,8 +340,8 @@ namespace Emby.Server.Implementations.Channels
}
catch
{
}
return;
}
@@ -365,11 +364,9 @@ namespace Emby.Server.Implementations.Channels
var channel = GetChannel(item.ChannelId);
var channelPlugin = GetChannelProvider(channel);
var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
IEnumerable<MediaSourceInfo> results;
if (requiresCallback != null)
if (channelPlugin is IRequiresMediaInfoCallback requiresCallback)
{
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
.ConfigureAwait(false);
@@ -384,9 +381,6 @@ namespace Emby.Server.Implementations.Channels
.ToList();
}
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
{
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
@@ -444,18 +438,21 @@ namespace Emby.Server.Implementations.Channels
{
isNew = true;
}
item.Path = path;
if (!item.ChannelId.Equals(id))
{
forceUpdate = true;
}
item.ChannelId = id;
if (item.ParentId != parentFolderId)
{
forceUpdate = true;
}
item.ParentId = parentFolderId;
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
@@ -472,10 +469,12 @@ namespace Emby.Server.Implementations.Channels
_libraryManager.CreateItem(item, null);
}
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = !isNew && forceUpdate
}, cancellationToken).ConfigureAwait(false);
await item.RefreshMetadata(
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = !isNew && forceUpdate
},
cancellationToken).ConfigureAwait(false);
return item;
}
@@ -509,12 +508,12 @@ namespace Emby.Server.Implementations.Channels
public ChannelFeatures[] GetAllChannelFeatures()
{
return _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Channel).Name },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
return _libraryManager.GetItemIds(
new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Channel).Name },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
}
public ChannelFeatures GetChannelFeatures(string id)
@@ -532,13 +531,13 @@ namespace Emby.Server.Implementations.Channels
public bool SupportsExternalTransfer(Guid channelId)
{
//var channel = GetChannel(channelId);
var channelProvider = GetChannelProvider(channelId);
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
}
public ChannelFeatures GetChannelFeaturesDto(Channel channel,
public ChannelFeatures GetChannelFeaturesDto(
Channel channel,
IChannel provider,
InternalChannelFeatures features)
{
@@ -567,6 +566,7 @@ namespace Emby.Server.Implementations.Channels
{
throw new ArgumentNullException(nameof(name));
}
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
}
@@ -614,7 +614,7 @@ namespace Emby.Server.Implementations.Channels
query.IsFolder = false;
// hack for trailers, figure out a better way later
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal);
if (sortByPremiereDate)
{
@@ -640,10 +640,12 @@ namespace Emby.Server.Implementations.Channels
{
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
var query = new InternalItemsQuery();
query.Parent = internalChannel;
query.EnableTotalRecordCount = false;
query.ChannelIds = new Guid[] { internalChannel.Id };
var query = new InternalItemsQuery
{
Parent = internalChannel,
EnableTotalRecordCount = false,
ChannelIds = new Guid[] { internalChannel.Id }
};
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
@@ -651,13 +653,15 @@ namespace Emby.Server.Implementations.Channels
{
if (item is Folder folder)
{
await GetChannelItemsInternal(new InternalItemsQuery
{
Parent = folder,
EnableTotalRecordCount = false,
ChannelIds = new Guid[] { internalChannel.Id }
}, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
await GetChannelItemsInternal(
new InternalItemsQuery
{
Parent = folder,
EnableTotalRecordCount = false,
ChannelIds = new Guid[] { internalChannel.Id }
},
new SimpleProgress<double>(),
cancellationToken).ConfigureAwait(false);
}
}
}
@@ -672,7 +676,8 @@ namespace Emby.Server.Implementations.Channels
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
var itemsResult = await GetChannelItems(channelProvider,
var itemsResult = await GetChannelItems(
channelProvider,
query.User,
parentItem is Channel ? null : parentItem.ExternalId,
null,
@@ -684,13 +689,12 @@ namespace Emby.Server.Implementations.Channels
{
query.Parent = channel;
}
query.ChannelIds = Array.Empty<Guid>();
// Not yet sure why this is causing a problem
query.GroupByPresentationUniqueKey = false;
//_logger.LogDebug("GetChannelItemsInternal");
// null if came from cache
if (itemsResult != null)
{
@@ -707,12 +711,15 @@ namespace Emby.Server.Implementations.Channels
var deadItem = _libraryManager.GetItemById(deadId);
if (deadItem != null)
{
_libraryManager.DeleteItem(deadItem, new DeleteOptions
{
DeleteFileLocation = false,
DeleteFromExternalProvider = false
}, parentItem, false);
_libraryManager.DeleteItem(
deadItem,
new DeleteOptions
{
DeleteFileLocation = false,
DeleteFromExternalProvider = false
},
parentItem,
false);
}
}
}
@@ -735,7 +742,6 @@ namespace Emby.Server.Implementations.Channels
return result;
}
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
User user,
string externalFolderId,
@@ -743,7 +749,7 @@ namespace Emby.Server.Implementations.Channels
bool sortDescending,
CancellationToken cancellationToken)
{
var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture);
var cacheLength = CacheLength;
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
@@ -761,11 +767,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
}
catch (IOException)
{
}
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -785,11 +789,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
}
catch (IOException)
{
}
var query = new InternalChannelItemQuery
@@ -833,7 +835,8 @@ namespace Emby.Server.Implementations.Channels
}
}
private string GetChannelDataCachePath(IChannel channel,
private string GetChannelDataCachePath(
IChannel channel,
string userId,
string externalFolderId,
ChannelItemSortField? sortField,
@@ -843,8 +846,7 @@ namespace Emby.Server.Implementations.Channels
var userCacheKey = string.Empty;
var hasCacheKey = channel as IHasCacheKey;
if (hasCacheKey != null)
if (channel is IHasCacheKey hasCacheKey)
{
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
}
@@ -858,6 +860,7 @@ namespace Emby.Server.Implementations.Channels
{
filename += "-sortField-" + sortField.Value;
}
if (sortDescending)
{
filename += "-sortDescending";
@@ -865,7 +868,8 @@ namespace Emby.Server.Implementations.Channels
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
return Path.Combine(_config.ApplicationPaths.CachePath,
return Path.Combine(
_config.ApplicationPaths.CachePath,
"channels",
channelId,
version,
@@ -981,7 +985,6 @@ namespace Emby.Server.Implementations.Channels
{
item.RunTimeTicks = null;
}
else if (isNew || !enableMediaProbe)
{
item.RunTimeTicks = info.RunTimeTicks;
@@ -1014,26 +1017,24 @@ namespace Emby.Server.Implementations.Channels
}
}
var hasArtists = item as IHasArtist;
if (hasArtists != null)
if (item is IHasArtist hasArtists)
{
hasArtists.Artists = info.Artists.ToArray();
}
var hasAlbumArtists = item as IHasAlbumArtist;
if (hasAlbumArtists != null)
if (item is IHasAlbumArtist hasAlbumArtists)
{
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
}
var trailer = item as Trailer;
if (trailer != null)
if (item is Trailer trailer)
{
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
{
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
forceUpdate = true;
}
trailer.TrailerTypes = info.TrailerTypes.ToArray();
}
@@ -1057,6 +1058,7 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
}
item.ChannelId = internalChannelId;
if (!item.ParentId.Equals(parentFolderId))
@@ -1064,16 +1066,17 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
}
item.ParentId = parentFolderId;
var hasSeries = item as IHasSeries;
if (hasSeries != null)
if (item is IHasSeries hasSeries)
{
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
{
forceUpdate = true;
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
}
hasSeries.SeriesName = info.SeriesName;
}
@@ -1082,24 +1085,23 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
}
item.ExternalId = info.Id;
var channelAudioItem = item as Audio;
if (channelAudioItem != null)
if (item is Audio channelAudioItem)
{
channelAudioItem.ExtraType = info.ExtraType;
var mediaSource = info.MediaSources.FirstOrDefault();
item.Path = mediaSource == null ? null : mediaSource.Path;
item.Path = mediaSource?.Path;
}
var channelVideoItem = item as Video;
if (channelVideoItem != null)
if (item is Video channelVideoItem)
{
channelVideoItem.ExtraType = info.ExtraType;
var mediaSource = info.MediaSources.FirstOrDefault();
item.Path = mediaSource == null ? null : mediaSource.Path;
item.Path = mediaSource?.Path;
}
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
@@ -1156,7 +1158,7 @@ namespace Emby.Server.Implementations.Channels
}
}
if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
if (isNew || forceUpdate || item.DateLastRefreshed == default)
{
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}

View File

@@ -14,14 +14,12 @@ namespace Emby.Server.Implementations.Channels
public class ChannelPostScanTask
{
private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
{
_channelManager = channelManager;
_userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
}

View File

@@ -7,29 +7,26 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.Channels
{
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
public RefreshChannelsScheduledTask(
IChannelManager channelManager,
IUserManager userManager,
ILogger<RefreshChannelsScheduledTask> logger,
ILibraryManager libraryManager,
ILocalizationManager localization)
{
_channelManager = channelManager;
_userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
_localization = localization;
@@ -63,7 +60,7 @@ namespace Emby.Server.Implementations.Channels
await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
await new ChannelPostScanTask(_channelManager, _userManager, _logger, _libraryManager).Run(progress, cancellationToken)
await new ChannelPostScanTask(_channelManager, _logger, _libraryManager).Run(progress, cancellationToken)
.ConfigureAwait(false);
}
@@ -72,7 +69,6 @@ namespace Emby.Server.Implementations.Channels
{
return new[]
{
// Every so often
new TaskTriggerInfo
{

View File

@@ -46,9 +46,7 @@ namespace Emby.Server.Implementations.Collections
{
var subItem = i;
var episode = subItem as Episode;
if (episode != null)
if (subItem is Episode episode)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))

View File

@@ -52,7 +52,9 @@ namespace Emby.Server.Implementations.Collections
}
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
private IEnumerable<Folder> FindFolders(string path)
@@ -109,9 +111,9 @@ namespace Emby.Server.Implementations.Collections
{
var folder = GetCollectionsFolder(false).Result;
return folder == null ?
new List<BoxSet>() :
folder.GetChildren(user, true).OfType<BoxSet>();
return folder == null
? Enumerable.Empty<BoxSet>()
: folder.GetChildren(user, true).OfType<BoxSet>();
}
public BoxSet CreateCollection(CollectionCreationOptions options)
@@ -191,7 +193,6 @@ namespace Emby.Server.Implementations.Collections
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
if (collection == null)
{
throw new ArgumentException("No collection exists with the supplied Id");
@@ -289,10 +290,13 @@ namespace Emby.Server.Implementations.Collections
}
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
}, RefreshPriority.High);
_providerManager.QueueRefresh(
collection.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
},
RefreshPriority.High);
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
{

View File

@@ -18,7 +18,7 @@ namespace Emby.Server.Implementations
{
{ HostWebClientKey, bool.TrueString },
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" },
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString }

View File

@@ -3,8 +3,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using MediaBrowser.Model.Serialization;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
@@ -109,25 +107,6 @@ namespace Emby.Server.Implementations.Data
return null;
}
/// <summary>
/// Serializes to bytes.
/// </summary>
/// <returns>System.Byte[][].</returns>
/// <exception cref="ArgumentNullException">obj</exception>
public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
using (var stream = new MemoryStream())
{
json.SerializeToStream(obj, stream);
return stream.ToArray();
}
}
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
{
var commandText = string.Format(
@@ -383,11 +362,11 @@ namespace Emby.Server.Implementations.Data
}
}
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
{
while (This.MoveNext())
while (statement.MoveNext())
{
yield return This.Current;
yield return statement.Current;
}
}
}

View File

@@ -33,7 +33,7 @@
<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="Mono.Nat" Version="2.0.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
<PackageReference Include="sharpcompress" Version="0.25.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />

View File

@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Text;
@@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly object _createdRulesLock = new object();
private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
private Timer _timer;
private string _lastConfigIdentifier;
private string _configIdentifier;
private bool _disposed = false;
@@ -60,6 +61,7 @@ namespace Emby.Server.Implementations.EntryPoints
return new StringBuilder(32)
.Append(config.EnableUPnP).Append(Separator)
.Append(config.PublicPort).Append(Separator)
.Append(config.PublicHttpsPort).Append(Separator)
.Append(_appHost.HttpPort).Append(Separator)
.Append(_appHost.HttpsPort).Append(Separator)
.Append(_appHost.EnableHttps).Append(Separator)
@@ -69,7 +71,10 @@ namespace Emby.Server.Implementations.EntryPoints
private void OnConfigurationUpdated(object sender, EventArgs e)
{
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
var oldConfigIdentifier = _configIdentifier;
_configIdentifier = GetConfigIdentifier();
if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
{
Stop();
Start();
@@ -93,21 +98,19 @@ namespace Emby.Server.Implementations.EntryPoints
return;
}
_logger.LogDebug("Starting NAT discovery");
_logger.LogInformation("Starting NAT discovery");
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
NatUtility.StartDiscovery();
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
_lastConfigIdentifier = GetConfigIdentifier();
}
private void Stop()
{
_logger.LogDebug("Stopping NAT discovery");
_logger.LogInformation("Stopping NAT discovery");
NatUtility.StopDiscovery();
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
@@ -117,26 +120,16 @@ namespace Emby.Server.Implementations.EntryPoints
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
}
private void ClearCreatedRules(object state)
{
lock (_createdRulesLock)
{
_createdRules.Clear();
}
}
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
}
private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
{
try
{
var device = e.Device;
CreateRules(device);
await CreateRules(e.Device).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -144,7 +137,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
private async void CreateRules(INatDevice device)
private Task CreateRules(INatDevice device)
{
if (_disposed)
{
@@ -153,50 +146,46 @@ namespace Emby.Server.Implementations.EntryPoints
// On some systems the device discovered event seems to fire repeatedly
// This check will help ensure we're not trying to port map the same device over and over
var address = device.DeviceEndpoint;
lock (_createdRulesLock)
if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
{
if (!_createdRules.Contains(address))
{
_createdRules.Add(address);
}
else
{
return;
}
return Task.CompletedTask;
}
try
{
await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating http port map");
return;
}
return Task.WhenAll(CreatePortMaps(device));
}
try
private IEnumerable<Task> CreatePortMaps(INatDevice device)
{
yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
if (_appHost.EnableHttps)
{
await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating https port map");
yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
}
}
private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
{
_logger.LogDebug(
"Creating port map on local port {0} to public port {1} with device {2}",
"Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
privatePort,
publicPort,
device.DeviceEndpoint);
return device.CreatePortMapAsync(
new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
try
{
var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
privatePort,
publicPort,
device.DeviceEndpoint);
}
}
/// <inheritdoc />

View File

@@ -28,6 +28,12 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public class HttpResultFactory : IHttpResultFactory
{
// Last-Modified and If-Modified-Since must follow strict date format,
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
private const string HttpDateFormat = "ddd, dd MMM yyyy HH:mm:ss \"GMT\"";
// We specifically use en-US culture because both day of week and month names require it
private static readonly CultureInfo _enUSculture = new CultureInfo("en-US", false);
/// <summary>
/// The logger.
/// </summary>
@@ -420,7 +426,11 @@ namespace Emby.Server.Implementations.HttpServer
if (!noCache)
{
DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal, out var ifModifiedSinceHeader))
{
_logger.LogDebug("Failed to parse If-Modified-Since header date: {0}", requestContext.Headers[HeaderNames.IfModifiedSince]);
return null;
}
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
{
@@ -629,7 +639,7 @@ namespace Emby.Server.Implementations.HttpServer
if (lastModifiedDate.HasValue)
{
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture);
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToUniversalTime().ToString(HttpDateFormat, _enUSculture);
}
}

View File

@@ -2335,7 +2335,7 @@ namespace Emby.Server.Implementations.Library
string videoPath,
string[] files)
{
new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
}
/// <inheritdoc />

View File

@@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Library
// for imdbid we also accept pattern matching
if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
{
var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase);
var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
return m.Success ? m.Value : null;
}

View File

@@ -258,6 +258,7 @@ namespace Emby.Server.Implementations.Library
{
if (string.IsNullOrWhiteSpace(username))
{
_logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint);
throw new ArgumentNullException(nameof(username));
}
@@ -313,11 +314,13 @@ namespace Emby.Server.Implementations.Library
if (user == null)
{
_logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("Invalid username or password entered.");
}
if (user.Policy.IsDisabled)
{
_logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException(
string.Format(
CultureInfo.InvariantCulture,
@@ -327,11 +330,13 @@ namespace Emby.Server.Implementations.Library
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
_logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
_logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("User is not allowed access at this time.");
}
@@ -345,14 +350,14 @@ namespace Emby.Server.Implementations.Library
}
ResetInvalidLoginAttemptCount(user);
_logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name);
}
else
{
IncrementInvalidLoginAttemptCount(user);
_logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint);
}
_logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
return success ? user : null;
}

View File

@@ -22,6 +22,10 @@ namespace Emby.Server.Implementations.LiveTv
{
public class LiveTvDtoService
{
private const string InternalVersionNumber = "4";
private const string ServiceName = "Emby";
private readonly ILogger _logger;
private readonly IImageProcessor _imageProcessor;
private readonly IDtoService _dtoService;
@@ -160,7 +164,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
DtoOptions = new DtoOptions(false)
}).FirstOrDefault();
if (librarySeries != null)
@@ -178,6 +181,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogError(ex, "Error");
}
}
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
@@ -198,13 +202,12 @@ namespace Emby.Server.Implementations.LiveTv
var program = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false),
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
}).FirstOrDefault();
if (program != null)
@@ -231,9 +234,10 @@ namespace Emby.Server.Implementations.LiveTv
try
{
dto.ParentBackdropImageTags = new string[]
{
{
_imageProcessor.GetImageCacheTag(program, image)
};
};
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
@@ -254,7 +258,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
DtoOptions = new DtoOptions(false)
}).FirstOrDefault();
if (librarySeries != null)
@@ -272,6 +275,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogError(ex, "Error");
}
}
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
@@ -297,7 +301,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false)
}).FirstOrDefault();
if (program == null)
@@ -310,7 +313,6 @@ namespace Emby.Server.Implementations.LiveTv
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false),
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
}).FirstOrDefault();
}
@@ -395,8 +397,6 @@ namespace Emby.Server.Implementations.LiveTv
return null;
}
private const string InternalVersionNumber = "4";
public Guid GetInternalChannelId(string serviceName, string externalId)
{
var name = serviceName + externalId + InternalVersionNumber;
@@ -404,7 +404,6 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel));
}
private const string ServiceName = "Emby";
public string GetInternalTimerId(string externalId)
{
var name = ServiceName + externalId + InternalVersionNumber;

View File

@@ -41,6 +41,10 @@ namespace Emby.Server.Implementations.LiveTv
/// </summary>
public class LiveTvManager : ILiveTvManager, IDisposable
{
private const string ExternalServiceTag = "ExternalServiceId";
private const string EtagKey = "ProgramEtag";
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
@@ -173,7 +177,6 @@ namespace Emby.Server.Implementations.LiveTv
{
Name = i.Name,
Id = i.Type
}).ToList();
}
@@ -256,6 +259,7 @@ namespace Emby.Server.Implementations.LiveTv
var endTime = DateTime.UtcNow;
_logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
}
info.RequiresClosing = true;
var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
@@ -357,30 +361,37 @@ namespace Emby.Server.Implementations.LiveTv
{
stream.BitRate = null;
}
if (stream.Channels.HasValue && stream.Channels <= 0)
{
stream.Channels = null;
}
if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
{
stream.AverageFrameRate = null;
}
if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
{
stream.RealFrameRate = null;
}
if (stream.Width.HasValue && stream.Width <= 0)
{
stream.Width = null;
}
if (stream.Height.HasValue && stream.Height <= 0)
{
stream.Height = null;
}
if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
{
stream.SampleRate = null;
}
if (stream.Level.HasValue && stream.Level <= 0)
{
stream.Level = null;
@@ -422,7 +433,6 @@ namespace Emby.Server.Implementations.LiveTv
}
}
private const string ExternalServiceTag = "ExternalServiceId";
private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
{
var parentFolderId = parentFolder.Id;
@@ -451,6 +461,7 @@ namespace Emby.Server.Implementations.LiveTv
{
isNew = true;
}
item.Tags = channelInfo.Tags;
}
@@ -458,6 +469,7 @@ namespace Emby.Server.Implementations.LiveTv
{
isNew = true;
}
item.ParentId = parentFolderId;
item.ChannelType = channelInfo.ChannelType;
@@ -467,24 +479,28 @@ namespace Emby.Server.Implementations.LiveTv
{
forceUpdate = true;
}
item.SetProviderId(ExternalServiceTag, serviceName);
if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
{
forceUpdate = true;
}
item.ExternalId = channelInfo.Id;
if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
{
forceUpdate = true;
}
item.Number = channelInfo.Number;
if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
{
forceUpdate = true;
}
item.Name = channelInfo.Name;
if (!item.HasImage(ImageType.Primary))
@@ -513,8 +529,6 @@ namespace Emby.Server.Implementations.LiveTv
return item;
}
private const string EtagKey = "ProgramEtag";
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
{
var id = _tvDtoService.GetInternalProgramId(info.Id);

View File

@@ -3,19 +3,19 @@
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
"Application": "Aplicació",
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'ha autentificat correctament",
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres",
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha sigut pujada des de {0}",
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
"Channels": "Canals",
"ChapterNameValue": "Episodi {0}",
"ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions",
"DeviceOfflineWithName": "{0} s'ha desconnectat",
"DeviceOnlineWithName": "{0} està connectat",
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
"Favorites": "Preferits",
"Folders": "Directoris",
"Folders": "Carpetes",
"Genres": "Gèneres",
"HeaderAlbumArtists": "Artistes dels Àlbums",
"HeaderAlbumArtists": "Artistes del Àlbum",
"HeaderCameraUploads": "Pujades de Càmera",
"HeaderContinueWatching": "Continua Veient",
"HeaderFavoriteAlbums": "Àlbums Preferits",

View File

@@ -35,8 +35,8 @@
"Latest": "Seneste",
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
"MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
"MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguration sektion {0} er blevet opdateret",
"MessageServerConfigurationUpdated": "Server konfigurationen er blevet opdateret",
"MixedContent": "Blandet indhold",
"Movies": "Film",
"Music": "Musik",
@@ -93,13 +93,13 @@
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
"ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}",
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiration.",
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiguration.",
"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.",
"TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdaterer metadata.",
"TaskRefreshLibrary": "Scan Medie Bibliotek",
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
"TaskCleanCache": "Ryd Cache Mappe",
@@ -108,5 +108,11 @@
"TasksLibraryCategory": "Bibliotek",
"TasksMaintenanceCategory": "Vedligeholdelse",
"TaskRefreshChapterImages": "Udtræk Kapitel billeder",
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler."
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.",
"TaskRefreshChannelsDescription": "Genopfrisker internet kanal information.",
"TaskRefreshChannels": "Genopfrisk Kanaler",
"TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.",
"TaskCleanTranscode": "Rengør Transcode Mappen",
"TaskRefreshPeople": "Genopfrisk Personer",
"TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek."
}

View File

@@ -3,7 +3,7 @@
"AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung",
"Artists": "Interpreten",
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifziert",
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert",
"Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
"Channels": "Kanäle",
@@ -99,11 +99,11 @@
"TaskRefreshChannels": "Erneuere Kanäle",
"TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.",
"TaskCleanTranscode": "Lösche Transkodier Pfad",
"TaskUpdatePluginsDescription": "Läd Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
"TaskUpdatePlugins": "Update Plugins",
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.",
"TaskRefreshPeople": "Erneuere Schausteller",
"TaskCleanLogsDescription": "Lösche Log Datein die älter als {0} Tage sind.",
"TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.",
"TaskCleanLogs": "Lösche Log Pfad",
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
"TaskRefreshLibrary": "Scanne alle Bibliotheken",

View File

@@ -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",

View File

@@ -11,11 +11,11 @@
"Collections": "Collections",
"DeviceOfflineWithName": "{0} s'est déconnecté",
"DeviceOnlineWithName": "{0} est connecté",
"FailedLoginAttemptWithUserName": "Échec de connexion de {0}",
"FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}",
"Favorites": "Favoris",
"Folders": "Dossiers",
"Genres": "Genres",
"HeaderAlbumArtists": "Artistes d'album",
"HeaderAlbumArtists": "Artistes de l'album",
"HeaderCameraUploads": "Photos transférées",
"HeaderContinueWatching": "Continuer à regarder",
"HeaderFavoriteAlbums": "Albums favoris",
@@ -69,7 +69,7 @@
"PluginUpdatedWithName": "{0} a été mis à jour",
"ProviderValue": "Fournisseur : {0}",
"ScheduledTaskFailedWithName": "{0} a échoué",
"ScheduledTaskStartedWithName": "{0} a commencé",
"ScheduledTaskStartedWithName": "{0} a démarré",
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
"Shows": "Émissions",
"Songs": "Chansons",
@@ -95,21 +95,21 @@
"VersionNumber": "Version {0}",
"TasksChannelsCategory": "Chaines en ligne",
"TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
"TaskDownloadMissingSubtitles": "Télécharge les sous-titres manquant",
"TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquant",
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
"TaskRefreshChannels": "Rafraîchit les chaines",
"TaskRefreshChannels": "Rafraîchir les chaines",
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
"TaskCleanTranscode": "Nettoie les dossier des transcodages",
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des plugins configurés pour être mis à jour automatiquement.",
"TaskUpdatePlugins": "Mettre à jour les plugins",
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et directeurs dans votre bibliothèque.",
"TaskRefreshPeople": "Rafraîchit les acteurs",
"TaskCleanTranscode": "Nettoyer les dossier des transcodages",
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.",
"TaskUpdatePlugins": "Mettre à jour les extensions",
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.",
"TaskRefreshPeople": "Rafraîchir les acteurs",
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
"TaskCleanLogs": "Nettoie le répertoire des journaux",
"TaskCleanLogs": "Nettoyer le répertoire des journaux",
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
"TaskRefreshLibrary": "Scanne toute les Bibliothèques",
"TaskRefreshLibrary": "Scanner toute les Bibliothèques",
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
"TaskRefreshChapterImages": "Extrait les images de chapitre",
"TaskRefreshChapterImages": "Extraire les images de chapitre",
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
"TaskCleanCache": "Vider le répertoire cache",
"TasksApplicationCategory": "Application",

View File

@@ -109,5 +109,8 @@
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
"TaskUpdatePlugins": "プラグインの更新",
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ"
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
"TaskRefreshChapterImages": "チャプター画像を抽出する"
}

View File

@@ -95,5 +95,13 @@
"TaskCleanCache": "Limpar Diretório de Cache",
"TasksApplicationCategory": "Aplicação",
"TasksLibraryCategory": "Biblioteca",
"TasksMaintenanceCategory": "Manutenção"
"TasksMaintenanceCategory": "Manutenção",
"TaskRefreshChannels": "Atualizar Canais",
"TaskUpdatePlugins": "Atualizar Plugins",
"TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.",
"TaskCleanLogs": "Limpar diretório de log",
"TaskRefreshLibrary": "Escanear biblioteca de mídias",
"TaskRefreshChapterImagesDescription": "Criar miniaturas para videos que tem capítulos.",
"TaskCleanCacheDescription": "Deletar arquivos de cache que não são mais usados pelo sistema.",
"TasksChannelsCategory": "Canais de Internet"
}

View File

@@ -9,8 +9,8 @@
"Channels": "Каналы",
"ChapterNameValue": "Сцена {0}",
"Collections": "Коллекции",
"DeviceOfflineWithName": "{0} - подкл. разъ-но",
"DeviceOnlineWithName": "{0} - подкл. уст-но",
"DeviceOfflineWithName": "{0} - отключено",
"DeviceOnlineWithName": "{0} - подключено",
"FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна",
"Favorites": "Избранное",
"Folders": "Папки",
@@ -26,30 +26,30 @@
"HeaderLiveTV": "Эфир",
"HeaderNextUp": "Очередное",
"HeaderRecordingGroups": "Группы записей",
"HomeVideos": "Дом. видео",
"HomeVideos": "Домашнее видео",
"Inherit": "Наследуемое",
"ItemAddedWithName": "{0} - добавлено в медиатеку",
"ItemRemovedWithName": "{0} - изъято из медиатеки",
"LabelIpAddressValue": "IP-адрес: {0}",
"LabelRunningTimeValue": "Длительность: {0}",
"Latest": "Новейшее",
"Latest": "Последнее",
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена",
"MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена",
"MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
"MessageServerConfigurationUpdated": "Конфигурация сервера была обновлена",
"MixedContent": "Смешанное содержимое",
"Movies": "Кино",
"Music": "Музыка",
"MusicVideos": "Муз. видео",
"MusicVideos": "Музыкальные клипы",
"NameInstallFailed": "Установка {0} неудачна",
"NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Сезон неопознан",
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
"NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но",
"NotificationOptionAudioPlaybackStopped": "Восп-ие аудио ост-но",
"NotificationOptionCameraImageUploaded": "Произведена выкладка отснятого с камеры",
"NotificationOptionAudioPlayback": "Воспроизведение аудио запущено",
"NotificationOptionAudioPlaybackStopped": "Воспроизведение аудио остановлено",
"NotificationOptionCameraImageUploaded": "Изображения с камеры загружены",
"NotificationOptionInstallationFailed": "Сбой установки",
"NotificationOptionNewLibraryContent": "Новое содержание добавлено",
"NotificationOptionPluginError": "Сбой плагина",
@@ -59,8 +59,8 @@
"NotificationOptionServerRestartRequired": "Требуется перезапуск сервера",
"NotificationOptionTaskFailed": "Сбой назначенной задачи",
"NotificationOptionUserLockedOut": "Пользователь заблокирован",
"NotificationOptionVideoPlayback": "Воспр-ие видео зап-но",
"NotificationOptionVideoPlaybackStopped": "Восп-ие видео ост-но",
"NotificationOptionVideoPlayback": "Воспроизведение видео запущено",
"NotificationOptionVideoPlaybackStopped": "Воспроизведение видео остановлено",
"Photos": "Фото",
"Playlists": "Плей-листы",
"Plugin": "Плагин",
@@ -76,21 +76,43 @@
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
"Sync": "Синхро",
"Sync": "Синхронизация",
"System": "Система",
"TvShows": "ТВ",
"User": "Польз-ль",
"User": "Пользователь",
"UserCreatedWithName": "Пользователь {0} был создан",
"UserDeletedWithName": "Пользователь {0} был удалён",
"UserDownloadingItemWithValues": "{0} загружает {1}",
"UserLockedOutWithName": "Пользователь {0} был заблокирован",
"UserOfflineFromDevice": "{0} - подкл. с {1} разъ-но",
"UserOnlineFromDevice": "{0} - подкл. с {1} уст-но",
"UserPasswordChangedWithName": "Пароль польз-ля {0} был изменён",
"UserPolicyUpdatedWithName": "Польз-ие политики {0} были обновлены",
"UserStartedPlayingItemWithValues": "{0} - воспр. «{1}» на {2}",
"UserStoppedPlayingItemWithValues": "{0} - воспр. «{1}» ост-но на {2}",
"UserOfflineFromDevice": "{0} отключился с {1}",
"UserOnlineFromDevice": "{0} подключился с {1}",
"UserPasswordChangedWithName": "Пароль пользователя {0} был изменён",
"UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены",
"UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}",
"UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}",
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
"ValueSpecialEpisodeName": "Спецэпизод - {0}",
"VersionNumber": "Версия {0}"
"ValueSpecialEpisodeName": "Специальный эпизод - {0}",
"VersionNumber": "Версия {0}",
"TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров",
"TaskRefreshChannels": "Обновление каналов",
"TaskCleanTranscode": "Очистка каталога перекодировки",
"TaskUpdatePlugins": "Обновление плагинов",
"TaskRefreshPeople": "Обновление метаданных людей",
"TaskCleanLogs": "Очистка каталога журналов",
"TaskRefreshLibrary": "Сканирование медиатеки",
"TaskRefreshChapterImages": "Извлечение изображений сцен",
"TaskCleanCache": "Очистка каталога кеша",
"TasksChannelsCategory": "Интернет-каналы",
"TasksApplicationCategory": "Приложение",
"TasksLibraryCategory": "Медиатека",
"TasksMaintenanceCategory": "Обслуживание",
"TaskDownloadMissingSubtitlesDescription": "Выполняется поиск в Интернете отсутствующих субтитров на основе конфигурации метаданных.",
"TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.",
"TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.",
"TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.",
"TaskRefreshPeopleDescription": "Обновляются метаданные актеров и режиссёров в медиатеке.",
"TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).",
"TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.",
"TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
"TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе."
}

View File

@@ -20,7 +20,7 @@
"HeaderContinueWatching": "繼續觀賞",
"HeaderFavoriteAlbums": "最愛專輯",
"HeaderFavoriteArtists": "最愛演出者",
"HeaderFavoriteEpisodes": "最愛級數",
"HeaderFavoriteEpisodes": "最愛影集",
"HeaderFavoriteShows": "最愛節目",
"HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播",

View File

@@ -26,7 +26,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Updates
{
/// <summary>
/// Manages all install, uninstall and update operations (both plugins and system).
/// Manages all install, uninstall, and update operations for the system and individual plugins.
/// </summary>
public class InstallationManager : IInstallationManager
{
@@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Updates
public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
/// <summary>
/// The _logger.
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
@@ -112,10 +112,10 @@ namespace Emby.Server.Implementations.Updates
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
/// <inheritdoc />
public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
public event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated;
/// <inheritdoc />
public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
public event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled;
/// <inheritdoc />
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
@@ -183,61 +183,56 @@ namespace Emby.Server.Implementations.Updates
}
/// <inheritdoc />
public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
IEnumerable<PackageVersionInfo> availableVersions,
Version minVersion = null,
PackageVersionClass classification = PackageVersionClass.Release)
public IEnumerable<VersionInfo> GetCompatibleVersions(
IEnumerable<VersionInfo> availableVersions,
Version minVersion = null)
{
var appVer = _applicationHost.ApplicationVersion;
availableVersions = availableVersions
.Where(x => x.classification == classification
&& Version.Parse(x.requiredVersionStr) <= appVer);
.Where(x => Version.Parse(x.targetAbi) <= appVer);
if (minVersion != null)
{
availableVersions = availableVersions.Where(x => x.Version >= minVersion);
availableVersions = availableVersions.Where(x => x.version >= minVersion);
}
return availableVersions.OrderByDescending(x => x.Version);
return availableVersions.OrderByDescending(x => x.version);
}
/// <inheritdoc />
public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
public IEnumerable<VersionInfo> GetCompatibleVersions(
IEnumerable<PackageInfo> availablePackages,
string name = null,
Guid guid = default,
Version minVersion = null,
PackageVersionClass classification = PackageVersionClass.Release)
Version minVersion = null)
{
var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
// Package not found.
// Package not found in repository
if (package == null)
{
return Enumerable.Empty<PackageVersionInfo>();
return Enumerable.Empty<VersionInfo>();
}
return GetCompatibleVersions(
package.versions,
minVersion,
classification);
minVersion);
}
/// <inheritdoc />
public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
public async Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
{
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
return GetAvailablePluginUpdates(catalog);
}
private IEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
private IEnumerable<VersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
{
foreach (var plugin in _applicationHost.Plugins)
{
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)))
var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version);
var version = compatibleversions.FirstOrDefault(y => y.version > plugin.Version);
if (version != null && !CompletedInstallations.Any(x => string.Equals(x.Guid, version.guid, StringComparison.OrdinalIgnoreCase)))
{
yield return version;
}
@@ -245,7 +240,7 @@ namespace Emby.Server.Implementations.Updates
}
/// <inheritdoc />
public async Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken)
public async Task InstallPackage(VersionInfo package, CancellationToken cancellationToken)
{
if (package == null)
{
@@ -254,11 +249,9 @@ namespace Emby.Server.Implementations.Updates
var installationInfo = new InstallationInfo
{
Id = Guid.NewGuid(),
Guid = package.guid,
Name = package.name,
AssemblyGuid = package.guid,
UpdateClass = package.classification,
Version = package.versionStr
Version = package.version.ToString()
};
var innerCancellationTokenSource = new CancellationTokenSource();
@@ -276,7 +269,7 @@ namespace Emby.Server.Implementations.Updates
var installationEventArgs = new InstallationEventArgs
{
InstallationInfo = installationInfo,
PackageVersionInfo = package
VersionInfo = package
};
PackageInstalling?.Invoke(this, installationEventArgs);
@@ -301,7 +294,7 @@ namespace Emby.Server.Implementations.Updates
_currentInstallations.Remove(tuple);
}
_logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.versionStr);
_logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.version);
PackageInstallationCancelled?.Invoke(this, installationEventArgs);
@@ -337,7 +330,7 @@ namespace Emby.Server.Implementations.Updates
/// <param name="package">The package.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns><see cref="Task" />.</returns>
private async Task InstallPackageInternal(PackageVersionInfo package, CancellationToken cancellationToken)
private async Task InstallPackageInternal(VersionInfo package, CancellationToken cancellationToken)
{
// Set last update time if we were installed before
IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
@@ -349,26 +342,26 @@ namespace Emby.Server.Implementations.Updates
// Do plugin-specific processing
if (plugin == null)
{
_logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
_logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.version);
PluginInstalled?.Invoke(this, new GenericEventArgs<PackageVersionInfo>(package));
PluginInstalled?.Invoke(this, new GenericEventArgs<VersionInfo>(package));
}
else
{
_logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
_logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.version);
PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, PackageVersionInfo)>((plugin, package)));
PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, VersionInfo)>((plugin, package)));
}
_applicationHost.NotifyPendingRestart();
}
private async Task PerformPackageInstallation(PackageVersionInfo package, CancellationToken cancellationToken)
private async Task PerformPackageInstallation(VersionInfo package, CancellationToken cancellationToken)
{
var extension = Path.GetExtension(package.targetFilename);
var extension = Path.GetExtension(package.filename);
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
{
_logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename);
_logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.filename);
return;
}
@@ -415,7 +408,7 @@ namespace Emby.Server.Implementations.Updates
}
/// <summary>
/// Uninstalls a plugin
/// Uninstalls a plugin.
/// </summary>
/// <param name="plugin">The plugin.</param>
public void UninstallPlugin(IPlugin plugin)
@@ -473,7 +466,7 @@ namespace Emby.Server.Implementations.Updates
{
lock (_currentInstallationsLock)
{
var install = _currentInstallations.Find(x => x.info.Id == id);
var install = _currentInstallations.Find(x => x.info.Guid == id.ToString());
if (install == default((InstallationInfo, CancellationTokenSource)))
{
return false;