mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-15 06:36:37 +00:00
Merge 'master' updates into 2354-soft-link-sizes
This commit is contained in:
@@ -88,25 +88,26 @@ namespace Emby.Server.Implementations.Activity
|
||||
|
||||
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated += OnUserCreated;
|
||||
_userManager.UserPasswordChanged += OnUserPasswordChanged;
|
||||
_userManager.UserDeleted += OnUserDeleted;
|
||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
||||
_userManager.UserLockedOut += OnUserLockedOut;
|
||||
_userManager.OnUserCreated += OnUserCreated;
|
||||
_userManager.OnUserPasswordChanged += OnUserPasswordChanged;
|
||||
_userManager.OnUserDeleted += OnUserDeleted;
|
||||
_userManager.OnUserLockedOut += OnUserLockedOut;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserLockedOutWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
NotificationType.UserLockedOut.ToString(),
|
||||
e.Argument.Id))
|
||||
.ConfigureAwait(false);
|
||||
e.Argument.Id)
|
||||
{
|
||||
LogSeverity = LogLevel.Error
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||
@@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
||||
user.Name,
|
||||
user.Username,
|
||||
GetItemName(item),
|
||||
e.DeviceName),
|
||||
GetPlaybackStoppedNotificationType(item.MediaType),
|
||||
@@ -187,7 +188,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
||||
user.Name,
|
||||
user.Username,
|
||||
GetItemName(item),
|
||||
e.DeviceName),
|
||||
GetPlaybackNotificationType(item.MediaType),
|
||||
@@ -304,49 +305,37 @@ namespace Emby.Server.Implementations.Activity
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
|
||||
e.Argument.Name),
|
||||
"UserPolicyUpdated",
|
||||
e.Argument.Id))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserDeletedWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
"UserDeleted",
|
||||
Guid.Empty))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
"UserPasswordChanged",
|
||||
e.Argument.Id))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserCreated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserCreatedWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
"UserCreated",
|
||||
e.Argument.Id))
|
||||
.ConfigureAwait(false);
|
||||
@@ -377,50 +366,50 @@ namespace Emby.Server.Implementations.Activity
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
|
||||
private async void OnPluginUpdated(object sender, InstallationInfo e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("PluginUpdatedWithName"),
|
||||
e.Argument.Item1.Name),
|
||||
e.Name),
|
||||
NotificationType.PluginUpdateInstalled.ToString(),
|
||||
Guid.Empty)
|
||||
{
|
||||
ShortOverview = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("VersionNumber"),
|
||||
e.Argument.Item2.version),
|
||||
Overview = e.Argument.Item2.changelog
|
||||
e.Version),
|
||||
Overview = e.Changelog
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||
private async void OnPluginUninstalled(object sender, IPlugin e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("PluginUninstalledWithName"),
|
||||
e.Argument.Name),
|
||||
e.Name),
|
||||
NotificationType.PluginUninstalled.ToString(),
|
||||
Guid.Empty))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
|
||||
private async void OnPluginInstalled(object sender, InstallationInfo e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("PluginInstalledWithName"),
|
||||
e.Argument.name),
|
||||
e.Name),
|
||||
NotificationType.PluginInstalled.ToString(),
|
||||
Guid.Empty)
|
||||
{
|
||||
ShortOverview = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("VersionNumber"),
|
||||
e.Argument.version)
|
||||
e.Version)
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -510,11 +499,10 @@ namespace Emby.Server.Implementations.Activity
|
||||
|
||||
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated -= OnUserCreated;
|
||||
_userManager.UserPasswordChanged -= OnUserPasswordChanged;
|
||||
_userManager.UserDeleted -= OnUserDeleted;
|
||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
||||
_userManager.UserLockedOut -= OnUserLockedOut;
|
||||
_userManager.OnUserCreated -= OnUserCreated;
|
||||
_userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
|
||||
_userManager.OnUserDeleted -= OnUserDeleted;
|
||||
_userManager.OnUserLockedOut -= OnUserLockedOut;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
CommonApplicationPaths = applicationPaths;
|
||||
XmlSerializer = xmlSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
Logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
Logger = loggerFactory.CreateLogger<BaseConfigurationManager>();
|
||||
|
||||
UpdateCachePath();
|
||||
}
|
||||
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
protected ILogger Logger { get; private set; }
|
||||
protected ILogger<BaseConfigurationManager> Logger { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XML serializer.
|
||||
|
||||
@@ -43,9 +43,9 @@ using Emby.Server.Implementations.Security;
|
||||
using Emby.Server.Implementations.Serialization;
|
||||
using Emby.Server.Implementations.Services;
|
||||
using Emby.Server.Implementations.Session;
|
||||
using Emby.Server.Implementations.SyncPlay;
|
||||
using Emby.Server.Implementations.TV;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using Emby.Server.Implementations.SyncPlay;
|
||||
using MediaBrowser.Api;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
@@ -78,8 +78,8 @@ using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Controller.SyncPlay;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.LocalMetadata.Savers;
|
||||
using MediaBrowser.MediaEncoding.BdInfo;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
@@ -173,7 +173,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
protected ILogger Logger { get; }
|
||||
protected ILogger<ApplicationHost> Logger { get; }
|
||||
|
||||
private IPlugin[] _plugins;
|
||||
|
||||
@@ -484,12 +484,10 @@ namespace Emby.Server.Implementations
|
||||
|
||||
foreach (var plugin in Plugins)
|
||||
{
|
||||
pluginBuilder.AppendLine(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} {1}",
|
||||
plugin.Name,
|
||||
plugin.Version));
|
||||
pluginBuilder.Append(plugin.Name)
|
||||
.Append(' ')
|
||||
.Append(plugin.Version)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
|
||||
@@ -562,17 +560,12 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
||||
|
||||
serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
|
||||
|
||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
||||
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||
|
||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
|
||||
serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
|
||||
serviceCollection.AddSingleton<IMediaEncoder>(provider =>
|
||||
ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty));
|
||||
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||
|
||||
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
||||
serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
||||
@@ -659,15 +652,11 @@ namespace Emby.Server.Implementations
|
||||
|
||||
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
|
||||
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
||||
((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
|
||||
|
||||
SetStaticProperties();
|
||||
|
||||
var userManager = (UserManager)Resolve<IUserManager>();
|
||||
userManager.Initialize();
|
||||
|
||||
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
|
||||
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager);
|
||||
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, Resolve<IUserManager>());
|
||||
|
||||
FindParts();
|
||||
}
|
||||
@@ -750,7 +739,6 @@ namespace Emby.Server.Implementations
|
||||
BaseItem.ProviderManager = Resolve<IProviderManager>();
|
||||
BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
|
||||
BaseItem.ItemRepository = Resolve<IItemRepository>();
|
||||
User.UserManager = Resolve<IUserManager>();
|
||||
BaseItem.FileSystem = _fileSystemManager;
|
||||
BaseItem.UserDataManager = Resolve<IUserDataManager>();
|
||||
BaseItem.ChannelManager = Resolve<IChannelManager>();
|
||||
@@ -881,6 +869,11 @@ namespace Emby.Server.Implementations
|
||||
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
|
||||
continue;
|
||||
}
|
||||
catch (TypeLoadException ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading types from {Assembly}.", ass.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Type type in exportedTypes)
|
||||
{
|
||||
@@ -964,7 +957,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies that the kernel that a change has been made that requires a restart
|
||||
/// Notifies that the kernel that a change has been made that requires a restart.
|
||||
/// </summary>
|
||||
public void NotifyPendingRestart()
|
||||
{
|
||||
@@ -1163,7 +1156,7 @@ namespace Emby.Server.Implementations
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetLocalApiUrl(addresses.First());
|
||||
return GetLocalApiUrl(addresses[0]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1236,13 +1229,13 @@ namespace Emby.Server.Implementations
|
||||
var addresses = ServerConfigurationManager
|
||||
.Configuration
|
||||
.LocalNetworkAddresses
|
||||
.Select(NormalizeConfiguredLocalAddress)
|
||||
.Select(x => NormalizeConfiguredLocalAddress(x))
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
|
||||
if (addresses.Count == 0)
|
||||
{
|
||||
addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
||||
addresses.AddRange(_networkManager.GetLocalIpAddresses());
|
||||
}
|
||||
|
||||
var resultList = new List<IPAddress>();
|
||||
@@ -1257,8 +1250,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
var valid = await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false);
|
||||
if (valid)
|
||||
if (await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
resultList.Add(address);
|
||||
|
||||
@@ -1272,13 +1264,12 @@ namespace Emby.Server.Implementations
|
||||
return resultList;
|
||||
}
|
||||
|
||||
public IPAddress NormalizeConfiguredLocalAddress(string address)
|
||||
public IPAddress NormalizeConfiguredLocalAddress(ReadOnlySpan<char> address)
|
||||
{
|
||||
var index = address.Trim('/').IndexOf('/');
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
address = address.Substring(index + 1);
|
||||
address = address.Slice(index + 1);
|
||||
}
|
||||
|
||||
if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Browser
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = appHost.Resolve<ILogger>();
|
||||
var logger = appHost.Resolve<ILogger<IServerApplicationHost>>();
|
||||
logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
@@ -13,8 +14,6 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Channels;
|
||||
@@ -24,6 +23,11 @@ using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
||||
using Season = MediaBrowser.Controller.Entities.TV.Season;
|
||||
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
||||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
@@ -36,7 +40,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<ChannelManager> _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
@@ -46,14 +50,14 @@ namespace Emby.Server.Implementations.Channels
|
||||
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>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="dtoService">The dto service.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="config">The server configuration manager.</param>
|
||||
/// <param name="fileSystem">The filesystem.</param>
|
||||
/// <param name="userDataManager">The user data manager.</param>
|
||||
@@ -791,7 +795,8 @@ namespace Emby.Server.Implementations.Channels
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
||||
private async Task<ChannelItemResult> GetChannelItems(
|
||||
IChannel channel,
|
||||
User user,
|
||||
string externalFolderId,
|
||||
ChannelItemSortField? sortField,
|
||||
@@ -1067,7 +1072,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
}
|
||||
|
||||
// was used for status
|
||||
//if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
|
||||
// if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
|
||||
//{
|
||||
// item.ExternalEtag = info.Etag;
|
||||
// forceUpdate = true;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<RefreshChannelsScheduledTask> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -29,7 +30,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILibraryMonitor _iLibraryMonitor;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<CollectionManager> _logger;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
@@ -56,7 +57,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
_libraryManager = libraryManager;
|
||||
_fileSystem = fileSystem;
|
||||
_iLibraryMonitor = iLibraryMonitor;
|
||||
_logger = loggerFactory.CreateLogger(nameof(CollectionManager));
|
||||
_logger = loggerFactory.CreateLogger<CollectionManager>();
|
||||
_providerManager = providerManager;
|
||||
_localizationManager = localizationManager;
|
||||
_appPaths = appPaths;
|
||||
@@ -362,60 +363,4 @@ namespace Emby.Server.Implementations.Collections
|
||||
return results.Values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The collection manager entry point.
|
||||
/// </summary>
|
||||
public sealed class CollectionManagerEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly CollectionManager _collectionManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collectionManager">The collection manager.</param>
|
||||
/// <param name="config">The server configuration manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public CollectionManagerEntryPoint(
|
||||
ICollectionManager collectionManager,
|
||||
IServerConfigurationManager config,
|
||||
ILogger<CollectionManagerEntryPoint> logger)
|
||||
{
|
||||
_collectionManager = (CollectionManager)collectionManager;
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RunAsync()
|
||||
{
|
||||
if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted)
|
||||
{
|
||||
var path = _collectionManager.GetCollectionsFolderPath();
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _collectionManager.EnsureLibraryFolder(path, true).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating camera uploads library");
|
||||
}
|
||||
|
||||
_config.Configuration.CollectionsUpgraded = true;
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing to dispose
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Configuration
|
||||
ValidateMetadataPath(newConfig);
|
||||
ValidateSslCertificate(newConfig);
|
||||
|
||||
ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration> { Argument = newConfig });
|
||||
ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration>(newConfig));
|
||||
|
||||
base.ReplaceConfiguration(newConfiguration);
|
||||
}
|
||||
@@ -109,7 +109,6 @@ namespace Emby.Server.Implementations.Configuration
|
||||
if (!string.IsNullOrWhiteSpace(newPath)
|
||||
&& !string.Equals(Configuration.CertificatePath, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
// Validate
|
||||
if (!File.Exists(newPath))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
@@ -133,7 +132,6 @@ namespace Emby.Server.Implementations.Configuration
|
||||
if (!string.IsNullOrWhiteSpace(newPath)
|
||||
&& !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
// Validate
|
||||
if (!Directory.Exists(newPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException(
|
||||
@@ -146,60 +144,5 @@ namespace Emby.Server.Implementations.Configuration
|
||||
EnsureWriteAccess(newPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all configuration values to their optimal values.
|
||||
/// </summary>
|
||||
/// <returns>If the configuration changed.</returns>
|
||||
public bool SetOptimalValues()
|
||||
{
|
||||
var config = Configuration;
|
||||
|
||||
var changed = false;
|
||||
|
||||
if (!config.EnableCaseSensitiveItemIds)
|
||||
{
|
||||
config.EnableCaseSensitiveItemIds = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.SkipDeserializationForBasicTypes)
|
||||
{
|
||||
config.SkipDeserializationForBasicTypes = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.EnableSimpleArtistDetection)
|
||||
{
|
||||
config.EnableSimpleArtistDetection = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.EnableNormalizedItemByNameIds)
|
||||
{
|
||||
config.EnableNormalizedItemByNameIds = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.DisableLiveTvChannelUserDataName)
|
||||
{
|
||||
config.DisableLiveTvChannelUserDataName = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.EnableNewOmdbSupport)
|
||||
{
|
||||
config.EnableNewOmdbSupport = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.CollectionsUpgraded)
|
||||
{
|
||||
config.CollectionsUpgraded = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
{ HostWebClientKey, bool.TrueString },
|
||||
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
||||
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
@@ -129,8 +131,6 @@ namespace Emby.Server.Implementations.Cryptography
|
||||
_randomNumberGenerator.Dispose();
|
||||
}
|
||||
|
||||
_randomNumberGenerator = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
protected BaseSqliteRepository(ILogger logger)
|
||||
protected BaseSqliteRepository(ILogger<BaseSqliteRepository> logger)
|
||||
{
|
||||
Logger = logger;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
protected ILogger Logger { get; }
|
||||
protected ILogger<BaseSqliteRepository> Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default connection flags.
|
||||
@@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}, ReadTransactionMode);
|
||||
}
|
||||
|
||||
@@ -248,12 +247,12 @@ namespace Emby.Server.Implementations.Data
|
||||
public enum SynchronousMode
|
||||
{
|
||||
/// <summary>
|
||||
/// SQLite continues without syncing as soon as it has handed data off to the operating system
|
||||
/// SQLite continues without syncing as soon as it has handed data off to the operating system.
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
|
||||
/// <summary>
|
||||
/// SQLite database engine will still sync at the most critical moments
|
||||
/// SQLite database engine will still sync at the most critical moments.
|
||||
/// </summary>
|
||||
Normal = 1,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Data
|
||||
public class CleanDatabaseScheduledTask : ILibraryPostScanTask
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<CleanDatabaseScheduledTask> _logger;
|
||||
|
||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
|
||||
{
|
||||
@@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Data
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// Opens the connection to the database.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private void InitializeInternal()
|
||||
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the display preferences associated with an item in the repo
|
||||
/// Save the display preferences associated with an item in the repo.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
@@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save all display preferences associated with a user in the repo
|
||||
/// Save all display preferences associated with a user in the repo.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -134,10 +135,12 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userData));
|
||||
}
|
||||
|
||||
if (internalUserId <= 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(internalUserId));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
@@ -152,6 +155,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userData));
|
||||
}
|
||||
|
||||
if (internalUserId <= 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(internalUserId));
|
||||
@@ -234,7 +238,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persist all user data for the specified user
|
||||
/// Persist all user data for the specified user.
|
||||
/// </summary>
|
||||
private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -308,7 +312,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return all user-data associated with the given user
|
||||
/// Return all user-data associated with the given user.
|
||||
/// </summary>
|
||||
/// <param name="internalUserId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -338,7 +342,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a row from the specified reader into the provided userData object
|
||||
/// Read a row from the specified reader into the provided userData object.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
|
||||
@@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var userData = new UserItemData();
|
||||
|
||||
userData.Key = reader[0].ToString();
|
||||
//userData.UserId = reader[1].ReadGuidFromBlob();
|
||||
// userData.UserId = reader[1].ReadGuidFromBlob();
|
||||
|
||||
if (reader[2].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteUserRepository
|
||||
/// </summary>
|
||||
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public SqliteUserRepository(
|
||||
ILogger<SqliteUserRepository> logger,
|
||||
IServerApplicationPaths appPaths)
|
||||
: base(logger)
|
||||
{
|
||||
_jsonOptions = JsonDefaults.GetOptions();
|
||||
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name => "SQLite";
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
var localUsersTableExists = TableExists(connection, "LocalUsersv2");
|
||||
|
||||
connection.RunQueries(new[] {
|
||||
"create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)",
|
||||
"drop index if exists idx_users"
|
||||
});
|
||||
|
||||
if (!localUsersTableExists && TableExists(connection, "Users"))
|
||||
{
|
||||
TryMigrateToLocalUsersTable(connection);
|
||||
}
|
||||
|
||||
RemoveEmptyPasswordHashes(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryMigrateToLocalUsersTable(ManagedConnection connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.RunQueries(new[]
|
||||
{
|
||||
"INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users"
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error migrating users database");
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveEmptyPasswordHashes(ManagedConnection connection)
|
||||
{
|
||||
foreach (var user in RetrieveAllUsers(connection))
|
||||
{
|
||||
// If the user password is the sha1 hash of the empty string, remove it
|
||||
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
|
||||
&& !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
user.Password = null;
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
||||
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
||||
{
|
||||
statement.TryBind("@InternalId", user.InternalId);
|
||||
statement.TryBind("@data", serialized);
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a user in the repo
|
||||
/// </summary>
|
||||
public void CreateUser(User user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
|
||||
{
|
||||
statement.TryBind("@guid", user.Id.ToByteArray());
|
||||
statement.TryBind("@data", serialized);
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
var createdUser = GetUser(user.Id, connection);
|
||||
|
||||
if (createdUser == null)
|
||||
{
|
||||
throw new ApplicationException("created user should never be null");
|
||||
}
|
||||
|
||||
user.InternalId = createdUser.InternalId;
|
||||
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateUser(User user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
||||
{
|
||||
statement.TryBind("@InternalId", user.InternalId);
|
||||
statement.TryBind("@data", serialized);
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private User GetUser(Guid guid, ManagedConnection connection)
|
||||
{
|
||||
using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
|
||||
{
|
||||
statement.TryBind("@guid", guid);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
return GetUser(row);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private User GetUser(IReadOnlyList<IResultSetValue> row)
|
||||
{
|
||||
var id = row[0].ToInt64();
|
||||
var guid = row[1].ReadGuidFromBlob();
|
||||
|
||||
var user = JsonSerializer.Deserialize<User>(row[2].ToBlob(), _jsonOptions);
|
||||
user.InternalId = id;
|
||||
user.Id = guid;
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all users from the database
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{User}.</returns>
|
||||
public List<User> RetrieveAllUsers()
|
||||
{
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
return new List<User>(RetrieveAllUsers(connection));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all users from the database
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{User}.</returns>
|
||||
private IEnumerable<User> RetrieveAllUsers(ManagedConnection connection)
|
||||
{
|
||||
foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
|
||||
{
|
||||
yield return GetUser(row);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="ArgumentNullException">user</exception>
|
||||
public void DeleteUser(User user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
|
||||
{
|
||||
statement.TryBind("@id", user.InternalId);
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
public class DeviceId
|
||||
{
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<DeviceId> _logger;
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_appPaths = appPaths;
|
||||
_logger = loggerFactory.CreateLogger("SystemId");
|
||||
_logger = loggerFactory.CreateLogger<DeviceId>();
|
||||
}
|
||||
|
||||
public string Value => _id ?? (_id = GetDeviceId());
|
||||
|
||||
@@ -5,10 +5,11 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Model.Devices;
|
||||
@@ -16,7 +17,6 @@ using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Session;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
@@ -27,11 +27,10 @@ namespace Emby.Server.Implementations.Devices
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IAuthenticationRepository _authRepo;
|
||||
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
|
||||
private readonly object _capabilitiesSyncLock = new object();
|
||||
|
||||
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
|
||||
|
||||
private readonly object _capabilitiesSyncLock = new object();
|
||||
|
||||
public DeviceManager(
|
||||
IAuthenticationRepository authRepo,
|
||||
IJsonSerializer json,
|
||||
@@ -62,13 +61,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
_authRepo.UpdateDeviceOptions(deviceId, options);
|
||||
|
||||
if (DeviceOptionsUpdated != null)
|
||||
{
|
||||
DeviceOptionsUpdated(this, new GenericEventArgs<Tuple<string, DeviceOptions>>()
|
||||
{
|
||||
Argument = new Tuple<string, DeviceOptions>(deviceId, options)
|
||||
});
|
||||
}
|
||||
DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options)));
|
||||
}
|
||||
|
||||
public DeviceOptions GetDeviceOptions(string deviceId)
|
||||
@@ -119,7 +112,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
|
||||
{
|
||||
//UserId = query.UserId
|
||||
// UserId = query.UserId
|
||||
HasUser = true
|
||||
}).Items;
|
||||
|
||||
@@ -176,12 +169,18 @@ namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
throw new ArgumentException("user not found");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(deviceId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deviceId));
|
||||
}
|
||||
|
||||
if (!CanAccessDevice(user.Policy, deviceId))
|
||||
if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var capabilities = GetCapabilities(deviceId);
|
||||
|
||||
@@ -193,20 +192,5 @@ namespace Emby.Server.Implementations.Devices
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CanAccessDevice(UserPolicy policy, string id)
|
||||
{
|
||||
if (policy.EnableAllDevices)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (policy.IsAdministrator)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
@@ -24,12 +24,20 @@ using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Book = MediaBrowser.Controller.Entities.Book;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
||||
using Person = MediaBrowser.Controller.Entities.Person;
|
||||
using Photo = MediaBrowser.Controller.Entities.Photo;
|
||||
using Season = MediaBrowser.Controller.Entities.TV.Season;
|
||||
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
||||
|
||||
namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
public class DtoService : IDtoService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<DtoService> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserDataManager _userDataRepository;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
@@ -66,7 +74,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a BaseItem to a DTOBaseItem
|
||||
/// Converts a BaseItem to a DTOBaseItem.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="fields">The fields.</param>
|
||||
@@ -269,6 +277,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.EpisodeTitle = dto.Name;
|
||||
dto.Name = dto.SeriesName;
|
||||
}
|
||||
|
||||
liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
|
||||
}
|
||||
|
||||
@@ -284,6 +293,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var containers = container.Split(new[] { ',' });
|
||||
if (containers.Length < 2)
|
||||
{
|
||||
@@ -384,7 +394,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (options.ContainsField(ItemFields.ChildCount))
|
||||
{
|
||||
dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user);
|
||||
dto.ChildCount ??= GetChildCount(folder, user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +408,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.DateLastMediaAdded = folder.DateLastMediaAdded;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (options.EnableUserData)
|
||||
@@ -414,7 +423,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (options.ContainsField(ItemFields.BasicSyncInfo))
|
||||
{
|
||||
var userCanSync = user != null && user.Policy.EnableContentDownloading;
|
||||
var userCanSync = user != null && user.HasPermission(PermissionKind.EnableContentDownloading);
|
||||
if (userCanSync && item.SupportsExternalTransfer)
|
||||
{
|
||||
dto.SupportsSync = true;
|
||||
@@ -435,7 +444,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets client-side Id of a server-side BaseItem
|
||||
/// Gets client-side Id of a server-side BaseItem.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
@@ -449,6 +458,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
dto.SeriesName = item.SeriesName;
|
||||
}
|
||||
|
||||
private static void SetPhotoProperties(BaseItemDto dto, Photo item)
|
||||
{
|
||||
dto.CameraMake = item.CameraMake;
|
||||
@@ -530,7 +540,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches People DTO's to a DTOBaseItem
|
||||
/// Attaches People DTO's to a DTOBaseItem.
|
||||
/// </summary>
|
||||
/// <param name="dto">The dto.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
@@ -547,22 +557,27 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (i.IsType(PersonType.GuestStar))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (i.IsType(PersonType.Director))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (i.IsType(PersonType.Writer))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (i.IsType(PersonType.Producer))
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (i.IsType(PersonType.Composer))
|
||||
{
|
||||
return 4;
|
||||
@@ -586,7 +601,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
_logger.LogError(ex, "Error getting person {Name}", c);
|
||||
return null;
|
||||
}
|
||||
|
||||
}).Where(i => i != null)
|
||||
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(x => x.First())
|
||||
@@ -605,8 +619,9 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (dictionary.TryGetValue(person.Name, out Person entity))
|
||||
{
|
||||
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
|
||||
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
|
||||
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
baseItemPerson.ImageBlurHashes = dto.ImageBlurHashes;
|
||||
list.Add(baseItemPerson);
|
||||
}
|
||||
}
|
||||
@@ -654,8 +669,72 @@ namespace Emby.Server.Implementations.Dto
|
||||
return _libraryManager.GetGenreId(name);
|
||||
}
|
||||
|
||||
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
|
||||
{
|
||||
var image = item.GetImageInfo(imageType, imageIndex);
|
||||
if (image != null)
|
||||
{
|
||||
return GetTagAndFillBlurhash(dto, item, image);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
|
||||
{
|
||||
var tag = GetImageCacheTag(item, image);
|
||||
if (!string.IsNullOrEmpty(image.BlurHash))
|
||||
{
|
||||
if (dto.ImageBlurHashes == null)
|
||||
{
|
||||
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||
}
|
||||
|
||||
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
|
||||
{
|
||||
dto.ImageBlurHashes[image.Type] = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
dto.ImageBlurHashes[image.Type][tag] = image.BlurHash;
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
|
||||
{
|
||||
return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
|
||||
}
|
||||
|
||||
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInfo> images)
|
||||
{
|
||||
var tags = GetImageTags(item, images);
|
||||
var hashes = new Dictionary<string, string>();
|
||||
for (int i = 0; i < images.Count; i++)
|
||||
{
|
||||
var img = images[i];
|
||||
if (!string.IsNullOrEmpty(img.BlurHash))
|
||||
{
|
||||
var tag = tags[i];
|
||||
hashes[tag] = img.BlurHash;
|
||||
}
|
||||
}
|
||||
|
||||
if (hashes.Count > 0)
|
||||
{
|
||||
if (dto.ImageBlurHashes == null)
|
||||
{
|
||||
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||
}
|
||||
|
||||
dto.ImageBlurHashes[imageType] = hashes;
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets simple property values on a DTOBaseItem
|
||||
/// Sets simple property values on a DTOBaseItem.
|
||||
/// </summary>
|
||||
/// <param name="dto">The dto.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
@@ -674,8 +753,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.LockData = item.IsLocked;
|
||||
dto.ForcedSortName = item.ForcedSortName;
|
||||
}
|
||||
dto.Container = item.Container;
|
||||
|
||||
dto.Container = item.Container;
|
||||
dto.EndDate = item.EndDate;
|
||||
|
||||
if (options.ContainsField(ItemFields.ExternalUrls))
|
||||
@@ -694,10 +773,12 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.AspectRatio = hasAspectRatio.AspectRatio;
|
||||
}
|
||||
|
||||
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||
|
||||
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
||||
if (backdropLimit > 0)
|
||||
{
|
||||
dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
|
||||
dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
|
||||
}
|
||||
|
||||
if (options.ContainsField(ItemFields.ScreenshotImageTags))
|
||||
@@ -705,7 +786,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
|
||||
if (screenshotLimit > 0)
|
||||
{
|
||||
dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
|
||||
dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,12 +802,11 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
// Prevent implicitly captured closure
|
||||
var currentItem = item;
|
||||
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type))
|
||||
.ToList())
|
||||
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
|
||||
{
|
||||
if (options.GetImageLimit(image.Type) > 0)
|
||||
{
|
||||
var tag = GetImageCacheTag(item, image);
|
||||
var tag = GetTagAndFillBlurhash(dto, item, image);
|
||||
|
||||
if (tag != null)
|
||||
{
|
||||
@@ -871,11 +951,10 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (albumParent != null)
|
||||
{
|
||||
dto.AlbumId = albumParent.Id;
|
||||
|
||||
dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
|
||||
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
|
||||
}
|
||||
|
||||
//if (options.ContainsField(ItemFields.MediaSourceCount))
|
||||
// if (options.ContainsField(ItemFields.MediaSourceCount))
|
||||
//{
|
||||
// Songs always have one
|
||||
//}
|
||||
@@ -885,13 +964,13 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
dto.Artists = hasArtist.Artists;
|
||||
|
||||
//var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
||||
// var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
||||
//{
|
||||
// EnableTotalRecordCount = false,
|
||||
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
||||
//});
|
||||
|
||||
//dto.ArtistItems = artistItems.Items
|
||||
// dto.ArtistItems = artistItems.Items
|
||||
// .Select(i =>
|
||||
// {
|
||||
// var artist = i.Item1;
|
||||
@@ -904,7 +983,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
// .ToList();
|
||||
|
||||
// Include artists that are not in the database yet, e.g., just added via metadata editor
|
||||
//var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
|
||||
// var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
|
||||
dto.ArtistItems = hasArtist.Artists
|
||||
//.Except(foundArtists, new DistinctNameComparer())
|
||||
.Select(i =>
|
||||
@@ -929,7 +1008,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}).Where(i => i != null).ToArray();
|
||||
}
|
||||
|
||||
@@ -938,13 +1016,13 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||
|
||||
//var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
||||
// var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
||||
//{
|
||||
// EnableTotalRecordCount = false,
|
||||
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
||||
//});
|
||||
|
||||
//dto.AlbumArtists = artistItems.Items
|
||||
// dto.AlbumArtists = artistItems.Items
|
||||
// .Select(i =>
|
||||
// {
|
||||
// var artist = i.Item1;
|
||||
@@ -980,7 +1058,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}).Where(i => i != null).ToArray();
|
||||
}
|
||||
|
||||
@@ -1094,12 +1171,12 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
// this block will add the series poster for episodes without a poster
|
||||
// TODO maybe remove the if statement entirely
|
||||
//if (options.ContainsField(ItemFields.SeriesPrimaryImage))
|
||||
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
|
||||
{
|
||||
episodeSeries = episodeSeries ?? episode.Series;
|
||||
if (episodeSeries != null)
|
||||
{
|
||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
|
||||
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1140,12 +1217,12 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
// this block will add the series poster for seasons without a poster
|
||||
// TODO maybe remove the if statement entirely
|
||||
//if (options.ContainsField(ItemFields.SeriesPrimaryImage))
|
||||
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
|
||||
{
|
||||
series = series ?? season.Series;
|
||||
if (series != null)
|
||||
{
|
||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
|
||||
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1275,9 +1352,10 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentLogoItemId = GetDtoId(parent);
|
||||
dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
|
||||
dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||
}
|
||||
}
|
||||
|
||||
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
|
||||
{
|
||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
|
||||
@@ -1285,9 +1363,10 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentArtItemId = GetDtoId(parent);
|
||||
dto.ParentArtImageTag = GetImageCacheTag(parent, image);
|
||||
dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||
}
|
||||
}
|
||||
|
||||
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
|
||||
{
|
||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
|
||||
@@ -1295,9 +1374,10 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentThumbItemId = GetDtoId(parent);
|
||||
dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
|
||||
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||
}
|
||||
}
|
||||
|
||||
if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0)))
|
||||
{
|
||||
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
|
||||
@@ -1305,7 +1385,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (images.Count > 0)
|
||||
{
|
||||
dto.ParentBackdropItemId = GetDtoId(parent);
|
||||
dto.ParentBackdropImageTags = GetImageTags(parent, images);
|
||||
dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IPNetwork2" Version="2.4.0.126" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" />
|
||||
<PackageReference Include="IPNetwork2" Version="2.5.211" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.0-pre1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
|
||||
@@ -34,14 +34,14 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.1" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.25.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.25.1" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="DotNet.Glob" Version="3.0.9" />
|
||||
</ItemGroup>
|
||||
@@ -54,6 +54,7 @@
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
public class ExternalPortForwarding : IServerEntryPoint
|
||||
{
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<ExternalPortForwarding> _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@@ -28,7 +29,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<LibraryChangedNotifier> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The library changed sync lock.
|
||||
@@ -131,7 +132,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,7 +302,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
|
||||
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None);
|
||||
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (LibraryUpdateTimer != null)
|
||||
{
|
||||
@@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// <param name="foldersAddedTo">The folders added to.</param>
|
||||
/// <param name="foldersRemovedFrom">The folders removed from.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
private async void SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
|
||||
private async Task SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
|
||||
{
|
||||
var userIds = _sessionManager.Sessions
|
||||
.Select(i => i.UserId)
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
@@ -17,7 +18,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<RecordingNotifier> _logger;
|
||||
|
||||
public RecordingNotifier(
|
||||
ISessionManager sessionManager,
|
||||
@@ -42,29 +43,29 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("SeriesTimerCreated", e.Argument);
|
||||
await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("TimerCreated", e.Argument);
|
||||
await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("SeriesTimerCancelled", e.Argument);
|
||||
await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("TimerCancelled", e.Argument);
|
||||
await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void SendMessage(string name, TimerEventInfo info)
|
||||
private async Task SendMessage(string name, TimerEventInfo info)
|
||||
{
|
||||
var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList();
|
||||
var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Class RefreshUsersMetadata.
|
||||
/// </summary>
|
||||
public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The user manager.
|
||||
/// </summary>
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
|
||||
/// </summary>
|
||||
public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Refresh Users";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "RefreshUsers";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "Refresh user infos";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => "Library";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsHidden => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLogged => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
foreach (var user in _userManager.Users)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
IntervalTicks = TimeSpan.FromDays(1).Ticks,
|
||||
Type = TaskTriggerInfo.TriggerInterval
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,16 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Updates;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
@@ -67,10 +68,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_userManager.UserDeleted += OnUserDeleted;
|
||||
_userManager.UserUpdated += OnUserUpdated;
|
||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
||||
_userManager.UserConfigurationUpdated += OnUserConfigurationUpdated;
|
||||
_userManager.OnUserDeleted += OnUserDeleted;
|
||||
_userManager.OnUserUpdated += OnUserUpdated;
|
||||
|
||||
_appHost.HasPendingRestartChanged += OnHasPendingRestartChanged;
|
||||
|
||||
@@ -85,29 +84,29 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OnPackageInstalling(object sender, InstallationEventArgs e)
|
||||
private async void OnPackageInstalling(object sender, InstallationInfo e)
|
||||
{
|
||||
SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo);
|
||||
await SendMessageToAdminSessions("PackageInstalling", e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnPackageInstallationCancelled(object sender, InstallationEventArgs e)
|
||||
private async void OnPackageInstallationCancelled(object sender, InstallationInfo e)
|
||||
{
|
||||
SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo);
|
||||
await SendMessageToAdminSessions("PackageInstallationCancelled", e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnPackageInstallationCompleted(object sender, InstallationEventArgs e)
|
||||
private async void OnPackageInstallationCompleted(object sender, InstallationInfo e)
|
||||
{
|
||||
SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo);
|
||||
await SendMessageToAdminSessions("PackageInstallationCompleted", e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||
private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||
{
|
||||
SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo);
|
||||
await SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
{
|
||||
SendMessageToAdminSessions("ScheduledTaskEnded", e.Result);
|
||||
await SendMessageToAdminSessions("ScheduledTaskEnded", e.Result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -115,9 +114,9 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The e.</param>
|
||||
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||
private async void OnPluginUninstalled(object sender, IPlugin e)
|
||||
{
|
||||
SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo());
|
||||
await SendMessageToAdminSessions("PluginUninstalled", e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -125,9 +124,9 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
||||
private void OnHasPendingRestartChanged(object sender, EventArgs e)
|
||||
private async void OnHasPendingRestartChanged(object sender, EventArgs e)
|
||||
{
|
||||
_sessionManager.SendRestartRequiredNotification(CancellationToken.None);
|
||||
await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,11 +134,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The e.</param>
|
||||
private void OnUserUpdated(object sender, GenericEventArgs<User> e)
|
||||
private async void OnUserUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
var dto = _userManager.GetUserDto(e.Argument);
|
||||
|
||||
SendMessageToUserSession(e.Argument, "UserUpdated", dto);
|
||||
await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,26 +146,12 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The e.</param>
|
||||
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
var dto = _userManager.GetUserDto(e.Argument);
|
||||
|
||||
SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto);
|
||||
}
|
||||
|
||||
private void OnUserConfigurationUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
var dto = _userManager.GetUserDto(e.Argument);
|
||||
|
||||
SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto);
|
||||
}
|
||||
|
||||
private async void SendMessageToAdminSessions<T>(string name, T data)
|
||||
private async Task SendMessageToAdminSessions<T>(string name, T data)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -174,11 +159,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async void SendMessageToUserSession<T>(User user, string name, T data)
|
||||
private async Task SendMessageToUserSession<T>(User user, string name, T data)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -190,7 +174,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,10 +192,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
_userManager.UserDeleted -= OnUserDeleted;
|
||||
_userManager.UserUpdated -= OnUserUpdated;
|
||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
||||
_userManager.UserConfigurationUpdated -= OnUserConfigurationUpdated;
|
||||
_userManager.OnUserDeleted -= OnUserDeleted;
|
||||
_userManager.OnUserUpdated -= OnUserUpdated;
|
||||
|
||||
_installationManager.PluginUninstalled -= OnPluginUninstalled;
|
||||
_installationManager.PackageInstalling -= OnPackageInstalling;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Udp;
|
||||
@@ -21,7 +22,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// <summary>
|
||||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<UdpServerEntryPoint> _logger;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
@@ -43,14 +44,22 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_logger = logger;
|
||||
_appHost = appHost;
|
||||
_config = configuration;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RunAsync()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_udpServer = new UdpServer(_logger, _appHost, _config);
|
||||
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
|
||||
try
|
||||
{
|
||||
_udpServer = new UdpServer(_logger, _appHost, _config);
|
||||
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Unable to start AutoDiscovery listener on UDP port {PortNumber}", PortNumber);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
/// </summary>
|
||||
public class HttpClientManager : IHttpClient
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<HttpClientManager> _logger;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IApplicationHost _appHost;
|
||||
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
=> SendAsync(options, HttpMethod.Get);
|
||||
|
||||
/// <summary>
|
||||
/// Performs a GET request and returns the resulting stream
|
||||
/// Performs a GET request and returns the resulting stream.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>Task{Stream}.</returns>
|
||||
|
||||
@@ -32,12 +32,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The _options
|
||||
/// The _options.
|
||||
/// </summary>
|
||||
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// The _requested ranges
|
||||
/// The _requested ranges.
|
||||
/// </summary>
|
||||
private List<KeyValuePair<long, long?>> _requestedRanges;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// </summary>
|
||||
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<HttpListenerHost> _logger;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly INetworkManager _networkManager;
|
||||
@@ -230,7 +230,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
httpRes.StatusCode = statusCode;
|
||||
|
||||
var errContent = NormalizeExceptionMessage(ex) ?? string.Empty;
|
||||
var errContent = _hostEnvironment.IsDevelopment()
|
||||
? (NormalizeExceptionMessage(ex) ?? string.Empty)
|
||||
: "Error processing request.";
|
||||
httpRes.ContentType = "text/plain";
|
||||
httpRes.ContentLength = errContent.Length;
|
||||
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
|
||||
@@ -397,7 +399,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
var response = context.Response;
|
||||
var localPath = context.Request.Path.ToString();
|
||||
|
||||
var req = new WebSocketSharpRequest(request, response, request.Path, _logger);
|
||||
var req = new WebSocketSharpRequest(request, response, request.Path);
|
||||
return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted);
|
||||
}
|
||||
|
||||
@@ -451,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
httpRes.Headers.Add(key, value);
|
||||
}
|
||||
|
||||
httpRes.ContentType = "text/plain";
|
||||
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
@@ -589,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the default CORS headers
|
||||
/// Get the default CORS headers.
|
||||
/// </summary>
|
||||
/// <param name="req"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <summary>
|
||||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<HttpResultFactory> _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
@@ -50,12 +50,13 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
_fileSystem = fileSystem;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_streamHelper = streamHelper;
|
||||
_logger = loggerfactory.CreateLogger("HttpResultFactory");
|
||||
_logger = loggerfactory.CreateLogger<HttpResultFactory>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result.
|
||||
/// </summary>
|
||||
/// <param name="requestContext">The request context.</param>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="responseHeaders">The response headers.</param>
|
||||
@@ -255,16 +256,20 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(acceptEncoding))
|
||||
if (!string.IsNullOrEmpty(acceptEncoding))
|
||||
{
|
||||
//if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
// if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
// return "br";
|
||||
|
||||
if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (acceptEncoding.Contains("deflate", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "deflate";
|
||||
}
|
||||
|
||||
if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "gzip";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -421,7 +426,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// </summary>
|
||||
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
|
||||
{
|
||||
bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
bool noCache = requestContext.Headers[HeaderNames.CacheControl].ToString().IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
|
||||
|
||||
if (!noCache)
|
||||
@@ -575,13 +580,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue)
|
||||
{
|
||||
var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest, _logger)
|
||||
var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest)
|
||||
{
|
||||
OnComplete = options.OnComplete
|
||||
};
|
||||
@@ -618,8 +622,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <summary>
|
||||
/// Adds the caching responseHeaders.
|
||||
/// </summary>
|
||||
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration,
|
||||
bool noCache, DateTime? lastModifiedDate)
|
||||
private void AddCachingHeaders(
|
||||
IDictionary<string, string> responseHeaders,
|
||||
TimeSpan? cacheDuration,
|
||||
bool noCache,
|
||||
DateTime? lastModifiedDate)
|
||||
{
|
||||
if (noCache)
|
||||
{
|
||||
@@ -688,7 +695,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
|
||||
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that.
|
||||
/// </summary>
|
||||
/// <param name="date">The date.</param>
|
||||
/// <returns>DateTime.</returns>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -8,13 +9,45 @@ using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult
|
||||
{
|
||||
private const int BufferSize = 81920;
|
||||
|
||||
private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
|
||||
|
||||
private List<KeyValuePair<long, long?>> _requestedRanges;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="rangeHeader">The range header.</param>
|
||||
/// <param name="contentLength">The content length.</param>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
|
||||
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentType));
|
||||
}
|
||||
|
||||
RangeHeader = rangeHeader;
|
||||
SourceStream = source;
|
||||
IsHeadRequest = isHeadRequest;
|
||||
|
||||
ContentType = contentType;
|
||||
Headers[HeaderNames.ContentType] = contentType;
|
||||
Headers[HeaderNames.AcceptRanges] = "bytes";
|
||||
StatusCode = HttpStatusCode.PartialContent;
|
||||
|
||||
SetRangeValues(contentLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source stream.
|
||||
/// </summary>
|
||||
@@ -29,19 +62,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private long TotalContentLength { get; set; }
|
||||
|
||||
public Action OnComplete { get; set; }
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private const int BufferSize = 81920;
|
||||
|
||||
/// <summary>
|
||||
/// The _options
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// The us culture
|
||||
/// </summary>
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// Additional HTTP Headers
|
||||
@@ -50,32 +70,57 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
public IDictionary<string, string> Headers => _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
|
||||
/// Gets the requested ranges.
|
||||
/// </summary>
|
||||
/// <param name="rangeHeader">The range header.</param>
|
||||
/// <param name="contentLength">The content length.</param>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
|
||||
/// <param name="logger">The logger instance.</param>
|
||||
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
|
||||
/// <value>The requested ranges.</value>
|
||||
protected List<KeyValuePair<long, long?>> RequestedRanges
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
get
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentType));
|
||||
if (_requestedRanges == null)
|
||||
{
|
||||
_requestedRanges = new List<KeyValuePair<long, long?>>();
|
||||
|
||||
// Example: bytes=0-,32-63
|
||||
var ranges = RangeHeader.Split('=')[1].Split(',');
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var vals = range.Split('-');
|
||||
|
||||
long start = 0;
|
||||
long? end = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[0]))
|
||||
{
|
||||
start = long.Parse(vals[0], CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[1]))
|
||||
{
|
||||
end = long.Parse(vals[1], CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
return _requestedRanges;
|
||||
}
|
||||
}
|
||||
|
||||
RangeHeader = rangeHeader;
|
||||
SourceStream = source;
|
||||
IsHeadRequest = isHeadRequest;
|
||||
this._logger = logger;
|
||||
public string ContentType { get; set; }
|
||||
|
||||
ContentType = contentType;
|
||||
Headers[HeaderNames.ContentType] = contentType;
|
||||
Headers[HeaderNames.AcceptRanges] = "bytes";
|
||||
StatusCode = HttpStatusCode.PartialContent;
|
||||
public IRequest RequestContext { get; set; }
|
||||
|
||||
SetRangeValues(contentLength);
|
||||
public object Response { get; set; }
|
||||
|
||||
public int Status { get; set; }
|
||||
|
||||
public HttpStatusCode StatusCode
|
||||
{
|
||||
get => (HttpStatusCode)Status;
|
||||
set => Status = (int)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,49 +154,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _requested ranges
|
||||
/// </summary>
|
||||
private List<KeyValuePair<long, long?>> _requestedRanges;
|
||||
/// <summary>
|
||||
/// Gets the requested ranges.
|
||||
/// </summary>
|
||||
/// <value>The requested ranges.</value>
|
||||
protected List<KeyValuePair<long, long?>> RequestedRanges
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_requestedRanges == null)
|
||||
{
|
||||
_requestedRanges = new List<KeyValuePair<long, long?>>();
|
||||
|
||||
// Example: bytes=0-,32-63
|
||||
var ranges = RangeHeader.Split('=')[1].Split(',');
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var vals = range.Split('-');
|
||||
|
||||
long start = 0;
|
||||
long? end = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[0]))
|
||||
{
|
||||
start = long.Parse(vals[0], UsCulture);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(vals[1]))
|
||||
{
|
||||
end = long.Parse(vals[1], UsCulture);
|
||||
}
|
||||
|
||||
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
return _requestedRanges;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
@@ -167,59 +169,44 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
// If the requested range is "0-", we can optimize by just doing a stream copy
|
||||
if (RangeEnd >= TotalContentLength - 1)
|
||||
{
|
||||
await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
|
||||
await source.CopyToAsync(responseStream, BufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
|
||||
await CopyToInternalAsync(source, responseStream, RangeLength, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (OnComplete != null)
|
||||
{
|
||||
OnComplete();
|
||||
}
|
||||
OnComplete?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
|
||||
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||
{
|
||||
var array = new byte[BufferSize];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
|
||||
var array = ArrayPool<byte>.Shared.Rent(BufferSize);
|
||||
try
|
||||
{
|
||||
if (bytesRead == 0)
|
||||
int bytesRead;
|
||||
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var bytesToCopy = Math.Min(bytesRead, copyLength);
|
||||
|
||||
var bytesToCopy = Math.Min(bytesRead, copyLength);
|
||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
|
||||
copyLength -= bytesToCopy;
|
||||
|
||||
copyLength -= bytesToCopy;
|
||||
|
||||
if (copyLength <= 0)
|
||||
{
|
||||
break;
|
||||
if (copyLength <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public IRequest RequestContext { get; set; }
|
||||
|
||||
public object Response { get; set; }
|
||||
|
||||
public int Status { get; set; }
|
||||
|
||||
public HttpStatusCode StatusCode
|
||||
{
|
||||
get => (HttpStatusCode)Status;
|
||||
set => Status = (int)value;
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using MediaBrowser.Controller.Net;
|
||||
@@ -42,11 +41,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
res.Headers.Add(key, value);
|
||||
}
|
||||
// Try to prevent compatibility view
|
||||
res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " +
|
||||
res.Headers["Access-Control-Allow-Headers"] = "Accept, Accept-Language, Authorization, Cache-Control, " +
|
||||
"Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " +
|
||||
"Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " +
|
||||
"Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " +
|
||||
"X-Emby-Authorization");
|
||||
"X-Emby-Authorization";
|
||||
|
||||
if (dto is Exception exception)
|
||||
{
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Authentication;
|
||||
using Emby.Server.Implementations.SocketSharp;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
@@ -45,11 +46,27 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
|
||||
{
|
||||
var req = new WebSocketSharpRequest(request, null, request.Path, _logger);
|
||||
var req = new WebSocketSharpRequest(request, null, request.Path);
|
||||
var user = ValidateUser(req, authAttributes);
|
||||
return user;
|
||||
}
|
||||
|
||||
public AuthorizationInfo Authenticate(HttpRequest request)
|
||||
{
|
||||
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||
if (auth?.User == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (auth.User.HasPermission(PermissionKind.IsDisabled))
|
||||
{
|
||||
throw new SecurityException("User account has been disabled.");
|
||||
}
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||
{
|
||||
// This code is executed before the service
|
||||
@@ -90,7 +107,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
!string.IsNullOrEmpty(auth.Client) &&
|
||||
!string.IsNullOrEmpty(auth.Device))
|
||||
{
|
||||
_sessionManager.LogSessionActivity(auth.Client,
|
||||
_sessionManager.LogSessionActivity(
|
||||
auth.Client,
|
||||
auth.Version,
|
||||
auth.DeviceId,
|
||||
auth.Device,
|
||||
@@ -104,21 +122,21 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
private void ValidateUserAccess(
|
||||
User user,
|
||||
IRequest request,
|
||||
IAuthenticationAttributes authAttribtues,
|
||||
IAuthenticationAttributes authAttributes,
|
||||
AuthorizationInfo auth)
|
||||
{
|
||||
if (user.Policy.IsDisabled)
|
||||
if (user.HasPermission(PermissionKind.IsDisabled))
|
||||
{
|
||||
throw new SecurityException("User account has been disabled.");
|
||||
}
|
||||
|
||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
||||
if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
||||
{
|
||||
throw new SecurityException("User account has been disabled.");
|
||||
}
|
||||
|
||||
if (!user.Policy.IsAdministrator
|
||||
&& !authAttribtues.EscapeParentalControl
|
||||
if (!user.HasPermission(PermissionKind.IsAdministrator)
|
||||
&& !authAttributes.EscapeParentalControl
|
||||
&& !user.IsParentalScheduleAllowed())
|
||||
{
|
||||
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
|
||||
@@ -138,6 +156,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (authAttribtues.AllowLocalOnly && request.IsLocal)
|
||||
{
|
||||
return true;
|
||||
@@ -180,7 +199,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
{
|
||||
if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user == null || !user.Policy.IsAdministrator)
|
||||
if (user == null || !user.HasPermission(PermissionKind.IsAdministrator))
|
||||
{
|
||||
throw new SecurityException("User does not have admin access.");
|
||||
}
|
||||
@@ -188,7 +207,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user == null || !user.Policy.EnableContentDeletion)
|
||||
if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion))
|
||||
{
|
||||
throw new SecurityException("User does not have delete access.");
|
||||
}
|
||||
@@ -196,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user == null || !user.Policy.EnableContentDownloading)
|
||||
if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading))
|
||||
{
|
||||
throw new SecurityException("User does not have download access.");
|
||||
}
|
||||
@@ -223,7 +242,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
throw new AuthenticationException("Access token is invalid or expired.");
|
||||
}
|
||||
|
||||
//if (!string.IsNullOrEmpty(info.UserId))
|
||||
// if (!string.IsNullOrEmpty(info.UserId))
|
||||
//{
|
||||
// var user = _userManager.GetUserById(info.UserId);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer.Security
|
||||
@@ -38,6 +39,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
return GetAuthorization(requestContext);
|
||||
}
|
||||
|
||||
public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext)
|
||||
{
|
||||
var auth = GetAuthorizationDictionary(requestContext);
|
||||
var (authInfo, _) =
|
||||
GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
|
||||
return authInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authorization.
|
||||
/// </summary>
|
||||
@@ -46,7 +55,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
private AuthorizationInfo GetAuthorization(IRequest httpReq)
|
||||
{
|
||||
var auth = GetAuthorizationDictionary(httpReq);
|
||||
var (authInfo, originalAuthInfo) =
|
||||
GetAuthorizationInfoFromDictionary(auth, httpReq.Headers, httpReq.QueryString);
|
||||
|
||||
if (originalAuthInfo != null)
|
||||
{
|
||||
httpReq.Items["OriginalAuthenticationInfo"] = originalAuthInfo;
|
||||
}
|
||||
|
||||
httpReq.Items["AuthorizationInfo"] = authInfo;
|
||||
return authInfo;
|
||||
}
|
||||
|
||||
private (AuthorizationInfo authInfo, AuthenticationInfo originalAuthenticationInfo) GetAuthorizationInfoFromDictionary(
|
||||
in Dictionary<string, string> auth,
|
||||
in IHeaderDictionary headers,
|
||||
in IQueryCollection queryString)
|
||||
{
|
||||
string deviceId = null;
|
||||
string device = null;
|
||||
string client = null;
|
||||
@@ -64,19 +89,20 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
token = httpReq.Headers["X-Emby-Token"];
|
||||
token = headers["X-Emby-Token"];
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
token = httpReq.Headers["X-MediaBrowser-Token"];
|
||||
}
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
token = httpReq.QueryString["api_key"];
|
||||
token = headers["X-MediaBrowser-Token"];
|
||||
}
|
||||
|
||||
var info = new AuthorizationInfo
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
token = queryString["api_key"];
|
||||
}
|
||||
|
||||
var authInfo = new AuthorizationInfo
|
||||
{
|
||||
Client = client,
|
||||
Device = device,
|
||||
@@ -85,6 +111,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
Token = token
|
||||
};
|
||||
|
||||
AuthenticationInfo originalAuthenticationInfo = null;
|
||||
if (!string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
var result = _authRepo.Get(new AuthenticationInfoQuery
|
||||
@@ -92,81 +119,77 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
AccessToken = token
|
||||
});
|
||||
|
||||
var tokenInfo = result.Items.Count > 0 ? result.Items[0] : null;
|
||||
originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
|
||||
|
||||
if (tokenInfo != null)
|
||||
if (originalAuthenticationInfo != null)
|
||||
{
|
||||
var updateToken = false;
|
||||
|
||||
// TODO: Remove these checks for IsNullOrWhiteSpace
|
||||
if (string.IsNullOrWhiteSpace(info.Client))
|
||||
if (string.IsNullOrWhiteSpace(authInfo.Client))
|
||||
{
|
||||
info.Client = tokenInfo.AppName;
|
||||
authInfo.Client = originalAuthenticationInfo.AppName;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(info.DeviceId))
|
||||
if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
|
||||
{
|
||||
info.DeviceId = tokenInfo.DeviceId;
|
||||
authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
|
||||
}
|
||||
|
||||
// Temporary. TODO - allow clients to specify that the token has been shared with a casting device
|
||||
var allowTokenInfoUpdate = info.Client == null || info.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
|
||||
var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(info.Device))
|
||||
if (string.IsNullOrWhiteSpace(authInfo.Device))
|
||||
{
|
||||
info.Device = tokenInfo.DeviceName;
|
||||
authInfo.Device = originalAuthenticationInfo.DeviceName;
|
||||
}
|
||||
|
||||
else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
|
||||
else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (allowTokenInfoUpdate)
|
||||
{
|
||||
updateToken = true;
|
||||
tokenInfo.DeviceName = info.Device;
|
||||
originalAuthenticationInfo.DeviceName = authInfo.Device;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(info.Version))
|
||||
if (string.IsNullOrWhiteSpace(authInfo.Version))
|
||||
{
|
||||
info.Version = tokenInfo.AppVersion;
|
||||
authInfo.Version = originalAuthenticationInfo.AppVersion;
|
||||
}
|
||||
else if (!string.Equals(info.Version, tokenInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
|
||||
else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (allowTokenInfoUpdate)
|
||||
{
|
||||
updateToken = true;
|
||||
tokenInfo.AppVersion = info.Version;
|
||||
originalAuthenticationInfo.AppVersion = authInfo.Version;
|
||||
}
|
||||
}
|
||||
|
||||
if ((DateTime.UtcNow - tokenInfo.DateLastActivity).TotalMinutes > 3)
|
||||
if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
|
||||
{
|
||||
tokenInfo.DateLastActivity = DateTime.UtcNow;
|
||||
originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
|
||||
updateToken = true;
|
||||
}
|
||||
|
||||
if (!tokenInfo.UserId.Equals(Guid.Empty))
|
||||
if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
info.User = _userManager.GetUserById(tokenInfo.UserId);
|
||||
authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
|
||||
|
||||
if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase))
|
||||
if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
tokenInfo.UserName = info.User.Name;
|
||||
originalAuthenticationInfo.UserName = authInfo.User.Username;
|
||||
updateToken = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updateToken)
|
||||
{
|
||||
_authRepo.Update(tokenInfo);
|
||||
_authRepo.Update(originalAuthenticationInfo);
|
||||
}
|
||||
}
|
||||
httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo;
|
||||
}
|
||||
|
||||
httpReq.Items["AuthorizationInfo"] = info;
|
||||
|
||||
return info;
|
||||
return (authInfo, originalAuthenticationInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -186,6 +209,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
return GetAuthorization(auth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the auth.
|
||||
/// </summary>
|
||||
/// <param name="httpReq">The HTTP req.</param>
|
||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||
private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq)
|
||||
{
|
||||
var auth = httpReq.Headers["X-Emby-Authorization"];
|
||||
|
||||
if (string.IsNullOrEmpty(auth))
|
||||
{
|
||||
auth = httpReq.Headers[HeaderNames.Authorization];
|
||||
}
|
||||
|
||||
return GetAuthorization(auth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authorization.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <summary>
|
||||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<WebSocketConnection> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The json serializer options.
|
||||
@@ -234,10 +234,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private Task SendKeepAliveResponse()
|
||||
{
|
||||
LastKeepAliveDate = DateTime.UtcNow;
|
||||
return SendAsync(new WebSocketMessage<string>
|
||||
{
|
||||
MessageType = "KeepAlive"
|
||||
}, CancellationToken.None);
|
||||
return SendAsync(
|
||||
new WebSocketMessage<string>
|
||||
{
|
||||
MessageId = Guid.NewGuid(),
|
||||
MessageType = "KeepAlive"
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
public class LibraryMonitor : ILibraryMonitor
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<LibraryMonitor> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
@@ -266,7 +266,6 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
DisposeWatcher(newWatcher, false);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -393,7 +392,6 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}))
|
||||
{
|
||||
monitorPath = false;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// </summary>
|
||||
public class ManagedFileSystem : IFileSystem
|
||||
{
|
||||
protected ILogger Logger;
|
||||
protected ILogger<ManagedFileSystem> Logger;
|
||||
|
||||
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
|
||||
private readonly string _tempPath;
|
||||
@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
|
||||
|
||||
//if (!result.IsDirectory)
|
||||
// if (!result.IsDirectory)
|
||||
//{
|
||||
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||
//}
|
||||
@@ -638,6 +638,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
@@ -692,6 +693,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
@@ -34,11 +36,6 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
string RestartArgs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the --plugin-manifest-url command line option.
|
||||
/// </summary>
|
||||
string PluginManifestUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the --published-server-url command line option.
|
||||
/// </summary>
|
||||
|
||||
60
Emby.Server.Implementations/Images/ArtistImageProvider.cs
Normal file
60
Emby.Server.Implementations/Images/ArtistImageProvider.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.Images;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ArtistImageProvider.
|
||||
/// </summary>
|
||||
public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist>
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get children objects used to create an artist image.
|
||||
/// </summary>
|
||||
/// <param name="item">The artist used to create the image.</param>
|
||||
/// <returns>Any relevant children objects.</returns>
|
||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||
{
|
||||
return Array.Empty<BaseItem>();
|
||||
|
||||
// TODO enable this when BaseDynamicImageProvider objects are configurable
|
||||
// return _libraryManager.GetItemList(new InternalItemsQuery
|
||||
// {
|
||||
// ArtistIds = new[] { item.Id },
|
||||
// IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
|
||||
// OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||
// Limit = 4,
|
||||
// Recursive = true,
|
||||
// ImageTypes = new[] { ImageType.Primary },
|
||||
// DtoOptions = new DtoOptions(false)
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,8 @@ namespace Emby.Server.Implementations.Images
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
protected virtual string CreateImage(BaseItem item,
|
||||
protected virtual string CreateImage(
|
||||
BaseItem item,
|
||||
IReadOnlyCollection<BaseItem> itemsWithImages,
|
||||
string outputPathWithoutExtension,
|
||||
ImageType imageType,
|
||||
@@ -214,7 +215,12 @@ namespace Emby.Server.Implementations.Images
|
||||
|
||||
if (imageType == ImageType.Primary)
|
||||
{
|
||||
if (item is UserView || item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum)
|
||||
if (item is UserView
|
||||
|| item is Playlist
|
||||
|| item is MusicGenre
|
||||
|| item is Genre
|
||||
|| item is PhotoAlbum
|
||||
|| item is MusicArtist)
|
||||
{
|
||||
return CreateSquareCollage(item, itemsWithImages, outputPath);
|
||||
}
|
||||
@@ -225,7 +231,7 @@ namespace Emby.Server.Implementations.Images
|
||||
throw new ArgumentException("Unexpected image type", nameof(imageType));
|
||||
}
|
||||
|
||||
public bool HasChanged(BaseItem item, IDirectoryService directoryServicee)
|
||||
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
||||
{
|
||||
if (!Supports(item))
|
||||
{
|
||||
@@ -236,6 +242,7 @@ namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -11,7 +13,7 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace Emby.Server.Implementations.UserViews
|
||||
namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
public class CollectionFolderImageProvider : BaseDynamicImageProvider<CollectionFolder>
|
||||
{
|
||||
@@ -69,7 +71,6 @@ namespace Emby.Server.Implementations.UserViews
|
||||
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
|
||||
},
|
||||
IncludeItemTypes = includeItemTypes
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -14,18 +16,16 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.UserViews
|
||||
namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
public class DynamicImageProvider : BaseDynamicImageProvider<UserView>
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager)
|
||||
public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager)
|
||||
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||
@@ -78,7 +78,6 @@ namespace Emby.Server.Implementations.UserViews
|
||||
}
|
||||
|
||||
return i;
|
||||
|
||||
}).GroupBy(x => x.Id)
|
||||
.Select(x => x.First());
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Server.Implementations.Images;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
@@ -11,7 +13,7 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace Emby.Server.Implementations.UserViews
|
||||
namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
public abstract class BaseFolderImageProvider<T> : BaseDynamicImageProvider<T>
|
||||
where T : Folder, new()
|
||||
@@ -75,16 +77,12 @@ namespace Emby.Server.Implementations.UserViews
|
||||
return false;
|
||||
}
|
||||
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
if (item is Folder && item.IsTopParent)
|
||||
{
|
||||
if (folder.IsTopParent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
//return item.SourceType == SourceType.Library;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.Images;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@@ -9,66 +9,21 @@ using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace Emby.Server.Implementations.Playlists
|
||||
namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist>
|
||||
{
|
||||
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||
{
|
||||
var playlist = (Playlist)item;
|
||||
|
||||
return playlist.GetManageableItems()
|
||||
.Select(i =>
|
||||
{
|
||||
var subItem = i.Item2;
|
||||
|
||||
var episode = subItem as Episode;
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
var series = episode.Series;
|
||||
if (series != null && series.HasImage(ImageType.Primary))
|
||||
{
|
||||
return series;
|
||||
}
|
||||
}
|
||||
|
||||
if (subItem.HasImage(ImageType.Primary))
|
||||
{
|
||||
return subItem;
|
||||
}
|
||||
|
||||
var parent = subItem.GetOwner() ?? subItem.GetParent();
|
||||
|
||||
if (parent != null && parent.HasImage(ImageType.Primary))
|
||||
{
|
||||
if (parent is MusicAlbum)
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class MusicGenreImageProvider.
|
||||
/// </summary>
|
||||
public class MusicGenreImageProvider : BaseDynamicImageProvider<MusicGenre>
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
@@ -76,6 +31,11 @@ namespace Emby.Server.Implementations.Playlists
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get children objects used to create an music genre image.
|
||||
/// </summary>
|
||||
/// <param name="item">The music genre used to create the image.</param>
|
||||
/// <returns>Any relevant children objects.</returns>
|
||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||
{
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||
@@ -91,8 +51,14 @@ namespace Emby.Server.Implementations.Playlists
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class GenreImageProvider.
|
||||
/// </summary>
|
||||
public class GenreImageProvider : BaseDynamicImageProvider<Genre>
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
@@ -100,6 +66,11 @@ namespace Emby.Server.Implementations.Playlists
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get children objects used to create an genre image.
|
||||
/// </summary>
|
||||
/// <param name="item">The genre used to create the image.</param>
|
||||
/// <returns>Any relevant children objects.</returns>
|
||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||
{
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||
66
Emby.Server.Implementations/Images/PlaylistImageProvider.cs
Normal file
66
Emby.Server.Implementations/Images/PlaylistImageProvider.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist>
|
||||
{
|
||||
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||
{
|
||||
var playlist = (Playlist)item;
|
||||
|
||||
return playlist.GetManageableItems()
|
||||
.Select(i =>
|
||||
{
|
||||
var subItem = i.Item2;
|
||||
|
||||
var episode = subItem as Episode;
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
var series = episode.Series;
|
||||
if (series != null && series.HasImage(ImageType.Primary))
|
||||
{
|
||||
return series;
|
||||
}
|
||||
}
|
||||
|
||||
if (subItem.HasImage(ImageType.Primary))
|
||||
{
|
||||
return subItem;
|
||||
}
|
||||
|
||||
var parent = subItem.GetOwner() ?? subItem.GetParent();
|
||||
|
||||
if (parent != null && parent.HasImage(ImageType.Primary))
|
||||
{
|
||||
if (parent is MusicAlbum)
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
@@ -8,24 +9,33 @@ using MediaBrowser.Model.IO;
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the core resolver ignore rules
|
||||
/// Provides the core resolver ignore rules.
|
||||
/// </summary>
|
||||
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerApplicationPaths _serverApplicationPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
|
||||
/// <param name="serverApplicationPaths">The server application paths.</param>
|
||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_serverApplicationPaths = serverApplicationPaths;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
|
||||
{
|
||||
// Don't ignore application folders
|
||||
if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't ignore top level folders
|
||||
if (fileInfo.IsDirectory && parent is AggregateFolder)
|
||||
{
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Cryptography;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// The default authentication provider.
|
||||
/// </summary>
|
||||
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
|
||||
{
|
||||
private readonly ICryptoProvider _cryptographyProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultAuthenticationProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="cryptographyProvider">The cryptography provider.</param>
|
||||
public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider)
|
||||
{
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Default";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
// This is dumb and an artifact of the backwards way auth providers were designed.
|
||||
// This version of authenticate was never meant to be called, but needs to be here for interface compat
|
||||
// Only the providers that don't provide local user support use this
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
// This is the version that we need to use for local users. Because reasons.
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
|
||||
{
|
||||
if (resolvedUser == null)
|
||||
{
|
||||
throw new AuthenticationException($"Specified user does not exist.");
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
// As long as jellyfin supports passwordless users, we need this little block here to accommodate
|
||||
if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password))
|
||||
{
|
||||
return Task.FromResult(new ProviderAuthenticationResult
|
||||
{
|
||||
Username = username
|
||||
});
|
||||
}
|
||||
|
||||
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
|
||||
|
||||
PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password);
|
||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
|
||||
|| _cryptographyProvider.DefaultHashMethod == readyHash.Id)
|
||||
{
|
||||
byte[] calculatedHash = _cryptographyProvider.ComputeHash(
|
||||
readyHash.Id,
|
||||
passwordbytes,
|
||||
readyHash.Salt.ToArray());
|
||||
|
||||
if (readyHash.Hash.SequenceEqual(calculatedHash))
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}");
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new AuthenticationException("Invalid username or password");
|
||||
}
|
||||
|
||||
return Task.FromResult(new ProviderAuthenticationResult
|
||||
{
|
||||
Username = username
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPassword(User user)
|
||||
=> !string.IsNullOrEmpty(user.Password);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ChangePassword(User user, string newPassword)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newPassword))
|
||||
{
|
||||
user.Password = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword);
|
||||
user.Password = newPasswordHash.ToString();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
if (newPassword != null)
|
||||
{
|
||||
newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newPasswordHash))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newPasswordHash));
|
||||
}
|
||||
|
||||
user.EasyPassword = newPasswordHash;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetEasyPasswordHash(User user)
|
||||
{
|
||||
return string.IsNullOrEmpty(user.EasyPassword)
|
||||
? null
|
||||
: Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hashed string.
|
||||
/// </summary>
|
||||
public string GetHashedString(User user, string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
return _cryptographyProvider.CreatePasswordHash(str).ToString();
|
||||
}
|
||||
|
||||
// TODO: make use of iterations parameter?
|
||||
PasswordHash passwordHash = PasswordHash.Parse(user.Password);
|
||||
var salt = passwordHash.Salt.ToArray();
|
||||
return new PasswordHash(
|
||||
passwordHash.Id,
|
||||
_cryptographyProvider.ComputeHash(
|
||||
passwordHash.Id,
|
||||
Encoding.UTF8.GetBytes(str),
|
||||
salt),
|
||||
salt,
|
||||
passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString();
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetHashed(User user, string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
return _cryptographyProvider.CreatePasswordHash(str).Hash;
|
||||
}
|
||||
|
||||
// TODO: make use of iterations parameter?
|
||||
PasswordHash passwordHash = PasswordHash.Parse(user.Password);
|
||||
return _cryptographyProvider.ComputeHash(
|
||||
passwordHash.Id,
|
||||
Encoding.UTF8.GetBytes(str),
|
||||
passwordHash.Salt.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// The default password reset provider.
|
||||
/// </summary>
|
||||
public class DefaultPasswordResetProvider : IPasswordResetProvider
|
||||
{
|
||||
private const string BaseResetFileName = "passwordreset";
|
||||
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private readonly string _passwordResetFileBase;
|
||||
private readonly string _passwordResetFileBaseDir;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultPasswordResetProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
public DefaultPasswordResetProvider(
|
||||
IServerConfigurationManager configurationManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IUserManager userManager)
|
||||
{
|
||||
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
|
||||
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Default Password Reset Provider";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
|
||||
{
|
||||
SerializablePasswordReset spr;
|
||||
List<string> usersreset = new List<string>();
|
||||
foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
|
||||
{
|
||||
using (var str = File.OpenRead(resetfile))
|
||||
{
|
||||
spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (spr.ExpirationDate < DateTime.Now)
|
||||
{
|
||||
File.Delete(resetfile);
|
||||
}
|
||||
else if (string.Equals(
|
||||
spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal),
|
||||
pin.Replace("-", string.Empty, StringComparison.Ordinal),
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var resetUser = _userManager.GetUserByName(spr.UserName);
|
||||
if (resetUser == null)
|
||||
{
|
||||
throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
|
||||
}
|
||||
|
||||
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
|
||||
usersreset.Add(resetUser.Name);
|
||||
File.Delete(resetfile);
|
||||
}
|
||||
}
|
||||
|
||||
if (usersreset.Count < 1)
|
||||
{
|
||||
throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new PinRedeemResult
|
||||
{
|
||||
Success = true,
|
||||
UsersReset = usersreset.ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
|
||||
{
|
||||
string pin = string.Empty;
|
||||
using (var cryptoRandom = RandomNumberGenerator.Create())
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
cryptoRandom.GetBytes(bytes);
|
||||
pin = BitConverter.ToString(bytes);
|
||||
}
|
||||
|
||||
DateTime expireTime = DateTime.Now.AddMinutes(30);
|
||||
string filePath = _passwordResetFileBase + user.InternalId + ".json";
|
||||
SerializablePasswordReset spr = new SerializablePasswordReset
|
||||
{
|
||||
ExpirationDate = expireTime,
|
||||
Pin = pin,
|
||||
PinFile = filePath,
|
||||
UserName = user.Name
|
||||
};
|
||||
|
||||
using (FileStream fileStream = File.OpenWrite(filePath))
|
||||
{
|
||||
_jsonSerializer.SerializeToStream(spr, fileStream);
|
||||
await fileStream.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new ForgotPasswordResult
|
||||
{
|
||||
Action = ForgotPasswordAction.PinCode,
|
||||
PinExpirationDate = expireTime,
|
||||
PinFile = filePath
|
||||
};
|
||||
}
|
||||
|
||||
private class SerializablePasswordReset : PasswordPinCreationResult
|
||||
{
|
||||
public string Pin { get; set; }
|
||||
|
||||
public string UserName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,13 @@ namespace Emby.Server.Implementations.Library
|
||||
public class ExclusiveLiveStream : ILiveStream
|
||||
{
|
||||
public int ConsumerCount { get; set; }
|
||||
|
||||
public string OriginalStreamId { get; set; }
|
||||
|
||||
public string TunerHostId => null;
|
||||
|
||||
public bool EnableStreamSharing { get; set; }
|
||||
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
|
||||
public string UniqueId { get; private set; }
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using DotNet.Globbing;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Glob patterns for files to ignore
|
||||
/// Glob patterns for files to ignore.
|
||||
/// </summary>
|
||||
public static class IgnorePatterns
|
||||
{
|
||||
/// <summary>
|
||||
/// Files matching these glob patterns will be ignored
|
||||
/// Files matching these glob patterns will be ignored.
|
||||
/// </summary>
|
||||
public static readonly string[] Patterns = new string[]
|
||||
private static readonly string[] _patterns =
|
||||
{
|
||||
"**/small.jpg",
|
||||
"**/albumart.jpg",
|
||||
@@ -19,32 +22,51 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
// Directories
|
||||
"**/metadata/**",
|
||||
"**/metadata",
|
||||
"**/ps3_update/**",
|
||||
"**/ps3_update",
|
||||
"**/ps3_vprm/**",
|
||||
"**/ps3_vprm",
|
||||
"**/extrafanart/**",
|
||||
"**/extrafanart",
|
||||
"**/extrathumbs/**",
|
||||
"**/extrathumbs",
|
||||
"**/.actors/**",
|
||||
"**/.actors",
|
||||
"**/.wd_tv/**",
|
||||
"**/.wd_tv",
|
||||
"**/lost+found/**",
|
||||
"**/lost+found",
|
||||
|
||||
// WMC temp recording directories that will constantly be written to
|
||||
"**/TempRec/**",
|
||||
"**/TempRec",
|
||||
"**/TempSBE/**",
|
||||
"**/TempSBE",
|
||||
|
||||
// Synology
|
||||
"**/eaDir/**",
|
||||
"**/eaDir",
|
||||
"**/@eaDir/**",
|
||||
"**/@eaDir",
|
||||
"**/#recycle/**",
|
||||
"**/#recycle",
|
||||
|
||||
// Qnap
|
||||
"**/@Recycle/**",
|
||||
"**/@Recycle",
|
||||
"**/.@__thumb/**",
|
||||
"**/.@__thumb",
|
||||
"**/$RECYCLE.BIN/**",
|
||||
"**/$RECYCLE.BIN",
|
||||
"**/System Volume Information/**",
|
||||
"**/System Volume Information",
|
||||
"**/.grab/**",
|
||||
"**/.grab",
|
||||
|
||||
// Unix hidden files and directories
|
||||
"**/.*/**",
|
||||
"**/.*",
|
||||
|
||||
// thumbs.db
|
||||
"**/thumbs.db",
|
||||
@@ -56,19 +78,31 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private static readonly GlobOptions _globOptions = new GlobOptions
|
||||
{
|
||||
Evaluation = {
|
||||
Evaluation =
|
||||
{
|
||||
CaseInsensitive = true
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
|
||||
private static readonly Glob[] _globs = _patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the supplied path should be ignored
|
||||
/// Returns true if the supplied path should be ignored.
|
||||
/// </summary>
|
||||
public static bool ShouldIgnore(string path)
|
||||
/// <param name="path">The path to test.</param>
|
||||
/// <returns>Whether to ignore the path.</returns>
|
||||
public static bool ShouldIgnore(ReadOnlySpan<char> path)
|
||||
{
|
||||
return _globs.Any(g => g.IsMatch(path));
|
||||
int len = _globs.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (_globs[i].IsMatch(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// An invalid authentication provider.
|
||||
/// </summary>
|
||||
public class InvalidAuthProvider : IAuthenticationProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "InvalidOrMissingAuthenticationProvider";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
||||
{
|
||||
throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPassword(User user)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ChangePassword(User user, string newPassword)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetPasswordHash(User user)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetEasyPasswordHash(User user)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,16 @@ using Emby.Server.Implementations.Library.Resolvers;
|
||||
using Emby.Server.Implementations.Library.Validators;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Emby.Server.Implementations.ScheduledTasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
@@ -35,6 +37,7 @@ using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -44,17 +47,20 @@ using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Providers.MediaInfo;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
using Person = MediaBrowser.Controller.Entities.Person;
|
||||
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
|
||||
using VideoResolver = Emby.Naming.Video.VideoResolver;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Class LibraryManager
|
||||
/// Class LibraryManager.
|
||||
/// </summary>
|
||||
public class LibraryManager : ILibraryManager
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<LibraryManager> _logger;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUserDataManager _userDataRepository;
|
||||
@@ -67,6 +73,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IItemRepository _itemRepository;
|
||||
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
|
||||
private NamingOptions _namingOptions;
|
||||
private string[] _videoFileExtensions;
|
||||
@@ -90,13 +97,13 @@ namespace Emby.Server.Implementations.Library
|
||||
private IIntroProvider[] IntroProviders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of entity resolution ignore rules
|
||||
/// Gets or sets the list of entity resolution ignore rules.
|
||||
/// </summary>
|
||||
/// <value>The entity resolution ignore rules.</value>
|
||||
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of currently registered entity resolvers
|
||||
/// Gets or sets the list of currently registered entity resolvers.
|
||||
/// </summary>
|
||||
/// <value>The entity resolvers enumerable.</value>
|
||||
private IItemResolver[] EntityResolvers { get; set; }
|
||||
@@ -129,12 +136,19 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The application host</param>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="taskManager">The task manager.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="userDataRepository">The user data repository.</param>
|
||||
/// <param name="libraryMonitorFactory">The library monitor.</param>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="providerManagerFactory">The provider manager.</param>
|
||||
/// <param name="userviewManagerFactory">The userview manager.</param>
|
||||
/// <param name="mediaEncoder">The media encoder.</param>
|
||||
/// <param name="itemRepository">The item repository.</param>
|
||||
/// <param name="imageProcessor">The image processor.</param>
|
||||
public LibraryManager(
|
||||
IServerApplicationHost appHost,
|
||||
ILogger<LibraryManager> logger,
|
||||
@@ -147,7 +161,8 @@ namespace Emby.Server.Implementations.Library
|
||||
Lazy<IProviderManager> providerManagerFactory,
|
||||
Lazy<IUserViewManager> userviewManagerFactory,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepository)
|
||||
IItemRepository itemRepository,
|
||||
IImageProcessor imageProcessor)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
@@ -161,6 +176,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_userviewManagerFactory = userviewManagerFactory;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepository = itemRepository;
|
||||
_imageProcessor = imageProcessor;
|
||||
|
||||
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
|
||||
|
||||
@@ -193,12 +209,12 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder
|
||||
/// The _root folder.
|
||||
/// </summary>
|
||||
private volatile AggregateFolder _rootFolder;
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder sync lock
|
||||
/// The _root folder sync lock.
|
||||
/// </summary>
|
||||
private readonly object _rootFolderSyncLock = new object();
|
||||
|
||||
@@ -325,7 +341,7 @@ namespace Emby.Server.Implementations.Library
|
||||
if (item is LiveTvProgram)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
"Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
item.GetType().Name,
|
||||
item.Name ?? "Unknown name",
|
||||
item.Path ?? string.Empty,
|
||||
@@ -334,7 +350,7 @@ namespace Emby.Server.Implementations.Library
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
"Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
item.GetType().Name,
|
||||
item.Name ?? "Unknown name",
|
||||
item.Path ?? string.Empty,
|
||||
@@ -352,7 +368,12 @@ namespace Emby.Server.Implementations.Library
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Deleting path {MetadataPath}", metadataPath);
|
||||
_logger.LogDebug(
|
||||
"Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
item.GetType().Name,
|
||||
item.Name ?? "Unknown name",
|
||||
metadataPath,
|
||||
item.Id);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -376,7 +397,13 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
|
||||
_logger.LogInformation(
|
||||
"Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
item.GetType().Name,
|
||||
item.Name ?? "Unknown name",
|
||||
fileSystemInfo.FullName,
|
||||
item.Id);
|
||||
|
||||
if (fileSystemInfo.IsDirectory)
|
||||
{
|
||||
Directory.Delete(fileSystemInfo.FullName, true);
|
||||
@@ -610,7 +637,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a path should be ignored based on its contents - called after the contents have been read
|
||||
/// Determines whether a path should be ignored based on its contents - called after the contents have been read.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
@@ -695,7 +722,9 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
Directory.CreateDirectory(rootFolderPath);
|
||||
|
||||
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>();
|
||||
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
|
||||
((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
|
||||
.DeepCopy<Folder, AggregateFolder>();
|
||||
|
||||
// In case program data folder was moved
|
||||
if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal))
|
||||
@@ -890,7 +919,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Genre
|
||||
/// Gets a Genre.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>Task{Genre}.</returns>
|
||||
@@ -971,7 +1000,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the root media folder
|
||||
/// Reloads the root media folder.
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
@@ -1524,7 +1553,8 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
// Handle grouping
|
||||
if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0)
|
||||
if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType)
|
||||
&& user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
|
||||
{
|
||||
return GetUserRootFolder()
|
||||
.GetChildren(user, true)
|
||||
@@ -1773,7 +1803,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// Creates the items.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="parent">The parent item</param>
|
||||
/// <param name="parent">The parent item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -1815,10 +1845,100 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateImages(BaseItem item)
|
||||
private bool ImageNeedsRefresh(ItemImageInfo image)
|
||||
{
|
||||
_itemRepository.SaveImages(item);
|
||||
if (image.Path != null && image.IsLocalFile)
|
||||
{
|
||||
if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot get file info for {0}", image.Path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return image.Path != null && !image.IsLocalFile;
|
||||
}
|
||||
|
||||
public void UpdateImages(BaseItem item, bool forceUpdate = false)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
||||
if (outdated.Length == 0)
|
||||
{
|
||||
RegisterItem(item);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var img in outdated)
|
||||
{
|
||||
var image = img;
|
||||
if (!img.IsLocalFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
var index = item.GetImageIndex(img);
|
||||
image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
_logger.LogWarning("Cannot get image index for {0}", img.Path);
|
||||
continue;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
_logger.LogWarning("Cannot fetch image from {0}", img.Path);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
|
||||
image.Width = size.Width;
|
||||
image.Height = size.Height;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannnot get image dimensions for {0}", image.Path);
|
||||
image.Width = 0;
|
||||
image.Height = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
|
||||
image.BlurHash = string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
|
||||
}
|
||||
}
|
||||
|
||||
_itemRepository.SaveImages(item);
|
||||
RegisterItem(item);
|
||||
}
|
||||
|
||||
@@ -1839,7 +1959,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
|
||||
RegisterItem(item);
|
||||
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
|
||||
}
|
||||
|
||||
_itemRepository.SaveItems(itemsList, cancellationToken);
|
||||
@@ -2495,7 +2615,7 @@ namespace Emby.Server.Implementations.Library
|
||||
Anime series don't generally have a season in their file name, however,
|
||||
tvdb needs a season to correctly get the metadata.
|
||||
Hence, a null season needs to be filled with something. */
|
||||
//FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
|
||||
// FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
|
||||
@@ -2684,10 +2804,12 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(from))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(to))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(to));
|
||||
@@ -2761,7 +2883,6 @@ namespace Emby.Server.Implementations.Library
|
||||
_logger.LogError(ex, "Error getting person");
|
||||
return null;
|
||||
}
|
||||
|
||||
}).Where(i => i != null).ToList();
|
||||
}
|
||||
|
||||
@@ -2796,7 +2917,8 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
|
||||
if (ex.StatusCode.HasValue
|
||||
&& (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2891,7 +3013,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private static bool ValidateNetworkPath(string path)
|
||||
{
|
||||
//if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
// if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
//{
|
||||
// // We can't validate protocol-based paths, so just allow them
|
||||
// if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
|
||||
|
||||
//_logger.LogDebug("Found cached media info");
|
||||
// _logger.LogDebug("Found cached media info");
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Library
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||
_json.SerializeToFile(mediaInfo, cacheFilePath);
|
||||
|
||||
//_logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,17 +148,14 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
videoStream.BitRate = 30000000;
|
||||
}
|
||||
|
||||
else if (width >= 1900)
|
||||
{
|
||||
videoStream.BitRate = 20000000;
|
||||
}
|
||||
|
||||
else if (width >= 1200)
|
||||
{
|
||||
videoStream.BitRate = 8000000;
|
||||
}
|
||||
|
||||
else if (width >= 700)
|
||||
{
|
||||
videoStream.BitRate = 2000000;
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -14,7 +16,6 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -33,7 +34,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<MediaSourceManager> _logger;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
@@ -190,10 +191,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!user.Policy.EnableAudioPlaybackTranscoding)
|
||||
{
|
||||
source.SupportsTranscoding = false;
|
||||
}
|
||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,22 +205,27 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return MediaProtocol.Rtsp;
|
||||
}
|
||||
|
||||
if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaProtocol.Rtmp;
|
||||
}
|
||||
|
||||
if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaProtocol.Http;
|
||||
}
|
||||
|
||||
if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaProtocol.Rtp;
|
||||
}
|
||||
|
||||
if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaProtocol.Ftp;
|
||||
}
|
||||
|
||||
if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaProtocol.Udp;
|
||||
@@ -352,7 +355,9 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
||||
{
|
||||
if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
|
||||
if (userData.SubtitleStreamIndex.HasValue
|
||||
&& user.RememberSubtitleSelections
|
||||
&& user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
|
||||
{
|
||||
var index = userData.SubtitleStreamIndex.Value;
|
||||
// Make sure the saved index is still valid
|
||||
@@ -363,26 +368,27 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
|
||||
? Array.Empty<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference);
|
||||
|
||||
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
|
||||
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
|
||||
|
||||
var defaultAudioIndex = source.DefaultAudioStreamIndex;
|
||||
var audioLangage = defaultAudioIndex == null
|
||||
? null
|
||||
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
|
||||
|
||||
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams,
|
||||
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
|
||||
source.MediaStreams,
|
||||
preferredSubs,
|
||||
user.Configuration.SubtitleMode,
|
||||
user.SubtitleMode,
|
||||
audioLangage);
|
||||
|
||||
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs,
|
||||
user.Configuration.SubtitleMode, audioLangage);
|
||||
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage);
|
||||
}
|
||||
|
||||
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
||||
{
|
||||
if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection)
|
||||
if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)
|
||||
{
|
||||
var index = userData.AudioStreamIndex.Value;
|
||||
// Make sure the saved index is still valid
|
||||
@@ -393,11 +399,11 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
|
||||
var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference)
|
||||
? Array.Empty<string>()
|
||||
: NormalizeLanguage(user.Configuration.AudioLanguagePreference);
|
||||
: NormalizeLanguage(user.AudioLanguagePreference);
|
||||
|
||||
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
|
||||
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
|
||||
}
|
||||
|
||||
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
|
||||
@@ -435,7 +441,6 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
|
||||
.ThenByDescending(i =>
|
||||
{
|
||||
@@ -521,11 +526,7 @@ namespace Emby.Server.Implementations.Library
|
||||
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
|
||||
}
|
||||
|
||||
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse
|
||||
{
|
||||
MediaSource = clone
|
||||
|
||||
}, liveStream as IDirectStreamProvider);
|
||||
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);
|
||||
}
|
||||
|
||||
private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)
|
||||
@@ -538,7 +539,7 @@ namespace Emby.Server.Implementations.Library
|
||||
mediaSource.RunTimeTicks = null;
|
||||
}
|
||||
|
||||
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
|
||||
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
|
||||
if (audioStream == null || audioStream.Index == -1)
|
||||
{
|
||||
@@ -549,7 +550,7 @@ namespace Emby.Server.Implementations.Library
|
||||
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
|
||||
}
|
||||
|
||||
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
|
||||
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
||||
if (videoStream != null)
|
||||
{
|
||||
if (!videoStream.BitRate.HasValue)
|
||||
@@ -560,17 +561,14 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
videoStream.BitRate = 30000000;
|
||||
}
|
||||
|
||||
else if (width >= 1900)
|
||||
{
|
||||
videoStream.BitRate = 20000000;
|
||||
}
|
||||
|
||||
else if (width >= 1200)
|
||||
{
|
||||
videoStream.BitRate = 8000000;
|
||||
}
|
||||
|
||||
else if (width >= 700)
|
||||
{
|
||||
videoStream.BitRate = 2000000;
|
||||
@@ -626,7 +624,6 @@ namespace Emby.Server.Implementations.Library
|
||||
MediaSource = mediaSource,
|
||||
ExtractChapters = false,
|
||||
MediaType = DlnaProfileType.Video
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
mediaSource.MediaStreams = info.MediaStreams;
|
||||
@@ -652,7 +649,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
|
||||
|
||||
//_logger.LogDebug("Found cached media info");
|
||||
// _logger.LogDebug("Found cached media info");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -674,20 +671,21 @@ namespace Emby.Server.Implementations.Library
|
||||
mediaSource.AnalyzeDurationMs = 3000;
|
||||
}
|
||||
|
||||
mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||
mediaInfo = await _mediaEncoder.GetMediaInfo(
|
||||
new MediaInfoRequest
|
||||
{
|
||||
MediaSource = mediaSource,
|
||||
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
||||
ExtractChapters = false
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (cacheFilePath != null)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||
_jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
|
||||
|
||||
//_logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,17 +751,14 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
videoStream.BitRate = 30000000;
|
||||
}
|
||||
|
||||
else if (width >= 1900)
|
||||
{
|
||||
videoStream.BitRate = 20000000;
|
||||
}
|
||||
|
||||
else if (width >= 1200)
|
||||
{
|
||||
videoStream.BitRate = 8000000;
|
||||
}
|
||||
|
||||
else if (width >= 700)
|
||||
{
|
||||
videoStream.BitRate = 2000000;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
@@ -75,7 +77,6 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
}).Where(i => !i.Equals(Guid.Empty)).ToArray();
|
||||
|
||||
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
|
||||
@@ -105,32 +106,27 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
|
||||
}
|
||||
|
||||
var playlist = item as Playlist;
|
||||
if (playlist != null)
|
||||
if (item is Playlist playlist)
|
||||
{
|
||||
return GetInstantMixFromPlaylist(playlist, user, dtoOptions);
|
||||
}
|
||||
|
||||
var album = item as MusicAlbum;
|
||||
if (album != null)
|
||||
if (item is MusicAlbum album)
|
||||
{
|
||||
return GetInstantMixFromAlbum(album, user, dtoOptions);
|
||||
}
|
||||
|
||||
var artist = item as MusicArtist;
|
||||
if (artist != null)
|
||||
if (item is MusicArtist artist)
|
||||
{
|
||||
return GetInstantMixFromArtist(artist, user, dtoOptions);
|
||||
}
|
||||
|
||||
var song = item as Audio;
|
||||
if (song != null)
|
||||
if (item is Audio song)
|
||||
{
|
||||
return GetInstantMixFromSong(song, user, dtoOptions);
|
||||
}
|
||||
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
if (item is Folder folder)
|
||||
{
|
||||
return GetInstantMixFromFolder(folder, user, dtoOptions);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures DateCreated and DateModified have values
|
||||
/// Ensures DateCreated and DateModified have values.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
|
||||
@@ -209,8 +209,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
Name = parseName ?
|
||||
resolvedItem.Name :
|
||||
Path.GetFileNameWithoutExtension(firstMedia.Path),
|
||||
//AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
|
||||
//LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
|
||||
// AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
|
||||
// LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
|
||||
};
|
||||
|
||||
result.Items.Add(libraryItem);
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
// Args points to an album if parent is an Artist folder or it directly contains music
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
// if (args.Parent is MusicArtist) return true; //saves us from testing children twice
|
||||
// if (args.Parent is MusicArtist) return true; // saves us from testing children twice
|
||||
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
|
||||
{
|
||||
return true;
|
||||
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
IEnumerable<FileSystemMetadata> list,
|
||||
bool allowSubfolders,
|
||||
IDirectoryService directoryService,
|
||||
ILogger logger,
|
||||
ILogger<MusicAlbumResolver> logger,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
|
||||
@@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
}
|
||||
|
||||
return true;
|
||||
//var blurayExtensions = new[]
|
||||
// var blurayExtensions = new[]
|
||||
//{
|
||||
// ".mts",
|
||||
// ".m2ts",
|
||||
@@ -300,7 +300,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
// ".mpls"
|
||||
//};
|
||||
|
||||
//return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
|
||||
// return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
|
||||
|
||||
// Only process items that are in a collection folder containing books
|
||||
if (!string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
@@ -55,7 +57,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
|
||||
|
||||
// Don't return a Book if there is more (or less) than one document in the directory
|
||||
if (bookFiles.Count != 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Book
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
public virtual ResolverPriority Priority => ResolverPriority.First;
|
||||
|
||||
/// <summary>
|
||||
/// Sets initial values on the newly resolved item
|
||||
/// Sets initial values on the newly resolved item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
item.SetProviderId(MetadataProviders.Tmdb, id);
|
||||
item.SetProviderId(MetadataProvider.Tmdb, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tmdbid))
|
||||
{
|
||||
item.SetProviderId(MetadataProviders.Tmdb, tmdbid);
|
||||
item.SetProviderId(MetadataProvider.Tmdb, tmdbid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(imdbid))
|
||||
{
|
||||
item.SetProviderId(MetadataProviders.Imdb, imdbid);
|
||||
item.SetProviderId(MetadataProvider.Imdb, imdbid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
return new AggregateFolder();
|
||||
}
|
||||
|
||||
if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new UserRootFolder(); //if we got here and still a root - must be user root
|
||||
return new UserRootFolder(); // if we got here and still a root - must be user root
|
||||
}
|
||||
|
||||
if (args.IsVf)
|
||||
{
|
||||
return new CollectionFolder
|
||||
@@ -73,7 +75,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
})
|
||||
.Select(i => _fileSystem.GetFileNameWithoutExtension(i))
|
||||
.FirstOrDefault();
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
episode.SeriesId = series.Id;
|
||||
episode.SeriesName = series.Name;
|
||||
}
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
episode.SeasonId = season.Id;
|
||||
|
||||
@@ -16,15 +16,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<SeasonResolver> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeasonResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="config">The config.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="localization">The localization</param>
|
||||
/// <param name="logger">The logger</param>
|
||||
/// <param name="localization">The localization.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public SeasonResolver(
|
||||
IServerConfigurationManager config,
|
||||
ILibraryManager libraryManager,
|
||||
@@ -94,7 +94,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
_localization.GetLocalizedString("NameSeasonNumber"),
|
||||
seasonNumber,
|
||||
args.GetLibraryOptions().PreferredMetadataLanguage);
|
||||
|
||||
}
|
||||
|
||||
return season;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
public class SeriesResolver : FolderResolver<Series>
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<SeriesResolver> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
var collectionType = args.GetCollectionType();
|
||||
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
|
||||
// if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
|
||||
//{
|
||||
// return new Series
|
||||
// {
|
||||
@@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
IEnumerable<FileSystemMetadata> fileSystemChildren,
|
||||
IDirectoryService directoryService,
|
||||
IFileSystem fileSystem,
|
||||
ILogger logger,
|
||||
ILogger<SeriesResolver> logger,
|
||||
ILibraryManager libraryManager,
|
||||
bool isTvContentType)
|
||||
{
|
||||
@@ -217,7 +217,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
item.SetProviderId(MetadataProviders.Tvdb, id);
|
||||
item.SetProviderId(MetadataProvider.Tvdb, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@@ -12,12 +13,14 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Search;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
using Person = MediaBrowser.Controller.Entities.Person;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
public class SearchEngine : ISearchEngine
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<SearchEngine> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
@@ -191,6 +194,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
searchQuery.AncestorIds = new[] { searchQuery.ParentId };
|
||||
}
|
||||
|
||||
searchQuery.ParentId = Guid.Empty;
|
||||
searchQuery.IncludeItemsByName = true;
|
||||
searchQuery.IncludeItemTypes = Array.Empty<string>();
|
||||
@@ -204,7 +208,6 @@ namespace Emby.Server.Implementations.Library
|
||||
return mediaItems.Select(i => new SearchHintInfo
|
||||
{
|
||||
Item = i
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Book = MediaBrowser.Controller.Entities.Book;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
@@ -26,7 +28,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly ConcurrentDictionary<string, UserItemData> _userData =
|
||||
new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<UserDataManager> _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUserDataRepository _repository;
|
||||
@@ -101,7 +103,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all user data for the given user
|
||||
/// Retrieve all user data for the given user.
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -186,7 +188,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a UserItemData to a DTOUserItemData
|
||||
/// Converts a UserItemData to a DTOUserItemData.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>DtoUserItemData.</returns>
|
||||
@@ -240,7 +242,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
// Enforce MinResumeDuration
|
||||
var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds;
|
||||
if (durationSeconds < _config.Configuration.MinResumeDurationSeconds)
|
||||
if (durationSeconds < _config.Configuration.MinResumeDurationSeconds && !(item is Book))
|
||||
{
|
||||
positionTicks = 0;
|
||||
data.Played = playedToCompletion = true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@@ -17,6 +19,8 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Library;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
using Person = MediaBrowser.Controller.Entities.Person;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
@@ -125,12 +129,12 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (!query.IncludeHidden)
|
||||
{
|
||||
list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
|
||||
list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
|
||||
}
|
||||
|
||||
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
|
||||
|
||||
var orders = user.Configuration.OrderedViews.ToList();
|
||||
var orders = user.GetPreference(PreferenceKind.OrderedViews).ToList();
|
||||
|
||||
return list
|
||||
.OrderBy(i =>
|
||||
@@ -165,7 +169,13 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetUserSubViewWithName(name, parentId, type, sortName);
|
||||
}
|
||||
|
||||
private Folder GetUserView(List<ICollectionFolder> parents, string viewType, string localizationKey, string sortName, User user, string[] presetViews)
|
||||
private Folder GetUserView(
|
||||
List<ICollectionFolder> parents,
|
||||
string viewType,
|
||||
string localizationKey,
|
||||
string sortName,
|
||||
Jellyfin.Data.Entities.User user,
|
||||
string[] presetViews)
|
||||
{
|
||||
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
@@ -270,7 +280,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes)
|
||||
.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -331,12 +342,11 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[]
|
||||
{
|
||||
typeof(Person).Name,
|
||||
typeof(Studio).Name,
|
||||
typeof(Year).Name,
|
||||
typeof(MusicGenre).Name,
|
||||
typeof(Genre).Name
|
||||
|
||||
nameof(Person),
|
||||
nameof(Studio),
|
||||
nameof(Year),
|
||||
nameof(MusicGenre),
|
||||
nameof(Genre)
|
||||
} : Array.Empty<string>();
|
||||
|
||||
var query = new InternalItemsQuery(user)
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// The _library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<ArtistsValidator> _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public ArtistsPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<ArtistsPostScanTask> logger,
|
||||
ILogger<ArtistsValidator> logger,
|
||||
IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <summary>
|
||||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<ArtistsValidator> _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
@@ -98,7 +98,6 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// The _library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<GenresValidator> _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public GenresPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<GenresPostScanTask> logger,
|
||||
ILogger<GenresValidator> logger,
|
||||
IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <summary>
|
||||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<GenresValidator> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenresValidator"/> class.
|
||||
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// The library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<MusicGenresValidator> _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public MusicGenresPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<MusicGenresPostScanTask> logger,
|
||||
ILogger<MusicGenresValidator> logger,
|
||||
IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <summary>
|
||||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<MusicGenresValidator> _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
public MusicGenresValidator(ILibraryManager libraryManager, ILogger<MusicGenresValidator> logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<StudiosValidator> _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public StudiosPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<StudiosPostScanTask> logger,
|
||||
ILogger<StudiosValidator> logger,
|
||||
IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <summary>
|
||||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<StudiosValidator> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StudiosValidator" /> class.
|
||||
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
public StudiosValidator(ILibraryManager libraryManager, ILogger<StudiosValidator> logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
@@ -92,7 +92,6 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
private const int TunerDiscoveryDurationMs = 3000;
|
||||
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<EmbyTV> _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
@@ -140,11 +140,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
||||
private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
||||
{
|
||||
if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OnRecordingFoldersChanged();
|
||||
await CreateRecordingFolders().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,11 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
return CreateRecordingFolders();
|
||||
}
|
||||
|
||||
private async void OnRecordingFoldersChanged()
|
||||
{
|
||||
await CreateRecordingFolders().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task CreateRecordingFolders()
|
||||
{
|
||||
try
|
||||
@@ -1334,7 +1329,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
await CreateRecordingFolders().ConfigureAwait(false);
|
||||
|
||||
TriggerRefresh(recordPath);
|
||||
EnforceKeepUpTo(timer, seriesPath);
|
||||
await EnforceKeepUpTo(timer, seriesPath).ConfigureAwait(false);
|
||||
};
|
||||
|
||||
await recorder.Record(directStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false);
|
||||
@@ -1494,7 +1489,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
return item;
|
||||
}
|
||||
|
||||
private async void EnforceKeepUpTo(TimerInfo timer, string seriesPath)
|
||||
private async Task EnforceKeepUpTo(TimerInfo timer, string seriesPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(timer.SeriesTimerId))
|
||||
{
|
||||
@@ -1552,7 +1547,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
IsFolder = false,
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
})
|
||||
.Where(i => i.IsFileProtocol && File.Exists(i.Path))
|
||||
.Skip(seriesTimer.KeepUpTo - 1)
|
||||
@@ -1898,22 +1892,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
writer.WriteStartDocument(true);
|
||||
writer.WriteStartElement("tvshow");
|
||||
string id;
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out id))
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out id))
|
||||
{
|
||||
writer.WriteElementString("id", id);
|
||||
}
|
||||
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id))
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out id))
|
||||
{
|
||||
writer.WriteElementString("imdb_id", id);
|
||||
}
|
||||
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out id))
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out id))
|
||||
{
|
||||
writer.WriteElementString("tmdbid", id);
|
||||
}
|
||||
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id))
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out id))
|
||||
{
|
||||
writer.WriteElementString("zap2itid", id);
|
||||
}
|
||||
@@ -2080,14 +2074,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
writer.WriteElementString("credits", person);
|
||||
}
|
||||
|
||||
var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection);
|
||||
var tmdbCollection = item.GetProviderId(MetadataProvider.TmdbCollection);
|
||||
|
||||
if (!string.IsNullOrEmpty(tmdbCollection))
|
||||
{
|
||||
writer.WriteElementString("collectionnumber", tmdbCollection);
|
||||
}
|
||||
|
||||
var imdb = item.GetProviderId(MetadataProviders.Imdb);
|
||||
var imdb = item.GetProviderId(MetadataProvider.Imdb);
|
||||
if (!string.IsNullOrEmpty(imdb))
|
||||
{
|
||||
if (!isSeriesEpisode)
|
||||
@@ -2101,7 +2095,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
lockData = false;
|
||||
}
|
||||
|
||||
var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
|
||||
var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
|
||||
if (!string.IsNullOrEmpty(tvdb))
|
||||
{
|
||||
writer.WriteElementString("tvdbid", tvdb);
|
||||
@@ -2110,7 +2104,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
lockData = false;
|
||||
}
|
||||
|
||||
var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
|
||||
var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
|
||||
if (!string.IsNullOrEmpty(tmdb))
|
||||
{
|
||||
writer.WriteElementString("tmdbid", tmdb);
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
onStarted();
|
||||
|
||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||
StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||
_ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||
|
||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
||||
|
||||
@@ -183,7 +183,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
|
||||
|
||||
//var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
|
||||
// var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
|
||||
// " -f mp4 -movflags frag_keyframe+empty_moov" :
|
||||
// string.Empty;
|
||||
|
||||
@@ -206,13 +206,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
return "-codec:a:0 copy";
|
||||
|
||||
//var audioChannels = 2;
|
||||
//var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
//if (audioStream != null)
|
||||
// var audioChannels = 2;
|
||||
// var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
// if (audioStream != null)
|
||||
//{
|
||||
// audioChannels = audioStream.Channels ?? audioChannels;
|
||||
//}
|
||||
//return "-codec:a:0 aac -strict experimental -ab 320000";
|
||||
// return "-codec:a:0 aac -strict experimental -ab 320000";
|
||||
}
|
||||
|
||||
private static bool EncodeVideo(MediaSourceInfo mediaSource)
|
||||
@@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
}
|
||||
|
||||
private async void StartStreamingLog(Stream source, Stream target)
|
||||
private async Task StartStreamingLog(Stream source, Stream target)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -56,7 +56,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
name += " " + info.EpisodeTitle;
|
||||
}
|
||||
}
|
||||
|
||||
else if (info.IsMovie && info.ProductionYear != null)
|
||||
{
|
||||
name += " (" + info.ProductionYear + ")";
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (startDate < now)
|
||||
{
|
||||
TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo> { Argument = item });
|
||||
TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo>(item));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
|
||||
if (timer != null)
|
||||
{
|
||||
TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo> { Argument = timer });
|
||||
TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
public class SchedulesDirect : IListingsProvider
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<SchedulesDirect> _logger;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
|
||||
@@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
var programsInfo = new List<ProgramInfo>();
|
||||
foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
|
||||
{
|
||||
//_logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
|
||||
// _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
|
||||
// " which corresponds to channel " + channelNumber + " and program id " +
|
||||
// schedule.programID + " which says it has images? " +
|
||||
// programDict[schedule.programID].hasImageArtwork);
|
||||
@@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
|
||||
|
||||
//programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
|
||||
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
|
||||
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
|
||||
// GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
|
||||
// GetProgramImage(ApiUrl, data, "Banner-LOT", false);
|
||||
@@ -212,6 +212,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
channelNumber = map.channel;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(channelNumber))
|
||||
{
|
||||
channelNumber = map.atscMajor + "." + map.atscMinor;
|
||||
@@ -276,7 +277,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
CommunityRating = null,
|
||||
EpisodeTitle = episodeTitle,
|
||||
Audio = audioType,
|
||||
//IsNew = programInfo.@new ?? false,
|
||||
// IsNew = programInfo.@new ?? false,
|
||||
IsRepeat = programInfo.@new == null,
|
||||
IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase),
|
||||
ImageUrl = details.primaryImage,
|
||||
@@ -342,7 +343,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
info.SeriesId = programId.Substring(0, 10);
|
||||
|
||||
info.SeriesProviderIds[MetadataProviders.Zap2It.ToString()] = info.SeriesId;
|
||||
info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId;
|
||||
|
||||
if (details.metadata != null)
|
||||
{
|
||||
@@ -400,6 +401,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
@@ -622,6 +624,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
_lastErrorResponse = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
@@ -701,7 +704,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
CancellationToken = cancellationToken,
|
||||
LogErrorResponseBody = true
|
||||
};
|
||||
//_logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
|
||||
// _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
|
||||
// httpOptions.RequestContent);
|
||||
|
||||
using (var response = await Post(httpOptions, false, null).ConfigureAwait(false))
|
||||
@@ -805,11 +808,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
throw new ArgumentException("Username is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(info.Password))
|
||||
{
|
||||
throw new ArgumentException("Password is required");
|
||||
}
|
||||
}
|
||||
|
||||
if (validateListings)
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.ListingsId))
|
||||
@@ -932,24 +937,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
public class Token
|
||||
{
|
||||
public int code { get; set; }
|
||||
|
||||
public string message { get; set; }
|
||||
|
||||
public string serverID { get; set; }
|
||||
|
||||
public string token { get; set; }
|
||||
}
|
||||
|
||||
public class Lineup
|
||||
{
|
||||
public string lineup { get; set; }
|
||||
|
||||
public string name { get; set; }
|
||||
|
||||
public string transport { get; set; }
|
||||
|
||||
public string location { get; set; }
|
||||
|
||||
public string uri { get; set; }
|
||||
}
|
||||
|
||||
public class Lineups
|
||||
{
|
||||
public int code { get; set; }
|
||||
|
||||
public string serverID { get; set; }
|
||||
|
||||
public string datetime { get; set; }
|
||||
|
||||
public List<Lineup> lineups { get; set; }
|
||||
}
|
||||
|
||||
@@ -957,8 +973,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
public class Headends
|
||||
{
|
||||
public string headend { get; set; }
|
||||
|
||||
public string transport { get; set; }
|
||||
|
||||
public string location { get; set; }
|
||||
|
||||
public List<Lineup> lineups { get; set; }
|
||||
}
|
||||
|
||||
@@ -967,59 +986,83 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
public class Map
|
||||
{
|
||||
public string stationID { get; set; }
|
||||
|
||||
public string channel { get; set; }
|
||||
|
||||
public string logicalChannelNumber { get; set; }
|
||||
|
||||
public int uhfVhf { get; set; }
|
||||
|
||||
public int atscMajor { get; set; }
|
||||
|
||||
public int atscMinor { get; set; }
|
||||
}
|
||||
|
||||
public class Broadcaster
|
||||
{
|
||||
public string city { get; set; }
|
||||
|
||||
public string state { get; set; }
|
||||
|
||||
public string postalcode { get; set; }
|
||||
|
||||
public string country { get; set; }
|
||||
}
|
||||
|
||||
public class Logo
|
||||
{
|
||||
public string URL { get; set; }
|
||||
|
||||
public int height { get; set; }
|
||||
|
||||
public int width { get; set; }
|
||||
|
||||
public string md5 { get; set; }
|
||||
}
|
||||
|
||||
public class Station
|
||||
{
|
||||
public string stationID { get; set; }
|
||||
|
||||
public string name { get; set; }
|
||||
|
||||
public string callsign { get; set; }
|
||||
|
||||
public List<string> broadcastLanguage { get; set; }
|
||||
|
||||
public List<string> descriptionLanguage { get; set; }
|
||||
|
||||
public Broadcaster broadcaster { get; set; }
|
||||
|
||||
public string affiliate { get; set; }
|
||||
|
||||
public Logo logo { get; set; }
|
||||
|
||||
public bool? isCommercialFree { get; set; }
|
||||
}
|
||||
|
||||
public class Metadata
|
||||
{
|
||||
public string lineup { get; set; }
|
||||
|
||||
public string modified { get; set; }
|
||||
|
||||
public string transport { get; set; }
|
||||
}
|
||||
|
||||
public class Channel
|
||||
{
|
||||
public List<Map> map { get; set; }
|
||||
|
||||
public List<Station> stations { get; set; }
|
||||
|
||||
public Metadata metadata { get; set; }
|
||||
}
|
||||
|
||||
public class RequestScheduleForChannel
|
||||
{
|
||||
public string stationID { get; set; }
|
||||
|
||||
public List<string> date { get; set; }
|
||||
}
|
||||
|
||||
@@ -1029,29 +1072,43 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
public class Rating
|
||||
{
|
||||
public string body { get; set; }
|
||||
|
||||
public string code { get; set; }
|
||||
}
|
||||
|
||||
public class Multipart
|
||||
{
|
||||
public int partNumber { get; set; }
|
||||
|
||||
public int totalParts { get; set; }
|
||||
}
|
||||
|
||||
public class Program
|
||||
{
|
||||
public string programID { get; set; }
|
||||
|
||||
public string airDateTime { get; set; }
|
||||
|
||||
public int duration { get; set; }
|
||||
|
||||
public string md5 { get; set; }
|
||||
|
||||
public List<string> audioProperties { get; set; }
|
||||
|
||||
public List<string> videoProperties { get; set; }
|
||||
|
||||
public List<Rating> ratings { get; set; }
|
||||
|
||||
public bool? @new { get; set; }
|
||||
|
||||
public Multipart multipart { get; set; }
|
||||
|
||||
public string liveTapeDelay { get; set; }
|
||||
|
||||
public bool premiere { get; set; }
|
||||
|
||||
public bool repeat { get; set; }
|
||||
|
||||
public string isPremiereOrFinale { get; set; }
|
||||
}
|
||||
|
||||
@@ -1060,16 +1117,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
public class MetadataSchedule
|
||||
{
|
||||
public string modified { get; set; }
|
||||
|
||||
public string md5 { get; set; }
|
||||
|
||||
public string startDate { get; set; }
|
||||
|
||||
public string endDate { get; set; }
|
||||
|
||||
public int days { get; set; }
|
||||
}
|
||||
|
||||
public class Day
|
||||
{
|
||||
public string stationID { get; set; }
|
||||
|
||||
public List<Program> programs { get; set; }
|
||||
|
||||
public MetadataSchedule metadata { get; set; }
|
||||
|
||||
public Day()
|
||||
@@ -1092,24 +1155,28 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
public class Description100
|
||||
{
|
||||
public string descriptionLanguage { get; set; }
|
||||
|
||||
public string description { get; set; }
|
||||
}
|
||||
|
||||
public class Description1000
|
||||
{
|
||||
public string descriptionLanguage { get; set; }
|
||||
|
||||
public string description { get; set; }
|
||||
}
|
||||
|
||||
public class DescriptionsProgram
|
||||
{
|
||||
public List<Description100> description100 { get; set; }
|
||||
|
||||
public List<Description1000> description1000 { get; set; }
|
||||
}
|
||||
|
||||
public class Gracenote
|
||||
{
|
||||
public int season { get; set; }
|
||||
|
||||
public int episode { get; set; }
|
||||
}
|
||||
|
||||
@@ -1121,104 +1188,154 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
public class ContentRating
|
||||
{
|
||||
public string body { get; set; }
|
||||
|
||||
public string code { get; set; }
|
||||
}
|
||||
|
||||
public class Cast
|
||||
{
|
||||
public string billingOrder { get; set; }
|
||||
|
||||
public string role { get; set; }
|
||||
|
||||
public string nameId { get; set; }
|
||||
|
||||
public string personId { get; set; }
|
||||
|
||||
public string name { get; set; }
|
||||
|
||||
public string characterName { get; set; }
|
||||
}
|
||||
|
||||
public class Crew
|
||||
{
|
||||
public string billingOrder { get; set; }
|
||||
|
||||
public string role { get; set; }
|
||||
|
||||
public string nameId { get; set; }
|
||||
|
||||
public string personId { get; set; }
|
||||
|
||||
public string name { get; set; }
|
||||
}
|
||||
|
||||
public class QualityRating
|
||||
{
|
||||
public string ratingsBody { get; set; }
|
||||
|
||||
public string rating { get; set; }
|
||||
|
||||
public string minRating { get; set; }
|
||||
|
||||
public string maxRating { get; set; }
|
||||
|
||||
public string increment { get; set; }
|
||||
}
|
||||
|
||||
public class Movie
|
||||
{
|
||||
public string year { get; set; }
|
||||
|
||||
public int duration { get; set; }
|
||||
|
||||
public List<QualityRating> qualityRating { get; set; }
|
||||
}
|
||||
|
||||
public class Recommendation
|
||||
{
|
||||
public string programID { get; set; }
|
||||
|
||||
public string title120 { get; set; }
|
||||
}
|
||||
|
||||
public class ProgramDetails
|
||||
{
|
||||
public string audience { get; set; }
|
||||
|
||||
public string programID { get; set; }
|
||||
|
||||
public List<Title> titles { get; set; }
|
||||
|
||||
public EventDetails eventDetails { get; set; }
|
||||
|
||||
public DescriptionsProgram descriptions { get; set; }
|
||||
|
||||
public string originalAirDate { get; set; }
|
||||
|
||||
public List<string> genres { get; set; }
|
||||
|
||||
public string episodeTitle150 { get; set; }
|
||||
|
||||
public List<MetadataPrograms> metadata { get; set; }
|
||||
|
||||
public List<ContentRating> contentRating { get; set; }
|
||||
|
||||
public List<Cast> cast { get; set; }
|
||||
|
||||
public List<Crew> crew { get; set; }
|
||||
|
||||
public string entityType { get; set; }
|
||||
|
||||
public string showType { get; set; }
|
||||
|
||||
public bool hasImageArtwork { get; set; }
|
||||
|
||||
public string primaryImage { get; set; }
|
||||
|
||||
public string thumbImage { get; set; }
|
||||
|
||||
public string backdropImage { get; set; }
|
||||
|
||||
public string bannerImage { get; set; }
|
||||
|
||||
public string imageID { get; set; }
|
||||
|
||||
public string md5 { get; set; }
|
||||
|
||||
public List<string> contentAdvisory { get; set; }
|
||||
|
||||
public Movie movie { get; set; }
|
||||
|
||||
public List<Recommendation> recommendations { get; set; }
|
||||
}
|
||||
|
||||
public class Caption
|
||||
{
|
||||
public string content { get; set; }
|
||||
|
||||
public string lang { get; set; }
|
||||
}
|
||||
|
||||
public class ImageData
|
||||
{
|
||||
public string width { get; set; }
|
||||
|
||||
public string height { get; set; }
|
||||
|
||||
public string uri { get; set; }
|
||||
|
||||
public string size { get; set; }
|
||||
|
||||
public string aspect { get; set; }
|
||||
|
||||
public string category { get; set; }
|
||||
|
||||
public string text { get; set; }
|
||||
|
||||
public string primary { get; set; }
|
||||
|
||||
public string tier { get; set; }
|
||||
|
||||
public Caption caption { get; set; }
|
||||
}
|
||||
|
||||
public class ShowImages
|
||||
{
|
||||
public string programID { get; set; }
|
||||
|
||||
public List<ImageData> data { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<XmlTvListingsProvider> _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IZipClient _zipClient;
|
||||
|
||||
@@ -224,6 +224,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (programInfo.EpisodeNumber.HasValue)
|
||||
{
|
||||
uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
private const string ServiceName = "Emby";
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<LiveTvDtoService> _logger;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly IApplicationHost _appHost;
|
||||
|
||||
@@ -7,17 +7,15 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Library;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
@@ -33,6 +31,8 @@ using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
@@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
private const string EtagKey = "ProgramEtag";
|
||||
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<LiveTvManager> _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
@@ -148,27 +148,18 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
var timerId = e.Argument;
|
||||
|
||||
TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>
|
||||
{
|
||||
Argument = new TimerEventInfo
|
||||
{
|
||||
Id = timerId
|
||||
}
|
||||
});
|
||||
TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(timerId)));
|
||||
}
|
||||
|
||||
private void OnEmbyTvTimerCreated(object sender, GenericEventArgs<TimerInfo> e)
|
||||
{
|
||||
var timer = e.Argument;
|
||||
|
||||
TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>
|
||||
{
|
||||
Argument = new TimerEventInfo
|
||||
TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
|
||||
new TimerEventInfo(timer.Id)
|
||||
{
|
||||
ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId),
|
||||
Id = timer.Id
|
||||
}
|
||||
});
|
||||
ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId)
|
||||
}));
|
||||
}
|
||||
|
||||
public List<NameIdPair> GetTunerHostTypes()
|
||||
@@ -415,8 +406,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
if (!(service is EmbyTV.EmbyTV))
|
||||
{
|
||||
// We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
|
||||
//mediaSource.SupportsDirectPlay = false;
|
||||
//mediaSource.SupportsDirectStream = false;
|
||||
// mediaSource.SupportsDirectPlay = false;
|
||||
// mediaSource.SupportsDirectStream = false;
|
||||
mediaSource.SupportsTranscoding = true;
|
||||
foreach (var stream in mediaSource.MediaStreams)
|
||||
{
|
||||
@@ -565,9 +556,10 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.ParentId = channel.Id;
|
||||
|
||||
//item.ChannelType = channelType;
|
||||
// item.ChannelType = channelType;
|
||||
|
||||
item.Audio = info.Audio;
|
||||
item.ChannelId = channel.Id;
|
||||
@@ -584,6 +576,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.ExternalSeriesId = seriesId;
|
||||
|
||||
var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle);
|
||||
@@ -598,30 +591,37 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
tags.Add("Live");
|
||||
}
|
||||
|
||||
if (info.IsPremiere)
|
||||
{
|
||||
tags.Add("Premiere");
|
||||
}
|
||||
|
||||
if (info.IsNews)
|
||||
{
|
||||
tags.Add("News");
|
||||
}
|
||||
|
||||
if (info.IsSports)
|
||||
{
|
||||
tags.Add("Sports");
|
||||
}
|
||||
|
||||
if (info.IsKids)
|
||||
{
|
||||
tags.Add("Kids");
|
||||
}
|
||||
|
||||
if (info.IsRepeat)
|
||||
{
|
||||
tags.Add("Repeat");
|
||||
}
|
||||
|
||||
if (info.IsMovie)
|
||||
{
|
||||
tags.Add("Movie");
|
||||
}
|
||||
|
||||
if (isSeries)
|
||||
{
|
||||
tags.Add("Series");
|
||||
@@ -644,6 +644,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.IsSeries = isSeries;
|
||||
|
||||
item.Name = info.Name;
|
||||
@@ -661,12 +662,14 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.StartDate = info.StartDate;
|
||||
|
||||
if (item.EndDate != info.EndDate)
|
||||
{
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.EndDate = info.EndDate;
|
||||
|
||||
item.ProductionYear = info.ProductionYear;
|
||||
@@ -707,7 +710,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
Path = info.ThumbImageUrl,
|
||||
Type = ImageType.Thumb
|
||||
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -720,7 +722,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
Path = info.LogoImageUrl,
|
||||
Type = ImageType.Logo
|
||||
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -733,7 +734,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
Path = info.BackdropImageUrl,
|
||||
Type = ImageType.Backdrop
|
||||
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -771,7 +771,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
|
||||
|
||||
var list = new List<Tuple<BaseItemDto, string, string>>() {
|
||||
var list = new List<Tuple<BaseItemDto, string, string>>
|
||||
{
|
||||
new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId)
|
||||
};
|
||||
|
||||
@@ -788,22 +789,12 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (query.OrderBy.Count == 0)
|
||||
{
|
||||
if (query.IsAiring ?? false)
|
||||
|
||||
// Unless something else was specified, order by start date to take advantage of a specialized index
|
||||
query.OrderBy = new[]
|
||||
{
|
||||
// Unless something else was specified, order by start date to take advantage of a specialized index
|
||||
query.OrderBy = new[]
|
||||
{
|
||||
(ItemSortBy.StartDate, SortOrder.Ascending)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unless something else was specified, order by start date to take advantage of a specialized index
|
||||
query.OrderBy = new[]
|
||||
{
|
||||
(ItemSortBy.StartDate, SortOrder.Ascending)
|
||||
};
|
||||
}
|
||||
(ItemSortBy.StartDate, SortOrder.Ascending)
|
||||
};
|
||||
}
|
||||
|
||||
RemoveFields(options);
|
||||
@@ -1189,7 +1180,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
ChannelIds = new Guid[] { currentChannel.Id },
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
|
||||
|
||||
var newPrograms = new List<LiveTvProgram>();
|
||||
@@ -1389,10 +1379,10 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
// limit = (query.Limit ?? 10) * 2;
|
||||
limit = null;
|
||||
|
||||
//var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
|
||||
//var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
|
||||
// var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
|
||||
// var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
|
||||
|
||||
//return new QueryResult<BaseItem>
|
||||
// return new QueryResult<BaseItem>
|
||||
//{
|
||||
// Items = items,
|
||||
// TotalRecordCount = items.Length
|
||||
@@ -1734,13 +1724,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (!(service is EmbyTV.EmbyTV))
|
||||
{
|
||||
TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>
|
||||
{
|
||||
Argument = new TimerEventInfo
|
||||
{
|
||||
Id = id
|
||||
}
|
||||
});
|
||||
TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1757,13 +1741,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
SeriesTimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>
|
||||
{
|
||||
Argument = new TimerEventInfo
|
||||
{
|
||||
Id = id
|
||||
}
|
||||
});
|
||||
SeriesTimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
|
||||
}
|
||||
|
||||
public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
|
||||
@@ -1771,7 +1749,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
var results = await GetTimers(new TimerQuery
|
||||
{
|
||||
Id = id
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -1823,7 +1800,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
.Select(i =>
|
||||
{
|
||||
return i.Item1;
|
||||
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
@@ -1878,7 +1854,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
}
|
||||
|
||||
return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName);
|
||||
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
@@ -1911,7 +1886,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) },
|
||||
TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id },
|
||||
DtoOptions = options
|
||||
|
||||
}) : new List<BaseItem>();
|
||||
|
||||
RemoveFields(options);
|
||||
@@ -1989,7 +1963,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
OriginalAirDate = program.PremiereDate,
|
||||
Overview = program.Overview,
|
||||
StartDate = program.StartDate,
|
||||
//ImagePath = program.ExternalImagePath,
|
||||
// ImagePath = program.ExternalImagePath,
|
||||
Name = program.Name,
|
||||
OfficialRating = program.OfficialRating
|
||||
};
|
||||
@@ -2082,14 +2056,11 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (!(service is EmbyTV.EmbyTV))
|
||||
{
|
||||
TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>
|
||||
{
|
||||
Argument = new TimerEventInfo
|
||||
TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
|
||||
new TimerEventInfo(newTimerId)
|
||||
{
|
||||
ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId),
|
||||
Id = newTimerId
|
||||
}
|
||||
});
|
||||
ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2114,14 +2085,11 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
SeriesTimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>
|
||||
{
|
||||
Argument = new TimerEventInfo
|
||||
SeriesTimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
|
||||
new TimerEventInfo(newTimerId)
|
||||
{
|
||||
ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId),
|
||||
Id = newTimerId
|
||||
}
|
||||
});
|
||||
ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
|
||||
}));
|
||||
}
|
||||
|
||||
public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
|
||||
@@ -2206,20 +2174,19 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
var info = new LiveTvInfo
|
||||
{
|
||||
Services = services,
|
||||
IsEnabled = services.Length > 0
|
||||
IsEnabled = services.Length > 0,
|
||||
EnabledUsers = _userManager.Users
|
||||
.Where(IsLiveTvEnabled)
|
||||
.Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
info.EnabledUsers = _userManager.Users
|
||||
.Where(IsLiveTvEnabled)
|
||||
.Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
|
||||
.ToArray();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private bool IsLiveTvEnabled(User user)
|
||||
{
|
||||
return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0);
|
||||
return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0);
|
||||
}
|
||||
|
||||
public IEnumerable<User> GetEnabledUsers()
|
||||
@@ -2496,7 +2463,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
UserId = user.Id,
|
||||
IsRecordingsFolder = true,
|
||||
RefreshLatestChannelItems = refreshChannels
|
||||
|
||||
}).Items);
|
||||
|
||||
return folders.Cast<BaseItem>().ToList();
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
private const string StreamIdDelimeterString = "_";
|
||||
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<LiveTvMediaSourceProvider> _logger;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
@@ -33,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run
|
||||
/// Creates the triggers that define when the task will run.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
|
||||
@@ -22,14 +22,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
public abstract class BaseTunerHost
|
||||
{
|
||||
protected readonly IServerConfigurationManager Config;
|
||||
protected readonly ILogger Logger;
|
||||
protected readonly ILogger<BaseTunerHost> Logger;
|
||||
protected IJsonSerializer JsonSerializer;
|
||||
protected readonly IFileSystem FileSystem;
|
||||
|
||||
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
|
||||
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
|
||||
protected BaseTunerHost(IServerConfigurationManager config, ILogger<BaseTunerHost> logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
|
||||
{
|
||||
Config = config;
|
||||
Logger = logger;
|
||||
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
|
||||
var list = result.ToList();
|
||||
//logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
|
||||
// logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
|
||||
|
||||
if (!string.IsNullOrEmpty(key) && list.Count > 0)
|
||||
{
|
||||
@@ -99,7 +99,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +115,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
ChannelType = ChannelType.TV,
|
||||
IsLegacyTuner = (i.URL ?? string.Empty).StartsWith("hdhomerun", StringComparison.OrdinalIgnoreCase),
|
||||
Path = i.URL
|
||||
|
||||
}).Cast<ChannelInfo>().ToList();
|
||||
}
|
||||
|
||||
@@ -171,6 +170,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
_modelCache[cacheKey] = response;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
|
||||
var name = line.Substring(0, index - 1);
|
||||
var currentChannel = line.Substring(index + 7);
|
||||
if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
|
||||
if (currentChannel != "none")
|
||||
{
|
||||
status = LiveTvTunerStatus.LiveTv;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = LiveTvTunerStatus.Available;
|
||||
}
|
||||
|
||||
tuners.Add(new LiveTvTunerInfo
|
||||
{
|
||||
Name = name,
|
||||
@@ -230,11 +238,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
inside = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (let == '>')
|
||||
{
|
||||
inside = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inside)
|
||||
{
|
||||
buffer[bufferIndex] = let;
|
||||
@@ -332,12 +342,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
private class Channels
|
||||
{
|
||||
public string GuideNumber { get; set; }
|
||||
|
||||
public string GuideName { get; set; }
|
||||
|
||||
public string VideoCodec { get; set; }
|
||||
|
||||
public string AudioCodec { get; set; }
|
||||
|
||||
public string URL { get; set; }
|
||||
|
||||
public bool Favorite { get; set; }
|
||||
|
||||
public bool DRM { get; set; }
|
||||
|
||||
public int HD { get; set; }
|
||||
}
|
||||
|
||||
@@ -481,7 +498,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
Height = height,
|
||||
BitRate = videoBitrate,
|
||||
NalLengthSize = nal
|
||||
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
@@ -502,8 +518,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
SupportsTranscoding = true,
|
||||
IsInfiniteStream = true,
|
||||
IgnoreDts = true,
|
||||
//IgnoreIndex = true,
|
||||
//ReadAtNativeFramerate = true
|
||||
// IgnoreIndex = true,
|
||||
// ReadAtNativeFramerate = true
|
||||
};
|
||||
|
||||
mediaSource.InferTotalBitrate();
|
||||
@@ -659,13 +675,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
public class DiscoverResponse
|
||||
{
|
||||
public string FriendlyName { get; set; }
|
||||
|
||||
public string ModelNumber { get; set; }
|
||||
|
||||
public string FirmwareName { get; set; }
|
||||
|
||||
public string FirmwareVersion { get; set; }
|
||||
|
||||
public string DeviceID { get; set; }
|
||||
|
||||
public string DeviceAuth { get; set; }
|
||||
|
||||
public string BaseURL { get; set; }
|
||||
|
||||
public string LineupURL { get; set; }
|
||||
|
||||
public int TunerCount { get; set; }
|
||||
|
||||
public bool SupportsTranscoding
|
||||
@@ -674,7 +698,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
var model = ModelNumber ?? string.Empty;
|
||||
|
||||
if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
|
||||
if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -722,7 +746,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
@@ -117,17 +117,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
taskCompletionSource,
|
||||
LiveStreamCancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
//OpenedMediaSource.Path = tempFile;
|
||||
//OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||
// OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
// OpenedMediaSource.Path = tempFile;
|
||||
// OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||
|
||||
MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||
MediaSource.Protocol = MediaProtocol.Http;
|
||||
//OpenedMediaSource.SupportsDirectPlay = false;
|
||||
//OpenedMediaSource.SupportsDirectStream = true;
|
||||
//OpenedMediaSource.SupportsTranscoding = true;
|
||||
// OpenedMediaSource.SupportsDirectPlay = false;
|
||||
// OpenedMediaSource.SupportsDirectStream = true;
|
||||
// OpenedMediaSource.SupportsTranscoding = true;
|
||||
|
||||
//await Task.Delay(5000).ConfigureAwait(false);
|
||||
// await Task.Delay(5000).ConfigureAwait(false);
|
||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,12 +58,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
protected virtual int EmptyReadLimit => 1000;
|
||||
|
||||
public MediaSourceInfo OriginalMediaSource { get; set; }
|
||||
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
|
||||
public int ConsumerCount { get; set; }
|
||||
|
||||
public string OriginalStreamId { get; set; }
|
||||
|
||||
public bool EnableStreamSharing { get; set; }
|
||||
|
||||
public string UniqueId { get; }
|
||||
|
||||
public string TunerHostId { get; }
|
||||
@@ -220,11 +223,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -127,7 +127,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,7 +210,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
@@ -284,7 +283,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
||||
{
|
||||
//channel.Number = number.ToString();
|
||||
// channel.Number = number.ToString();
|
||||
nameInExtInf = nameInExtInf.Substring(numberIndex + 1).Trim(new[] { ' ', '-' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,21 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
|
||||
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
//OpenedMediaSource.Path = tempFile;
|
||||
//OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||
// OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
// OpenedMediaSource.Path = tempFile;
|
||||
// OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||
|
||||
MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||
MediaSource.Protocol = MediaProtocol.Http;
|
||||
|
||||
//OpenedMediaSource.Path = TempFilePath;
|
||||
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
// OpenedMediaSource.Path = TempFilePath;
|
||||
// OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
|
||||
//OpenedMediaSource.Path = _tempFilePath;
|
||||
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
//OpenedMediaSource.SupportsDirectPlay = false;
|
||||
//OpenedMediaSource.SupportsDirectStream = true;
|
||||
//OpenedMediaSource.SupportsTranscoding = true;
|
||||
// OpenedMediaSource.Path = _tempFilePath;
|
||||
// OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
// OpenedMediaSource.SupportsDirectPlay = false;
|
||||
// OpenedMediaSource.SupportsDirectStream = true;
|
||||
// OpenedMediaSource.SupportsTranscoding = true;
|
||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
if (taskCompletionSource.Task.Exception != null)
|
||||
{
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
"Sync": "Sinkroniseer",
|
||||
"HeaderFavoriteSongs": "Gunsteling Liedjies",
|
||||
"Songs": "Liedjies",
|
||||
"DeviceOnlineWithName": "{0} is verbind",
|
||||
"DeviceOfflineWithName": "{0} het afgesluit",
|
||||
"DeviceOnlineWithName": "{0} gekoppel is",
|
||||
"DeviceOfflineWithName": "{0} is ontkoppel",
|
||||
"Collections": "Versamelings",
|
||||
"Inherit": "Ontvang",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
@@ -91,5 +91,9 @@
|
||||
"ChapterNameValue": "Hoofstuk",
|
||||
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
|
||||
"Albums": "Albums"
|
||||
"Albums": "Albums",
|
||||
"TasksChannelsCategory": "Internet kanale",
|
||||
"TasksApplicationCategory": "aansoek",
|
||||
"TasksLibraryCategory": "biblioteek",
|
||||
"TasksMaintenanceCategory": "onderhoud"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Albums": "ألبومات",
|
||||
"Albums": "البومات",
|
||||
"AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
|
||||
"Application": "تطبيق",
|
||||
"Artists": "الفنانين",
|
||||
@@ -14,7 +14,7 @@
|
||||
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
|
||||
"Favorites": "المفضلة",
|
||||
"Folders": "المجلدات",
|
||||
"Genres": "الأنواع",
|
||||
"Genres": "التضنيفات",
|
||||
"HeaderAlbumArtists": "فناني الألبومات",
|
||||
"HeaderCameraUploads": "تحميلات الكاميرا",
|
||||
"HeaderContinueWatching": "استئناف",
|
||||
@@ -50,7 +50,7 @@
|
||||
"NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
|
||||
"NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي",
|
||||
"NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا",
|
||||
"NotificationOptionInstallationFailed": "فشل في التثبيت",
|
||||
"NotificationOptionInstallationFailed": "فشل التثبيت",
|
||||
"NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد",
|
||||
"NotificationOptionPluginError": "فشل في البرنامج المضاف",
|
||||
"NotificationOptionPluginInstalled": "تم تثبيت الملحق",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user