mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-21 09:34:44 +01:00
Merge branch 'master' into warnings-cleanup1
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ namespace Emby.Server.Implementations.Channels
|
||||
_channelManager = channelManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Channel Image Provider";
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
@@ -39,9 +42,6 @@ namespace Emby.Server.Implementations.Channels
|
||||
return channel.GetChannelImage(type, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Channel Image Provider";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -31,31 +31,41 @@ 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;
|
||||
}
|
||||
else if (!_config.Configuration.IsStartupWizardCompleted)
|
||||
|
||||
// Do nothing if the web app is configured to not run automatically
|
||||
var options = ((ApplicationHost)_appHost).StartupOptions;
|
||||
if (!_config.Configuration.AutoRunWebApp || options.NoAutoRunWebApp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"HeaderFavoriteEpisodes": "قسمتهای مورد علاقه",
|
||||
"HeaderFavoriteShows": "سریالهای مورد علاقه",
|
||||
"HeaderFavoriteSongs": "آهنگهای مورد علاقه",
|
||||
"HeaderLiveTV": "تلویزیون زنده",
|
||||
"HeaderLiveTV": "پخش زنده",
|
||||
"HeaderNextUp": "قسمت بعدی",
|
||||
"HeaderRecordingGroups": "گروههای ضبط",
|
||||
"HomeVideos": "ویدیوهای خانگی",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -109,5 +109,8 @@
|
||||
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
||||
"TaskUpdatePlugins": "プラグインの更新",
|
||||
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
|
||||
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ"
|
||||
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
|
||||
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
|
||||
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
||||
"TaskRefreshChapterImages": "チャプター画像を抽出する"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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": "Удаляются файлы кэша, которые больше не нужны системе."
|
||||
}
|
||||
|
||||
@@ -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": "維修"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user