aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs26
-rw-r--r--Emby.Server.Implementations/Activity/ActivityManager.cs9
-rw-r--r--Emby.Server.Implementations/Activity/ActivityRepository.cs13
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs27
-rw-r--r--Emby.Server.Implementations/Channels/ChannelImageProvider.cs6
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs123
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs18
-rw-r--r--Emby.Server.Implementations/ConfigurationOptions.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteExtensions.cs27
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj7
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs93
-rw-r--r--Emby.Server.Implementations/EntryPoints/StartupWizard.cs36
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs14
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs25
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs24
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/da.json16
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/fa.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json26
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-PT.json26
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json66
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json36
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs77
30 files changed, 425 insertions, 324 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index 2ea678976..4685a03ac 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -26,7 +26,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
/// <summary>
- /// The activity log entry point.
+ /// Entry point for the activity logger.
/// </summary>
public sealed class ActivityLogEntryPoint : IServerEntryPoint
{
@@ -169,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
});
@@ -395,7 +400,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
{
@@ -407,8 +412,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
});
}
@@ -424,7 +429,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
{
@@ -436,7 +441,7 @@ namespace Emby.Server.Implementations.Activity
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
- e.Argument.versionStr)
+ e.Argument.version)
});
}
@@ -464,7 +469,8 @@ namespace Emby.Server.Implementations.Activity
var result = e.Result;
var task = e.Task;
- if (task.ScheduledTask is IConfigurableScheduledTask activityTask && !activityTask.IsLogged)
+ if (task.ScheduledTask is IConfigurableScheduledTask activityTask
+ && !activityTask.IsLogged)
{
return;
}
@@ -538,9 +544,7 @@ namespace Emby.Server.Implementations.Activity
/// <summary>
/// Constructs a user-friendly string for this TimeSpan instance.
/// </summary>
- /// <param name="span">The timespan.</param>
- /// <returns>The user-friendly string.</returns>
- public static string ToUserFriendlyString(TimeSpan span)
+ private static string ToUserFriendlyString(TimeSpan span)
{
const int DaysInYear = 365;
const int DaysInMonth = 30;
diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs
index 5c04c0e51..81bebae3d 100644
--- a/Emby.Server.Implementations/Activity/ActivityManager.cs
+++ b/Emby.Server.Implementations/Activity/ActivityManager.cs
@@ -11,9 +11,6 @@ namespace Emby.Server.Implementations.Activity
/// </summary>
public class ActivityManager : IActivityManager
{
- /// <inheritdoc />
- public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
-
private readonly IActivityRepository _repo;
private readonly IUserManager _userManager;
@@ -22,15 +19,15 @@ namespace Emby.Server.Implementations.Activity
/// </summary>
/// <param name="repo">The activity repository.</param>
/// <param name="userManager">The user manager.</param>
- public ActivityManager(
- IActivityRepository repo,
- IUserManager userManager)
+ public ActivityManager(IActivityRepository repo, IUserManager userManager)
{
_repo = repo;
_userManager = userManager;
}
/// <inheritdoc />
+ public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
+
public void Create(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs
index 3aa1f0397..83471935d 100644
--- a/Emby.Server.Implementations/Activity/ActivityRepository.cs
+++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs
@@ -18,17 +18,18 @@ namespace Emby.Server.Implementations.Activity
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityRepository"/> class.
/// </summary>
- /// <param name="loggerFactory">The logger factory.</param>
+ /// <param name="logger">The logger.</param>
/// <param name="appPaths">The server application paths.</param>
/// <param name="fileSystem">The filesystem.</param>
- public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
- : base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
+ public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
+ : base(logger)
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
_fileSystem = fileSystem;
@@ -205,7 +206,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[]
@@ -300,7 +301,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;
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 7e80900f4..d6d153818 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -211,19 +211,6 @@ namespace Emby.Server.Implementations
public IFileSystem FileSystemManager { 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>
@@ -781,7 +768,16 @@ namespace Emby.Server.Implementations
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
serviceCollection.AddSingleton(DtoService);
- ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager);
+ ChannelManager = new ChannelManager(
+ UserManager,
+ DtoService,
+ LibraryManager,
+ LoggerFactory.CreateLogger<ChannelManager>(),
+ ServerConfigurationManager,
+ FileSystemManager,
+ UserDataManager,
+ JsonSerializer,
+ ProviderManager);
serviceCollection.AddSingleton(ChannelManager);
SessionManager = new SessionManager(
@@ -970,7 +966,7 @@ namespace Emby.Server.Implementations
private IActivityRepository GetActivityLogRepository()
{
- var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager);
+ var repo = new ActivityRepository(LoggerFactory.CreateLogger<ActivityRepository>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager);
repo.Initialize();
@@ -1381,7 +1377,6 @@ namespace Emby.Server.Implementations
SupportsLibraryMonitor = true,
EncoderLocation = MediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture,
- SystemUpdateLevel = SystemUpdateLevel,
PackageName = StartupOptions.PackageName
};
}
diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
index c08a237fb..25cbfcf14 100644
--- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
@@ -26,6 +26,9 @@ namespace Emby.Server.Implementations.Channels
}
/// <inheritdoc />
+ public string Name => "Channel Image Provider";
+
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return GetChannel(item).GetSupportedChannelImages();
@@ -40,9 +43,6 @@ namespace Emby.Server.Implementations.Channels
}
/// <inheritdoc />
- public string Name => "Channel Image Provider";
-
- /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is Channel;
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 41eb58bab..138832fb8 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -32,8 +32,6 @@ namespace Emby.Server.Implementations.Channels
/// </summary>
public class ChannelManager : IChannelManager
{
- internal IChannel[] Channels { get; private set; }
-
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly IDtoService _dtoService;
@@ -44,6 +42,11 @@ 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);
+
/// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
/// </summary>
@@ -60,7 +63,7 @@ namespace Emby.Server.Implementations.Channels
IUserManager userManager,
IDtoService dtoService,
ILibraryManager libraryManager,
- ILoggerFactory loggerFactory,
+ ILogger<ChannelManager> logger,
IServerConfigurationManager config,
IFileSystem fileSystem,
IUserDataManager userDataManager,
@@ -70,7 +73,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;
@@ -78,6 +81,8 @@ namespace Emby.Server.Implementations.Channels
_providerManager = providerManager;
}
+ internal IChannel[] Channels { get; private set; }
+
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
/// <inheritdoc />
@@ -169,7 +174,7 @@ namespace Emby.Server.Implementations.Channels
try
{
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
- && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
+ && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
}
catch
{
@@ -276,11 +281,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>
@@ -360,8 +363,8 @@ namespace Emby.Server.Implementations.Channels
}
catch
{
-
}
+
return;
}
@@ -391,11 +394,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);
@@ -410,9 +411,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))
@@ -470,18 +468,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);
@@ -498,10 +499,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;
}
@@ -537,11 +540,12 @@ namespace Emby.Server.Implementations.Channels
/// <inheritdoc />
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();
}
/// <inheritdoc />
@@ -565,7 +569,6 @@ namespace Emby.Server.Implementations.Channels
/// <returns>Whether or not the provided Guid supports external transfer.</returns>
public bool SupportsExternalTransfer(Guid channelId)
{
- //var channel = GetChannel(channelId);
var channelProvider = GetChannelProvider(channelId);
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
@@ -608,6 +611,7 @@ namespace Emby.Server.Implementations.Channels
{
throw new ArgumentNullException(nameof(name));
}
+
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
}
@@ -657,7 +661,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)
{
@@ -683,10 +687,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);
@@ -694,13 +700,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);
}
}
}
@@ -716,7 +724,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,
@@ -728,13 +737,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)
{
@@ -751,11 +759,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);
}
}
}
@@ -779,7 +791,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,
@@ -805,11 +816,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
-
}
catch (IOException)
{
-
}
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -829,11 +838,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
-
}
catch (IOException)
{
-
}
var query = new InternalChannelItemQuery
@@ -877,7 +884,8 @@ namespace Emby.Server.Implementations.Channels
}
}
- private string GetChannelDataCachePath(IChannel channel,
+ private string GetChannelDataCachePath(
+ IChannel channel,
string userId,
string externalFolderId,
ChannelItemSortField? sortField,
@@ -901,6 +909,7 @@ namespace Emby.Server.Implementations.Channels
{
filename += "-sortField-" + sortField.Value;
}
+
if (sortDescending)
{
filename += "-sortDescending";
@@ -908,7 +917,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,
@@ -1045,6 +1055,7 @@ namespace Emby.Server.Implementations.Channels
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
forceUpdate = true;
}
+
trailer.TrailerTypes = info.TrailerTypes.ToArray();
}
@@ -1068,6 +1079,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))
@@ -1075,6 +1087,7 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
}
+
item.ParentId = parentFolderId;
if (item is IHasSeries hasSeries)
@@ -1084,6 +1097,7 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
}
+
hasSeries.SeriesName = info.SeriesName;
}
@@ -1092,6 +1106,7 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
}
+
item.ExternalId = info.Id;
if (item is Audio channelAudioItem)
@@ -1164,7 +1179,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);
}
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 4b04accbb..7c518d483 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -125,9 +125,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>();
}
/// <inheritdoc />
@@ -210,7 +210,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");
@@ -310,10 +309,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
{
diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
index 20bdd18e7..db7c35a7c 100644
--- a/Emby.Server.Implementations/ConfigurationOptions.cs
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -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 }
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index e7c5270b9..716e5071d 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -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;
}
}
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index d46b9507b..bf4a0d939 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
@@ -33,7 +38,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" />
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index e290c62e1..37d7fd479 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -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
- {
- await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
- }
- catch (Exception ex)
+ private IEnumerable<Task> CreatePortMaps(INatDevice device)
+ {
+ yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
+
+ if (_appHost.EnableHttps)
{
- _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 />
diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs
index 8e9771931..a0a653d75 100644
--- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs
+++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs
@@ -32,30 +32,40 @@ namespace Emby.Server.Implementations.EntryPoints
/// <inheritdoc />
public Task RunAsync()
{
+ Run();
+ return Task.CompletedTask;
+ }
+
+ private void Run()
+ {
if (!_appHost.CanLaunchWebBrowser)
{
- return Task.CompletedTask;
+ return;
}
- if (!_appConfig.HostWebClient())
+ // Always launch the startup wizard if possible when it has not been completed
+ if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient())
{
- BrowserLauncher.OpenSwaggerPage(_appHost);
+ BrowserLauncher.OpenWebApp(_appHost);
+ return;
+ }
+
+ // Do nothing if the web app is configured to not run automatically
+ var options = ((ApplicationHost)_appHost).StartupOptions;
+ if (!_config.Configuration.AutoRunWebApp || options.NoAutoRunWebApp)
+ {
+ return;
}
- else if (!_config.Configuration.IsStartupWizardCompleted)
+
+ // Launch the swagger page if the web client is not hosted, otherwise open the web client
+ if (_appConfig.HostWebClient())
{
BrowserLauncher.OpenWebApp(_appHost);
}
- else if (_config.Configuration.AutoRunWebApp)
+ else
{
- var options = ((ApplicationHost)_appHost).StartupOptions;
-
- if (!options.NoAutoRunWebApp)
- {
- BrowserLauncher.OpenWebApp(_appHost);
- }
+ BrowserLauncher.OpenSwaggerPage(_appHost);
}
-
- return Task.CompletedTask;
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index b42662420..464ca3a0b 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -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);
}
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 4e2cf334c..50a5135d4 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2364,7 +2364,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 />
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 4fdf73b77..1d61ed57e 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -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;
}
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 7b17cc913..15076a194 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -264,6 +264,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));
}
@@ -319,11 +320,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,
@@ -333,11 +336,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.");
}
@@ -351,14 +356,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;
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 6e903a18e..3b36247a9 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -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;
@@ -32,13 +36,13 @@ namespace Emby.Server.Implementations.LiveTv
public LiveTvDtoService(
IDtoService dtoService,
IImageProcessor imageProcessor,
- ILoggerFactory loggerFactory,
+ ILogger<LiveTvDtoService> logger,
IApplicationHost appHost,
ILibraryManager libraryManager)
{
_dtoService = dtoService;
_imageProcessor = imageProcessor;
- _logger = loggerFactory.CreateLogger(nameof(LiveTvDtoService));
+ _logger = logger;
_appHost = appHost;
_libraryManager = libraryManager;
}
@@ -161,7 +165,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (librarySeries != null)
@@ -179,6 +182,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogError(ex, "Error");
}
}
+
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
@@ -199,13 +203,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)
@@ -232,9 +235,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)
@@ -255,7 +259,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (librarySeries != null)
@@ -273,6 +276,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogError(ex, "Error");
}
}
+
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
@@ -298,7 +302,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (program == null)
@@ -311,7 +314,6 @@ namespace Emby.Server.Implementations.LiveTv
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false),
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
-
}).FirstOrDefault();
}
@@ -396,8 +398,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;
@@ -405,7 +405,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;
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index b64fe8634..bbc064a24 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -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;
@@ -91,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv
_userDataManager = userDataManager;
_channelManager = channelManager;
- _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, loggerFactory, appHost, _libraryManager);
+ _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, loggerFactory.CreateLogger<LiveTvDtoService>(), appHost, _libraryManager);
}
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
@@ -178,7 +182,6 @@ namespace Emby.Server.Implementations.LiveTv
{
Name = i.Name,
Id = i.Type
-
}).ToList();
}
@@ -261,6 +264,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) + "_";
@@ -362,30 +366,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;
@@ -427,7 +438,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;
@@ -456,6 +466,7 @@ namespace Emby.Server.Implementations.LiveTv
{
isNew = true;
}
+
item.Tags = channelInfo.Tags;
}
@@ -463,6 +474,7 @@ namespace Emby.Server.Implementations.LiveTv
{
isNew = true;
}
+
item.ParentId = parentFolderId;
item.ChannelType = channelInfo.ChannelType;
@@ -472,24 +484,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))
@@ -518,8 +534,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);
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 2d8299367..7464ac1c0 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -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",
diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json
index 93b8052d3..f5397b62c 100644
--- a/Emby.Server.Implementations/Localization/Core/da.json
+++ b/Emby.Server.Implementations/Localization/Core/da.json
@@ -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."
}
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 414430ff7..82df43be1 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -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",
diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json
index be6f87ee3..500c29217 100644
--- a/Emby.Server.Implementations/Localization/Core/fa.json
+++ b/Emby.Server.Implementations/Localization/Core/fa.json
@@ -23,7 +23,7 @@
"HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه",
"HeaderFavoriteShows": "سریال‌های مورد علاقه",
"HeaderFavoriteSongs": "آهنگ‌های مورد علاقه",
- "HeaderLiveTV": "تلویزیون زنده",
+ "HeaderLiveTV": "پخش زنده",
"HeaderNextUp": "قسمت بعدی",
"HeaderRecordingGroups": "گروه‌های ضبط",
"HomeVideos": "ویدیوهای خانگی",
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index d1403c494..150952d8b 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -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",
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index d0daed7a3..5e017d4c4 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -109,5 +109,8 @@
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
"TaskUpdatePlugins": "プラグインの更新",
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
- "TaskRefreshPeople": "俳優や監督のデータのリフレッシュ"
+ "TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
+ "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
+ "TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
+ "TaskRefreshChapterImages": "チャプター画像を抽出する"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index 3bc9c2a77..22dcf1d2e 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -1,11 +1,11 @@
{
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Apparaat: {1}",
- "Application": "Applicatie",
+ "Application": "Programma",
"Artists": "Artiesten",
- "AuthenticationSucceededWithUserName": "{0} succesvol geauthenticeerd",
+ "AuthenticationSucceededWithUserName": "{0} is succesvol geverifiëerd",
"Books": "Boeken",
- "CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd van {0}",
+ "CameraImageUploadedFrom": "Er is een nieuwe afbeelding toegevoegd via {0}",
"Channels": "Kanalen",
"ChapterNameValue": "Hoofdstuk {0}",
"Collections": "Verzamelingen",
diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json
index ebf35c492..c1fb65743 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-PT.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json
@@ -26,7 +26,7 @@
"HeaderLiveTV": "TV em Direto",
"HeaderNextUp": "A Seguir",
"HeaderRecordingGroups": "Grupos de Gravação",
- "HomeVideos": "Home videos",
+ "HomeVideos": "Videos caseiros",
"Inherit": "Herdar",
"ItemAddedWithName": "{0} foi adicionado à biblioteca",
"ItemRemovedWithName": "{0} foi removido da biblioteca",
@@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}",
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia",
"ValueSpecialEpisodeName": "Especial - {0}",
- "VersionNumber": "Versão {0}"
+ "VersionNumber": "Versão {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas em falta baseado na configuração de metadados.",
+ "TaskDownloadMissingSubtitles": "Fazer download de legendas em falta",
+ "TaskRefreshChannelsDescription": "Atualizar informação sobre canais da Internet.",
+ "TaskRefreshChannels": "Atualizar Canais",
+ "TaskCleanTranscodeDescription": "Apagar ficheiros de transcode com mais de um dia.",
+ "TaskCleanTranscode": "Limpar a Diretoria de Transcode",
+ "TaskUpdatePluginsDescription": "Faz o download e instala updates para os plugins que estão configurados para atualizar automaticamente.",
+ "TaskUpdatePlugins": "Atualizar Plugins",
+ "TaskRefreshPeopleDescription": "Atualizar metadados para atores e diretores na biblioteca.",
+ "TaskRefreshPeople": "Atualizar Pessoas",
+ "TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.",
+ "TaskCleanLogs": "Limpar a Diretoria de Logs",
+ "TaskRefreshLibraryDescription": "Scannear a biblioteca de música para novos ficheiros e atualizar os metadados.",
+ "TaskRefreshLibrary": "Scannear Biblioteca de Música",
+ "TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.",
+ "TaskRefreshChapterImages": "Extrair Imagens dos Capítulos",
+ "TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.",
+ "TaskCleanCache": "Limpar Cache",
+ "TasksChannelsCategory": "Canais da Internet",
+ "TasksApplicationCategory": "Aplicação",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Manutenção"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index 661ee8603..25c5b9053 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -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"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index c46aa5c30..71ee6446c 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -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": "Удаляются файлы кэша, которые больше не нужны системе."
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index 21034b76f..a22f66df9 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -20,7 +20,7 @@
"HeaderContinueWatching": "繼續觀賞",
"HeaderFavoriteAlbums": "最愛專輯",
"HeaderFavoriteArtists": "最愛演出者",
- "HeaderFavoriteEpisodes": "最愛級數",
+ "HeaderFavoriteEpisodes": "最愛影集",
"HeaderFavoriteShows": "最愛節目",
"HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播",
@@ -50,10 +50,10 @@
"NotificationOptionCameraImageUploaded": "相機相片已上傳",
"NotificationOptionInstallationFailed": "安裝失敗",
"NotificationOptionNewLibraryContent": "已新增新內容",
- "NotificationOptionPluginError": "擴充元件錯誤",
- "NotificationOptionPluginInstalled": "擴充元件已安裝",
- "NotificationOptionPluginUninstalled": "擴充元件已移除",
- "NotificationOptionPluginUpdateInstalled": "已更新擴充元件",
+ "NotificationOptionPluginError": "插件安裝錯誤",
+ "NotificationOptionPluginInstalled": "插件已安裝",
+ "NotificationOptionPluginUninstalled": "插件已移除",
+ "NotificationOptionPluginUpdateInstalled": "插件已更新",
"NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
"NotificationOptionTaskFailed": "排程任務失敗",
"NotificationOptionUserLockedOut": "使用者已鎖定",
@@ -61,7 +61,7 @@
"NotificationOptionVideoPlaybackStopped": "影片停止播放",
"Photos": "相片",
"Playlists": "播放清單",
- "Plugin": "外掛",
+ "Plugin": "插件",
"PluginInstalledWithName": "{0} 已安裝",
"PluginUninstalledWithName": "{0} 已移除",
"PluginUpdatedWithName": "{0} 已更新",
@@ -91,5 +91,27 @@
"VersionNumber": "版本 {0}",
"HeaderRecordingGroups": "錄製組",
"Inherit": "繼承",
- "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕"
+ "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕",
+ "TaskDownloadMissingSubtitlesDescription": "在網路上透過描述資料搜尋遺失的字幕。",
+ "TaskDownloadMissingSubtitles": "下載遺失的字幕",
+ "TaskRefreshChannels": "重新整理頻道",
+ "TaskUpdatePlugins": "更新插件",
+ "TaskRefreshPeople": "重新整理人員",
+ "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔案。",
+ "TaskCleanLogs": "清空紀錄資料夾",
+ "TaskRefreshLibraryDescription": "掃描媒體庫內新的檔案並重新整理描述資料。",
+ "TaskRefreshLibrary": "掃描媒體庫",
+ "TaskRefreshChapterImages": "擷取章節圖片",
+ "TaskCleanCacheDescription": "刪除系統長時間不需要的快取。",
+ "TaskCleanCache": "清除快取資料夾",
+ "TasksLibraryCategory": "媒體庫",
+ "TaskRefreshChannelsDescription": "重新整理網絡頻道資料。",
+ "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
+ "TaskCleanTranscode": "清除轉碼資料夾",
+ "TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。",
+ "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。",
+ "TaskRefreshChapterImagesDescription": "為有章節的視頻創建縮圖。",
+ "TasksChannelsCategory": "網絡頻道",
+ "TasksApplicationCategory": "應用程式",
+ "TasksMaintenanceCategory": "維修"
}
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 25f70471a..0b2309889 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -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;