mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-09 20:02:11 +01:00
Merge remote-tracking branch 'upstream/master' into random
This commit is contained in:
@@ -616,8 +616,8 @@ namespace Emby.Server.Implementations.Activity
|
||||
/// <summary>
|
||||
/// Constructs a string description of a time-span value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value of this item</param>
|
||||
/// <param name="description">The name of this item (singular form)</param>
|
||||
/// <param name="value">The value of this item.</param>
|
||||
/// <param name="description">The name of this item (singular form).</param>
|
||||
private static string CreateValueString(int value, string description)
|
||||
{
|
||||
return string.Format(
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
@@ -16,7 +15,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Server.Implementations.AppBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Class BaseConfigurationManager
|
||||
/// Class BaseConfigurationManager.
|
||||
/// </summary>
|
||||
public abstract class BaseConfigurationManager : IConfigurationManager
|
||||
{
|
||||
@@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// <summary>
|
||||
/// The _configuration sync lock.
|
||||
/// </summary>
|
||||
private object _configurationSyncLock = new object();
|
||||
private readonly object _configurationSyncLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The _configuration.
|
||||
@@ -48,7 +47,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// <param name="applicationPaths">The application paths.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
/// <param name="xmlSerializer">The XML serializer.</param>
|
||||
/// <param name="fileSystem">The file system</param>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||
{
|
||||
CommonApplicationPaths = applicationPaths;
|
||||
@@ -85,6 +84,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
protected ILogger Logger { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XML serializer.
|
||||
/// </summary>
|
||||
@@ -92,23 +92,39 @@ namespace Emby.Server.Implementations.AppBase
|
||||
protected IXmlSerializer XmlSerializer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application paths.
|
||||
/// Gets the application paths.
|
||||
/// </summary>
|
||||
/// <value>The application paths.</value>
|
||||
public IApplicationPaths CommonApplicationPaths { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system configuration
|
||||
/// Gets or sets the system configuration.
|
||||
/// </summary>
|
||||
/// <value>The configuration.</value>
|
||||
public BaseApplicationConfiguration CommonConfiguration
|
||||
{
|
||||
get
|
||||
{
|
||||
// Lazy load
|
||||
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
|
||||
return _configuration;
|
||||
if (_configurationLoaded)
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
|
||||
lock (_configurationSyncLock)
|
||||
{
|
||||
if (_configurationLoaded)
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
|
||||
_configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer);
|
||||
|
||||
_configurationLoaded = true;
|
||||
|
||||
return _configuration;
|
||||
}
|
||||
}
|
||||
|
||||
protected set
|
||||
{
|
||||
_configuration = value;
|
||||
@@ -158,7 +174,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// Replaces the configuration.
|
||||
/// </summary>
|
||||
/// <param name="newConfiguration">The new configuration.</param>
|
||||
/// <exception cref="ArgumentNullException">newConfiguration</exception>
|
||||
/// <exception cref="ArgumentNullException"><c>newConfiguration</c> is <c>null</c>.</exception>
|
||||
public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
|
||||
{
|
||||
if (newConfiguration == null)
|
||||
@@ -201,7 +217,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
cachePath = CommonConfiguration.CachePath;
|
||||
}
|
||||
|
||||
Logger.LogInformation("Setting cache path to " + cachePath);
|
||||
Logger.LogInformation("Setting cache path: {Path}", cachePath);
|
||||
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
|
||||
}
|
||||
|
||||
@@ -209,7 +225,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// Replaces the cache path.
|
||||
/// </summary>
|
||||
/// <param name="newConfig">The new configuration.</param>
|
||||
/// <exception cref="DirectoryNotFoundException"></exception>
|
||||
/// <exception cref="DirectoryNotFoundException">The new cache path doesn't exist.</exception>
|
||||
private void ValidateCachePath(BaseApplicationConfiguration newConfig)
|
||||
{
|
||||
var newPath = newConfig.CachePath;
|
||||
@@ -220,7 +236,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
// Validate
|
||||
if (!Directory.Exists(newPath))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
throw new DirectoryNotFoundException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} does not exist.",
|
||||
@@ -299,8 +315,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
throw new ArgumentException("Expected configuration type is " + configurationType.Name);
|
||||
}
|
||||
|
||||
var validatingStore = configurationStore as IValidatingConfiguration;
|
||||
if (validatingStore != null)
|
||||
if (configurationStore is IValidatingConfiguration validatingStore)
|
||||
{
|
||||
var currentConfiguration = GetConfiguration(key);
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ using MediaBrowser.Model.Serialization;
|
||||
namespace Emby.Server.Implementations.AppBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ConfigurationHelper
|
||||
/// Class ConfigurationHelper.
|
||||
/// </summary>
|
||||
public static class ConfigurationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads an xml configuration file from the file system
|
||||
/// It will immediately re-serialize and save if new serialization data is available due to property changes
|
||||
/// It will immediately re-serialize and save if new serialization data is available due to property changes.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="path">The path.</param>
|
||||
|
||||
@@ -88,7 +88,6 @@ using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
@@ -110,9 +109,8 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ServiceStack;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
@@ -232,7 +230,25 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
protected IServiceProvider _serviceProvider;
|
||||
/// <summary>
|
||||
/// Gets or sets the service provider.
|
||||
/// </summary>
|
||||
public IServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the http port for the webhost.
|
||||
/// </summary>
|
||||
public int HttpPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the https port for the webhost.
|
||||
/// </summary>
|
||||
public int HttpsPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content root for the webhost.
|
||||
/// </summary>
|
||||
public string ContentRoot { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server configuration manager.
|
||||
@@ -321,7 +337,7 @@ namespace Emby.Server.Implementations
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the installation manager.
|
||||
/// Gets the installation manager.
|
||||
/// </summary>
|
||||
/// <value>The installation manager.</value>
|
||||
protected IInstallationManager InstallationManager { get; private set; }
|
||||
@@ -362,7 +378,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
_configuration = configuration;
|
||||
|
||||
XmlSerializer = new MyXmlSerializer(fileSystem, loggerFactory);
|
||||
XmlSerializer = new MyXmlSerializer();
|
||||
|
||||
NetworkManager = networkManager;
|
||||
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
||||
@@ -410,13 +426,17 @@ namespace Emby.Server.Implementations
|
||||
_validAddressResults.Clear();
|
||||
}
|
||||
|
||||
public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
||||
/// <inheritdoc />
|
||||
public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application user agent.
|
||||
/// </summary>
|
||||
/// <value>The application user agent.</value>
|
||||
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion;
|
||||
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the email address for use within a comment section of a user agent field.
|
||||
@@ -452,20 +472,20 @@ namespace Emby.Server.Implementations
|
||||
public string Name => ApplicationProductName;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependencies
|
||||
/// Creates an instance of type and resolves all constructor dependencies.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public object CreateInstance(Type type)
|
||||
=> ActivatorUtilities.CreateInstance(_serviceProvider, type);
|
||||
=> ActivatorUtilities.CreateInstance(ServiceProvider, type);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependencies
|
||||
/// Creates an instance of type and resolves all constructor dependencies.
|
||||
/// </summary>
|
||||
/// /// <typeparam name="T">The type.</typeparam>
|
||||
/// <returns>T.</returns>
|
||||
public T CreateInstance<T>()
|
||||
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
|
||||
=> ActivatorUtilities.CreateInstance<T>(ServiceProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the instance safe.
|
||||
@@ -477,7 +497,7 @@ namespace Emby.Server.Implementations
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("Creating instance of {Type}", type);
|
||||
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
|
||||
return ActivatorUtilities.CreateInstance(ServiceProvider, type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -491,12 +511,12 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type</typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T Resolve<T>() => _serviceProvider.GetService<T>();
|
||||
public T Resolve<T>() => ServiceProvider.GetService<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the export types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type</typeparam>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <returns>IEnumerable{Type}.</returns>
|
||||
public IEnumerable<Type> GetExportTypes<T>()
|
||||
{
|
||||
@@ -508,11 +528,12 @@ namespace Emby.Server.Implementations
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
|
||||
{
|
||||
// Convert to list so this isn't executed for each iteration
|
||||
var parts = GetExportTypes<T>()
|
||||
.Select(CreateInstanceSafe)
|
||||
.Where(i => i != null)
|
||||
.Cast<T>()
|
||||
.ToList(); // Convert to list so this isn't executed for each iteration
|
||||
.ToList();
|
||||
|
||||
if (manageLifetime)
|
||||
{
|
||||
@@ -607,77 +628,14 @@ namespace Emby.Server.Implementations
|
||||
|
||||
await RegisterResources(serviceCollection).ConfigureAwait(false);
|
||||
|
||||
FindParts();
|
||||
|
||||
string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
|
||||
if (string.IsNullOrEmpty(contentRoot))
|
||||
ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
|
||||
if (string.IsNullOrEmpty(ContentRoot))
|
||||
{
|
||||
contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
|
||||
}
|
||||
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
var addresses = ServerConfigurationManager
|
||||
.Configuration
|
||||
.LocalNetworkAddresses
|
||||
.Select(NormalizeConfiguredLocalAddress)
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
if (addresses.Any())
|
||||
{
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
Logger.LogInformation("Kestrel listening on {ipaddr}", address);
|
||||
options.Listen(address, HttpPort);
|
||||
|
||||
if (EnableHttps && Certificate != null)
|
||||
{
|
||||
options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation("Kestrel listening on all interfaces");
|
||||
options.ListenAnyIP(HttpPort);
|
||||
|
||||
if (EnableHttps && Certificate != null)
|
||||
{
|
||||
options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
|
||||
}
|
||||
}
|
||||
})
|
||||
.UseContentRoot(contentRoot)
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddResponseCompression();
|
||||
services.AddHttpContextAccessor();
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseWebSockets();
|
||||
|
||||
app.UseResponseCompression();
|
||||
|
||||
// TODO app.UseMiddleware<WebSocketMiddleware>();
|
||||
app.Use(ExecuteWebsocketHandlerAsync);
|
||||
app.Use(ExecuteHttpHandlerAsync);
|
||||
})
|
||||
.Build();
|
||||
|
||||
try
|
||||
{
|
||||
await host.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again.");
|
||||
throw;
|
||||
ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
|
||||
public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
@@ -688,7 +646,7 @@ namespace Emby.Server.Implementations
|
||||
await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
|
||||
public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
|
||||
{
|
||||
if (context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
@@ -749,7 +707,8 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
||||
|
||||
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
|
||||
var cryptoProvider = new CryptographyProvider();
|
||||
serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
|
||||
|
||||
SocketFactory = new SocketFactory();
|
||||
serviceCollection.AddSingleton(SocketFactory);
|
||||
@@ -788,7 +747,17 @@ namespace Emby.Server.Implementations
|
||||
|
||||
_userRepository = GetUserRepository();
|
||||
|
||||
UserManager = new UserManager(LoggerFactory.CreateLogger<UserManager>(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
|
||||
UserManager = new UserManager(
|
||||
LoggerFactory.CreateLogger<UserManager>(),
|
||||
_userRepository,
|
||||
XmlSerializer,
|
||||
NetworkManager,
|
||||
() => ImageProcessor,
|
||||
() => DtoService,
|
||||
this,
|
||||
JsonSerializer,
|
||||
FileSystemManager,
|
||||
cryptoProvider);
|
||||
|
||||
serviceCollection.AddSingleton(UserManager);
|
||||
|
||||
@@ -866,8 +835,7 @@ namespace Emby.Server.Implementations
|
||||
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
||||
serviceCollection.AddSingleton(NotificationManager);
|
||||
|
||||
serviceCollection.AddSingleton<IDeviceDiscovery>(
|
||||
new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
|
||||
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
|
||||
|
||||
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
||||
serviceCollection.AddSingleton(ChapterManager);
|
||||
@@ -896,7 +864,7 @@ namespace Emby.Server.Implementations
|
||||
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
||||
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
||||
|
||||
AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager);
|
||||
AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
|
||||
serviceCollection.AddSingleton(AuthService);
|
||||
|
||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
|
||||
@@ -906,7 +874,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
_displayPreferencesRepository.Initialize();
|
||||
|
||||
var userDataRepo = new SqliteUserDataRepository(LoggerFactory, ApplicationPaths);
|
||||
var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths);
|
||||
|
||||
SetStaticProperties();
|
||||
|
||||
@@ -915,8 +883,6 @@ namespace Emby.Server.Implementations
|
||||
((UserDataManager)UserDataManager).Repository = userDataRepo;
|
||||
ItemRepository.Initialize(userDataRepo, UserManager);
|
||||
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
|
||||
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
||||
@@ -1007,7 +973,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirty hacks
|
||||
/// Dirty hacks.
|
||||
/// </summary>
|
||||
private void SetStaticProperties()
|
||||
{
|
||||
@@ -1073,9 +1039,9 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Finds the parts.
|
||||
/// </summary>
|
||||
protected void FindParts()
|
||||
public void FindParts()
|
||||
{
|
||||
InstallationManager = _serviceProvider.GetService<IInstallationManager>();
|
||||
InstallationManager = ServiceProvider.GetService<IInstallationManager>();
|
||||
InstallationManager.PluginInstalled += PluginInstalled;
|
||||
|
||||
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
|
||||
@@ -1204,7 +1170,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
private CertificateInfo CertificateInfo { get; set; }
|
||||
|
||||
protected X509Certificate2 Certificate { get; private set; }
|
||||
public X509Certificate2 Certificate { get; private set; }
|
||||
|
||||
private IEnumerable<string> GetUrlPrefixes()
|
||||
{
|
||||
@@ -1415,17 +1381,18 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Gets the system status.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>SystemInfo.</returns>
|
||||
public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
|
||||
{
|
||||
var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
||||
var transcodingTempPath = ConfigurationManager.GetTranscodePath();
|
||||
|
||||
return new SystemInfo
|
||||
{
|
||||
HasPendingRestart = HasPendingRestart,
|
||||
IsShuttingDown = IsShuttingDown,
|
||||
Version = ApplicationVersion,
|
||||
Version = ApplicationVersionString,
|
||||
WebSocketPortNumber = HttpPort,
|
||||
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
|
||||
Id = SystemId,
|
||||
@@ -1443,7 +1410,7 @@ namespace Emby.Server.Implementations
|
||||
CanSelfRestart = CanSelfRestart,
|
||||
CanLaunchWebBrowser = CanLaunchWebBrowser,
|
||||
HasUpdateAvailable = HasUpdateAvailable,
|
||||
TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
|
||||
TranscodingTempPath = transcodingTempPath,
|
||||
ServerName = FriendlyName,
|
||||
LocalAddress = localAddress,
|
||||
SupportsLibraryMonitor = true,
|
||||
@@ -1465,7 +1432,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
return new PublicSystemInfo
|
||||
{
|
||||
Version = ApplicationVersion,
|
||||
Version = ApplicationVersionString,
|
||||
ProductName = ApplicationProductName,
|
||||
Id = SystemId,
|
||||
OperatingSystem = OperatingSystem.Id.ToString(),
|
||||
@@ -1588,7 +1555,7 @@ namespace Emby.Server.Implementations
|
||||
return resultList;
|
||||
}
|
||||
|
||||
private IPAddress NormalizeConfiguredLocalAddress(string address)
|
||||
public IPAddress NormalizeConfiguredLocalAddress(string address)
|
||||
{
|
||||
var index = address.Trim('/').IndexOf('/');
|
||||
|
||||
@@ -1664,10 +1631,6 @@ namespace Emby.Server.Implementations
|
||||
? Environment.MachineName
|
||||
: ServerConfigurationManager.Configuration.ServerName;
|
||||
|
||||
public int HttpPort { get; private set; }
|
||||
|
||||
public int HttpsPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down.
|
||||
/// </summary>
|
||||
@@ -1730,7 +1693,7 @@ namespace Emby.Server.Implementations
|
||||
/// dns is prefixed with a valid Uri prefix.
|
||||
/// </summary>
|
||||
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
|
||||
/// <returns>The hostname in <paramref name="externalDns"/></returns>
|
||||
/// <returns>The hostname in <paramref name="externalDns"/>.</returns>
|
||||
private static string GetHostnameFromExternalDns(string externalDns)
|
||||
{
|
||||
if (string.IsNullOrEmpty(externalDns))
|
||||
@@ -1844,6 +1807,7 @@ namespace Emby.Server.Implementations
|
||||
internal class CertificateInfo
|
||||
{
|
||||
public string Path { get; set; }
|
||||
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,10 +470,10 @@ namespace Emby.Server.Implementations.Channels
|
||||
_libraryManager.CreateItem(item, null);
|
||||
}
|
||||
|
||||
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
ForceSave = !isNew && forceUpdate
|
||||
}, cancellationToken);
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -636,7 +636,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var internalChannel = await GetChannel(channel, cancellationToken);
|
||||
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var query = new InternalItemsQuery();
|
||||
query.Parent = internalChannel;
|
||||
@@ -1156,7 +1156,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
|
||||
{
|
||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
|
||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
// This could cause it to get re-resolved as a plain folder
|
||||
var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
|
||||
|
||||
var parentFolder = GetCollectionsFolder(true).Result;
|
||||
var parentFolder = GetCollectionsFolder(true).GetAwaiter().GetResult();
|
||||
|
||||
if (parentFolder == null)
|
||||
{
|
||||
@@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
|
||||
if (options.ItemIdList.Length > 0)
|
||||
{
|
||||
AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
// The initial adding of items is going to create a local metadata file
|
||||
// This will cause internet metadata to be skipped as a result
|
||||
@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
}
|
||||
else
|
||||
{
|
||||
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.High);
|
||||
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
|
||||
}
|
||||
|
||||
CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs
|
||||
@@ -178,12 +178,12 @@ namespace Emby.Server.Implementations.Collections
|
||||
|
||||
public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
|
||||
{
|
||||
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
|
||||
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
|
||||
}
|
||||
|
||||
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
|
||||
{
|
||||
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
|
||||
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
|
||||
}
|
||||
|
||||
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
|
||||
@@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
}
|
||||
|
||||
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
|
||||
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
ForceSave = true
|
||||
}, RefreshPriority.High);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Emby.Server.Implementations.AppBase;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
@@ -14,7 +13,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Server.Implementations.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ServerConfigurationManager
|
||||
/// Class ServerConfigurationManager.
|
||||
/// </summary>
|
||||
public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager
|
||||
{
|
||||
@@ -62,13 +61,6 @@ namespace Emby.Server.Implementations.Configuration
|
||||
base.OnConfigurationUpdated();
|
||||
}
|
||||
|
||||
public override void AddParts(IEnumerable<IConfigurationFactory> factories)
|
||||
{
|
||||
base.AddParts(factories);
|
||||
|
||||
UpdateTranscodePath();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the metadata path.
|
||||
/// </summary>
|
||||
@@ -84,28 +76,6 @@ namespace Emby.Server.Implementations.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the transcoding temporary path.
|
||||
/// </summary>
|
||||
private void UpdateTranscodePath()
|
||||
{
|
||||
var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding");
|
||||
|
||||
((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ?
|
||||
null :
|
||||
Path.Combine(encodingConfig.TranscodingTempPath, "transcodes");
|
||||
}
|
||||
|
||||
protected override void OnNamedConfigurationUpdated(string key, object configuration)
|
||||
{
|
||||
base.OnNamedConfigurationUpdated(key, configuration);
|
||||
|
||||
if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
UpdateTranscodePath();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the configuration.
|
||||
/// </summary>
|
||||
@@ -123,7 +93,6 @@ namespace Emby.Server.Implementations.Configuration
|
||||
base.ReplaceConfiguration(newConfiguration);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Validates the SSL certificate.
|
||||
/// </summary>
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace Emby.Server.Implementations.Cryptography
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CryptographyProvider"/> class.
|
||||
/// </summary>
|
||||
public CryptographyProvider()
|
||||
{
|
||||
// FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
|
||||
@@ -59,12 +62,6 @@ namespace Emby.Server.Implementations.Cryptography
|
||||
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
|
||||
}
|
||||
|
||||
public byte[] ComputeHash(string hashMethod, byte[] bytes)
|
||||
=> ComputeHash(hashMethod, bytes, Array.Empty<byte>());
|
||||
|
||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
|
||||
=> ComputeHash(DefaultHashMethod, bytes);
|
||||
|
||||
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
|
||||
{
|
||||
if (hashMethod == DefaultHashMethod)
|
||||
@@ -90,7 +87,6 @@ namespace Emby.Server.Implementations.Cryptography
|
||||
}
|
||||
|
||||
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
||||
|
||||
}
|
||||
|
||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
|
||||
|
||||
@@ -110,8 +110,8 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
|
||||
{
|
||||
statement.TryBind("@id", displayPreferences.Id.ToGuidBlob());
|
||||
statement.TryBind("@userId", userId.ToGuidBlob());
|
||||
statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
statement.TryBind("@client", client);
|
||||
statement.TryBind("@data", serialized);
|
||||
|
||||
@@ -170,8 +170,8 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
|
||||
{
|
||||
statement.TryBind("@id", guidId.ToGuidBlob());
|
||||
statement.TryBind("@userId", userId.ToGuidBlob());
|
||||
statement.TryBind("@id", guidId.ToByteArray());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
statement.TryBind("@client", client);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
@@ -200,7 +200,7 @@ namespace Emby.Server.Implementations.Data
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
|
||||
{
|
||||
statement.TryBind("@userId", userId.ToGuidBlob());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
|
||||
@@ -9,6 +9,47 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
public static class SqliteExtensions
|
||||
{
|
||||
private const string DatetimeFormatUtc = "yyyy-MM-dd HH:mm:ss.FFFFFFFK";
|
||||
private const string DatetimeFormatLocal = "yyyy-MM-dd HH:mm:ss.FFFFFFF";
|
||||
|
||||
/// <summary>
|
||||
/// An array of ISO-8601 DateTime formats that we support parsing.
|
||||
/// </summary>
|
||||
private static readonly string[] _datetimeFormats = new string[]
|
||||
{
|
||||
"THHmmssK",
|
||||
"THHmmK",
|
||||
"HH:mm:ss.FFFFFFFK",
|
||||
"HH:mm:ssK",
|
||||
"HH:mmK",
|
||||
DatetimeFormatUtc,
|
||||
"yyyy-MM-dd HH:mm:ssK",
|
||||
"yyyy-MM-dd HH:mmK",
|
||||
"yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
|
||||
"yyyy-MM-ddTHH:mmK",
|
||||
"yyyy-MM-ddTHH:mm:ssK",
|
||||
"yyyyMMddHHmmssK",
|
||||
"yyyyMMddHHmmK",
|
||||
"yyyyMMddTHHmmssFFFFFFFK",
|
||||
"THHmmss",
|
||||
"THHmm",
|
||||
"HH:mm:ss.FFFFFFF",
|
||||
"HH:mm:ss",
|
||||
"HH:mm",
|
||||
DatetimeFormatLocal,
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
"yyyy-MM-dd HH:mm",
|
||||
"yyyy-MM-ddTHH:mm:ss.FFFFFFF",
|
||||
"yyyy-MM-ddTHH:mm",
|
||||
"yyyy-MM-ddTHH:mm:ss",
|
||||
"yyyyMMddHHmmss",
|
||||
"yyyyMMddHHmm",
|
||||
"yyyyMMddTHHmmssFFFFFFF",
|
||||
"yyyy-MM-dd",
|
||||
"yyyyMMdd",
|
||||
"yy-MM-dd"
|
||||
};
|
||||
|
||||
public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries)
|
||||
{
|
||||
if (queries == null)
|
||||
@@ -22,16 +63,6 @@ namespace Emby.Server.Implementations.Data
|
||||
});
|
||||
}
|
||||
|
||||
public static byte[] ToGuidBlob(this string str)
|
||||
{
|
||||
return ToGuidBlob(new Guid(str));
|
||||
}
|
||||
|
||||
public static byte[] ToGuidBlob(this Guid guid)
|
||||
{
|
||||
return guid.ToByteArray();
|
||||
}
|
||||
|
||||
public static Guid ReadGuidFromBlob(this IResultSetValue result)
|
||||
{
|
||||
return new Guid(result.ToBlob());
|
||||
@@ -50,58 +81,16 @@ namespace Emby.Server.Implementations.Data
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static string GetDateTimeKindFormat(
|
||||
DateTimeKind kind)
|
||||
{
|
||||
return (kind == DateTimeKind.Utc) ? _datetimeFormatUtc : _datetimeFormatLocal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An array of ISO-8601 DateTime formats that we support parsing.
|
||||
/// </summary>
|
||||
private static string[] _datetimeFormats = new string[] {
|
||||
"THHmmssK",
|
||||
"THHmmK",
|
||||
"HH:mm:ss.FFFFFFFK",
|
||||
"HH:mm:ssK",
|
||||
"HH:mmK",
|
||||
"yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (5). */
|
||||
"yyyy-MM-dd HH:mm:ssK",
|
||||
"yyyy-MM-dd HH:mmK",
|
||||
"yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
|
||||
"yyyy-MM-ddTHH:mmK",
|
||||
"yyyy-MM-ddTHH:mm:ssK",
|
||||
"yyyyMMddHHmmssK",
|
||||
"yyyyMMddHHmmK",
|
||||
"yyyyMMddTHHmmssFFFFFFFK",
|
||||
"THHmmss",
|
||||
"THHmm",
|
||||
"HH:mm:ss.FFFFFFF",
|
||||
"HH:mm:ss",
|
||||
"HH:mm",
|
||||
"yyyy-MM-dd HH:mm:ss.FFFFFFF", /* NOTE: Non-UTC default (19). */
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
"yyyy-MM-dd HH:mm",
|
||||
"yyyy-MM-ddTHH:mm:ss.FFFFFFF",
|
||||
"yyyy-MM-ddTHH:mm",
|
||||
"yyyy-MM-ddTHH:mm:ss",
|
||||
"yyyyMMddHHmmss",
|
||||
"yyyyMMddHHmm",
|
||||
"yyyyMMddTHHmmssFFFFFFF",
|
||||
"yyyy-MM-dd",
|
||||
"yyyyMMdd",
|
||||
"yy-MM-dd"
|
||||
};
|
||||
|
||||
private static string _datetimeFormatUtc = _datetimeFormats[5];
|
||||
private static string _datetimeFormatLocal = _datetimeFormats[19];
|
||||
private static string GetDateTimeKindFormat(DateTimeKind kind)
|
||||
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
|
||||
|
||||
public static DateTime ReadDateTime(this IResultSetValue result)
|
||||
{
|
||||
var dateText = result.ToString();
|
||||
|
||||
return DateTime.ParseExact(
|
||||
dateText, _datetimeFormats,
|
||||
dateText,
|
||||
_datetimeFormats,
|
||||
DateTimeFormatInfo.InvariantInfo,
|
||||
DateTimeStyles.None).ToUniversalTime();
|
||||
}
|
||||
@@ -139,7 +128,10 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
|
||||
{
|
||||
var commandText = string.Format("attach @path as {0};", alias);
|
||||
var commandText = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"attach @path as {0};",
|
||||
alias);
|
||||
|
||||
using (var statement = db.PrepareStatement(commandText))
|
||||
{
|
||||
@@ -186,10 +178,7 @@ namespace Emby.Server.Implementations.Data
|
||||
private static void CheckName(string name)
|
||||
{
|
||||
#if DEBUG
|
||||
//if (!name.IndexOf("@", StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
throw new Exception("Invalid param name: " + name);
|
||||
}
|
||||
throw new ArgumentException("Invalid param name: " + name, nameof(name));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -264,7 +253,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
||||
{
|
||||
bindParam.Bind(value.ToGuidBlob());
|
||||
bindParam.Bind(value.ToByteArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -392,8 +381,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(
|
||||
this IStatement This)
|
||||
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
|
||||
{
|
||||
while (This.MoveNext())
|
||||
{
|
||||
|
||||
@@ -27,7 +27,6 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
@@ -548,7 +547,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
|
||||
{
|
||||
saveImagesStatement.TryBind("@Id", item.Id.ToGuidBlob());
|
||||
saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
|
||||
saveImagesStatement.TryBind("@Images", SerializeImages(item));
|
||||
|
||||
saveImagesStatement.MoveNext();
|
||||
@@ -658,12 +657,14 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement)
|
||||
{
|
||||
saveItemStatement.TryBind("@guid", item.Id);
|
||||
saveItemStatement.TryBind("@type", item.GetType().FullName);
|
||||
Type type = item.GetType();
|
||||
|
||||
if (TypeRequiresDeserialization(item.GetType()))
|
||||
saveItemStatement.TryBind("@guid", item.Id);
|
||||
saveItemStatement.TryBind("@type", type.FullName);
|
||||
|
||||
if (TypeRequiresDeserialization(type))
|
||||
{
|
||||
saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, _jsonOptions));
|
||||
saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, type, _jsonOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1177,7 +1178,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException(nameof(id), "Guid can't be empty");
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
@@ -1988,7 +1989,7 @@ namespace Emby.Server.Implementations.Data
|
||||
throw new ArgumentNullException(nameof(chapters));
|
||||
}
|
||||
|
||||
var idBlob = id.ToGuidBlob();
|
||||
var idBlob = id.ToByteArray();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
@@ -3760,7 +3761,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, personId.ToGuidBlob());
|
||||
statement.TryBind(paramName, personId.ToByteArray());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -3971,7 +3972,7 @@ namespace Emby.Server.Implementations.Data
|
||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, artistId.ToGuidBlob());
|
||||
statement.TryBind(paramName, artistId.ToByteArray());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -3990,7 +3991,7 @@ namespace Emby.Server.Implementations.Data
|
||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, artistId.ToGuidBlob());
|
||||
statement.TryBind(paramName, artistId.ToByteArray());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -4009,7 +4010,7 @@ namespace Emby.Server.Implementations.Data
|
||||
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, artistId.ToGuidBlob());
|
||||
statement.TryBind(paramName, artistId.ToByteArray());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -4028,7 +4029,7 @@ namespace Emby.Server.Implementations.Data
|
||||
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, albumId.ToGuidBlob());
|
||||
statement.TryBind(paramName, albumId.ToByteArray());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -4047,7 +4048,7 @@ namespace Emby.Server.Implementations.Data
|
||||
clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, artistId.ToGuidBlob());
|
||||
statement.TryBind(paramName, artistId.ToByteArray());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -4066,7 +4067,7 @@ namespace Emby.Server.Implementations.Data
|
||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, genreId.ToGuidBlob());
|
||||
statement.TryBind(paramName, genreId.ToByteArray());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -4137,7 +4138,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, studioId.ToGuidBlob());
|
||||
statement.TryBind(paramName, studioId.ToByteArray());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -4913,7 +4914,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
var idBlob = id.ToGuidBlob();
|
||||
var idBlob = id.ToByteArray();
|
||||
|
||||
// Delete people
|
||||
ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob);
|
||||
@@ -5032,7 +5033,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
whereClauses.Add("ItemId=@ItemId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ItemId", query.ItemId.ToGuidBlob());
|
||||
statement.TryBind("@ItemId", query.ItemId.ToByteArray());
|
||||
}
|
||||
}
|
||||
if (!query.AppearsInItemId.Equals(Guid.Empty))
|
||||
@@ -5040,7 +5041,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidBlob());
|
||||
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray());
|
||||
}
|
||||
}
|
||||
var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
|
||||
@@ -5109,7 +5110,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
var itemIdBlob = itemId.ToGuidBlob();
|
||||
var itemIdBlob = itemId.ToByteArray();
|
||||
|
||||
// First delete
|
||||
deleteAncestorsStatement.Reset();
|
||||
@@ -5143,7 +5144,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
var ancestorId = ancestorIds[i];
|
||||
|
||||
statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob());
|
||||
statement.TryBind("@AncestorId" + index, ancestorId.ToByteArray());
|
||||
statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
@@ -5608,7 +5609,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
var guidBlob = itemId.ToGuidBlob();
|
||||
var guidBlob = itemId.ToByteArray();
|
||||
|
||||
// First delete
|
||||
db.Execute("delete from ItemValues where ItemId=@Id", guidBlob);
|
||||
@@ -5632,10 +5633,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
if (isSubsequentRow)
|
||||
{
|
||||
insertText.Append(",");
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
insertText.AppendFormat("(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})", i.ToString(CultureInfo.InvariantCulture));
|
||||
insertText.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
|
||||
i);
|
||||
isSubsequentRow = true;
|
||||
}
|
||||
|
||||
@@ -5688,7 +5692,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
var itemIdBlob = itemId.ToGuidBlob();
|
||||
var itemIdBlob = itemId.ToByteArray();
|
||||
|
||||
// First delete chapters
|
||||
db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
|
||||
@@ -5807,7 +5811,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
using (var statement = PrepareStatement(connection, cmdText))
|
||||
{
|
||||
statement.TryBind("@ItemId", query.ItemId.ToGuidBlob());
|
||||
statement.TryBind("@ItemId", query.ItemId.ToByteArray());
|
||||
|
||||
if (query.Type.HasValue)
|
||||
{
|
||||
@@ -5849,7 +5853,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
var itemIdBlob = id.ToGuidBlob();
|
||||
var itemIdBlob = id.ToByteArray();
|
||||
|
||||
// First delete chapters
|
||||
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -15,23 +14,19 @@ namespace Emby.Server.Implementations.Data
|
||||
public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
|
||||
{
|
||||
public SqliteUserDataRepository(
|
||||
ILoggerFactory loggerFactory,
|
||||
ILogger<SqliteUserDataRepository> logger,
|
||||
IApplicationPaths appPaths)
|
||||
: base(loggerFactory.CreateLogger(nameof(SqliteUserDataRepository)))
|
||||
: base(logger)
|
||||
{
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
/// <inheritdoc />
|
||||
public string Name => "SQLite";
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// Opens the connection to the database.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
|
||||
{
|
||||
WriteLock.Dispose();
|
||||
@@ -97,7 +92,7 @@ namespace Emby.Server.Implementations.Data
|
||||
continue;
|
||||
}
|
||||
|
||||
statement.TryBind("@UserId", user.Id.ToGuidBlob());
|
||||
statement.TryBind("@UserId", user.Id.ToByteArray());
|
||||
statement.TryBind("@InternalUserId", user.InternalId);
|
||||
|
||||
statement.MoveNext();
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
|
||||
{
|
||||
statement.TryBind("@guid", user.Id.ToGuidBlob());
|
||||
statement.TryBind("@guid", user.Id.ToByteArray());
|
||||
statement.TryBind("@data", serialized);
|
||||
|
||||
statement.MoveNext();
|
||||
|
||||
@@ -130,7 +130,6 @@ namespace Emby.Server.Implementations.Devices
|
||||
var session = _authRepo.Get(new AuthenticationInfoQuery
|
||||
{
|
||||
DeviceId = id
|
||||
|
||||
}).Items.FirstOrDefault();
|
||||
|
||||
var device = session == null ? null : ToDeviceInfo(session);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
|
||||
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
|
||||
<ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
@@ -10,7 +11,6 @@
|
||||
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
|
||||
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
|
||||
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
|
||||
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
|
||||
@@ -29,12 +29,15 @@
|
||||
<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.Logging" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -47,16 +50,12 @@
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- We need at least C# 7.3 to compare tuples-->
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
@@ -15,209 +14,134 @@ using Mono.Nat;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Server entrypoint handling external port forwarding.
|
||||
/// </summary>
|
||||
public class ExternalPortForwarding : IServerEntryPoint
|
||||
{
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||
|
||||
private readonly object _createdRulesLock = new object();
|
||||
private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
|
||||
private Timer _timer;
|
||||
private string _lastConfigIdentifier;
|
||||
|
||||
private NatManager _natManager;
|
||||
private bool _disposed = false;
|
||||
|
||||
public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExternalPortForwarding"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
/// <param name="deviceDiscovery">The device discovery.</param>
|
||||
public ExternalPortForwarding(
|
||||
ILogger<ExternalPortForwarding> logger,
|
||||
IServerApplicationHost appHost,
|
||||
IServerConfigurationManager config,
|
||||
IDeviceDiscovery deviceDiscovery)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger("PortMapper");
|
||||
_logger = logger;
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_deviceDiscovery = deviceDiscovery;
|
||||
_httpClient = httpClient;
|
||||
_config.ConfigurationUpdated += _config_ConfigurationUpdated1;
|
||||
}
|
||||
|
||||
private void _config_ConfigurationUpdated1(object sender, EventArgs e)
|
||||
{
|
||||
_config_ConfigurationUpdated(sender, e);
|
||||
}
|
||||
|
||||
private string _lastConfigIdentifier;
|
||||
private string GetConfigIdentifier()
|
||||
{
|
||||
var values = new List<string>();
|
||||
const char Separator = '|';
|
||||
var config = _config.Configuration;
|
||||
|
||||
values.Add(config.EnableUPnP.ToString());
|
||||
values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture));
|
||||
values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture));
|
||||
values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture));
|
||||
values.Add(_appHost.EnableHttps.ToString());
|
||||
values.Add((config.EnableRemoteAccess).ToString());
|
||||
|
||||
return string.Join("|", values.ToArray());
|
||||
return new StringBuilder(32)
|
||||
.Append(config.EnableUPnP).Append(Separator)
|
||||
.Append(config.PublicPort).Append(Separator)
|
||||
.Append(_appHost.HttpPort).Append(Separator)
|
||||
.Append(_appHost.HttpsPort).Append(Separator)
|
||||
.Append(_appHost.EnableHttps).Append(Separator)
|
||||
.Append(config.EnableRemoteAccess).Append(Separator)
|
||||
.ToString();
|
||||
}
|
||||
|
||||
private async void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
private void OnConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DisposeNat();
|
||||
|
||||
await RunAsync();
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
Start();
|
||||
|
||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
||||
_config.ConfigurationUpdated += OnConfigurationUpdated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_logger.LogDebug("Starting NAT discovery");
|
||||
if (_natManager == null)
|
||||
if (!_config.Configuration.EnableUPnP || !_config.Configuration.EnableRemoteAccess)
|
||||
{
|
||||
_natManager = new NatManager(_logger, _httpClient);
|
||||
_natManager.DeviceFound += NatUtility_DeviceFound;
|
||||
_natManager.StartDiscovery();
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Starting NAT discovery");
|
||||
|
||||
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
|
||||
NatUtility.StartDiscovery();
|
||||
|
||||
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
||||
|
||||
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
||||
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
|
||||
|
||||
_lastConfigIdentifier = GetConfigIdentifier();
|
||||
}
|
||||
|
||||
private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||
private void Stop()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_logger.LogDebug("Stopping NAT discovery");
|
||||
|
||||
var info = e.Argument;
|
||||
NatUtility.StopDiscovery();
|
||||
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
|
||||
|
||||
if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
|
||||
_timer?.Dispose();
|
||||
|
||||
if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
|
||||
|
||||
// Filter device type
|
||||
if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
|
||||
nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
|
||||
usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
|
||||
nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
|
||||
|
||||
if (info.Location == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_usnsHandled)
|
||||
{
|
||||
if (_usnsHandled.Contains(identifier))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_usnsHandled.Add(identifier);
|
||||
}
|
||||
|
||||
_logger.LogDebug("Found NAT device: " + identifier);
|
||||
|
||||
if (IPAddress.TryParse(info.Location.Host, out var address))
|
||||
{
|
||||
// The Handle method doesn't need the port
|
||||
var endpoint = new IPEndPoint(address, info.Location.Port);
|
||||
|
||||
IPAddress localAddress = null;
|
||||
|
||||
try
|
||||
{
|
||||
var localAddressString = await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
if (Uri.TryCreate(localAddressString, UriKind.Absolute, out var uri))
|
||||
{
|
||||
localAddressString = uri.Host;
|
||||
|
||||
if (!IPAddress.TryParse(localAddressString, out localAddress))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This should never happen, but the Handle method will throw ArgumentNullException if it does
|
||||
if (localAddress == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var natManager = _natManager;
|
||||
if (natManager != null)
|
||||
{
|
||||
await natManager.Handle(localAddress, info, endpoint, NatProtocol.Upnp).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
||||
}
|
||||
|
||||
private void ClearCreatedRules(object state)
|
||||
{
|
||||
lock (_createdRules)
|
||||
lock (_createdRulesLock)
|
||||
{
|
||||
_createdRules.Clear();
|
||||
}
|
||||
lock (_usnsHandled)
|
||||
{
|
||||
_usnsHandled.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
|
||||
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
|
||||
}
|
||||
|
||||
private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = e.Device;
|
||||
|
||||
CreateRules(device);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Commenting out because users are reporting problems out of our control
|
||||
//_logger.LogError(ex, "Error creating port forwarding rules");
|
||||
_logger.LogError(ex, "Error creating port forwarding rules");
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> _createdRules = new List<string>();
|
||||
private List<string> _usnsHandled = new List<string>();
|
||||
private async void CreateRules(INatDevice device)
|
||||
{
|
||||
if (_disposed)
|
||||
@@ -227,15 +151,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
// On some systems the device discovered event seems to fire repeatedly
|
||||
// This check will help ensure we're not trying to port map the same device over and over
|
||||
var address = device.LocalAddress;
|
||||
var address = device.DeviceEndpoint;
|
||||
|
||||
var addressString = address.ToString();
|
||||
|
||||
lock (_createdRules)
|
||||
lock (_createdRulesLock)
|
||||
{
|
||||
if (!_createdRules.Contains(addressString))
|
||||
if (!_createdRules.Contains(address))
|
||||
{
|
||||
_createdRules.Add(addressString);
|
||||
_createdRules.Add(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -263,54 +185,43 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
}
|
||||
}
|
||||
|
||||
private Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
|
||||
private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
|
||||
{
|
||||
_logger.LogDebug("Creating port map on local port {0} to public port {1} with device {2}", privatePort, publicPort, device.LocalAddress.ToString());
|
||||
_logger.LogDebug(
|
||||
"Creating port map on local port {0} to public port {1} with device {2}",
|
||||
privatePort,
|
||||
publicPort,
|
||||
device.DeviceEndpoint);
|
||||
|
||||
return device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
|
||||
{
|
||||
Description = _appHost.Name
|
||||
});
|
||||
return device.CreatePortMapAsync(
|
||||
new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
|
||||
}
|
||||
|
||||
private bool _disposed = false;
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
DisposeNat();
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DisposeNat()
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
_logger.LogDebug("Stopping NAT discovery");
|
||||
|
||||
if (_timer != null)
|
||||
if (_disposed)
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
|
||||
_config.ConfigurationUpdated -= OnConfigurationUpdated;
|
||||
|
||||
var natManager = _natManager;
|
||||
Stop();
|
||||
|
||||
if (natManager != null)
|
||||
{
|
||||
_natManager = null;
|
||||
_timer = null;
|
||||
|
||||
using (natManager)
|
||||
{
|
||||
try
|
||||
{
|
||||
natManager.StopDiscovery();
|
||||
natManager.DeviceFound -= NatUtility_DeviceFound;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error stopping NAT Discovery");
|
||||
}
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
public class LibraryChangedNotifier : IServerEntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The _library manager
|
||||
/// The library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The _library changed sync lock
|
||||
/// The library changed sync lock.
|
||||
/// </summary>
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private Timer LibraryUpdateTimer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The library update duration
|
||||
/// The library update duration.
|
||||
/// </summary>
|
||||
private const int LibraryUpdateDuration = 30000;
|
||||
|
||||
@@ -188,8 +188,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (LibraryUpdateTimer == null)
|
||||
{
|
||||
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
|
||||
Timeout.Infinite);
|
||||
LibraryUpdateTimer = new Timer(
|
||||
LibraryUpdateTimerCallback,
|
||||
null,
|
||||
LibraryUpdateDuration,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -452,7 +455,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
return new[] { item };
|
||||
}
|
||||
|
||||
return new T[] { };
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
try
|
||||
{
|
||||
await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None);
|
||||
await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@@ -325,7 +325,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
|
||||
if (options.LogErrorResponseBody)
|
||||
{
|
||||
var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
_logger.LogError("HTTP request failed with message: {Message}", msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Configuration;
|
||||
using Emby.Server.Implementations.Net;
|
||||
using Emby.Server.Implementations.Services;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
@@ -16,11 +15,9 @@ using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -166,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
OnReceive = ProcessWebSocketMessageReceived,
|
||||
Url = e.Url,
|
||||
QueryString = e.QueryString ?? new QueryCollection()
|
||||
QueryString = e.QueryString
|
||||
};
|
||||
|
||||
connection.Closed += OnConnectionClosed;
|
||||
@@ -539,6 +536,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (httpRes.StatusCode >= 500)
|
||||
{
|
||||
_logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog);
|
||||
}
|
||||
|
||||
stopWatch.Stop();
|
||||
var elapsed = stopWatch.Elapsed;
|
||||
if (elapsed.TotalMilliseconds > 500)
|
||||
|
||||
@@ -460,7 +460,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
throw new ArgumentException("Path can't be empty.", nameof(options));
|
||||
}
|
||||
|
||||
if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)
|
||||
|
||||
@@ -48,12 +48,14 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
public IDictionary<string, string> Headers => _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
|
||||
/// 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>
|
||||
/// <param name="logger">The logger instance.</param>
|
||||
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.SocketSharp;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -7,22 +8,27 @@ using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer.Security
|
||||
{
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
private readonly IAuthorizationContext _authorizationContext;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
public AuthService(
|
||||
ILogger<AuthService> logger,
|
||||
IAuthorizationContext authorizationContext,
|
||||
IServerConfigurationManager config,
|
||||
ISessionManager sessionManager,
|
||||
INetworkManager networkManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_authorizationContext = authorizationContext;
|
||||
_config = config;
|
||||
_sessionManager = sessionManager;
|
||||
@@ -34,7 +40,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
ValidateUser(request, authAttribtues);
|
||||
}
|
||||
|
||||
private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
|
||||
{
|
||||
var req = new WebSocketSharpRequest(request, null, request.Path, _logger);
|
||||
var user = ValidateUser(req, authAttributes);
|
||||
return user;
|
||||
}
|
||||
|
||||
private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||
{
|
||||
// This code is executed before the service
|
||||
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||
@@ -81,6 +94,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
request.RemoteIp,
|
||||
user);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private void ValidateUserAccess(
|
||||
|
||||
@@ -2,11 +2,11 @@ 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;
|
||||
using static MediaBrowser.Common.HexHelper;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
@@ -59,7 +59,10 @@ namespace Emby.Server.Implementations.Library
|
||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
|
||||
|| _cryptographyProvider.DefaultHashMethod == readyHash.Id)
|
||||
{
|
||||
byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt);
|
||||
byte[] calculatedHash = _cryptographyProvider.ComputeHash(
|
||||
readyHash.Id,
|
||||
passwordbytes,
|
||||
readyHash.Salt);
|
||||
|
||||
if (calculatedHash.SequenceEqual(readyHash.Hash))
|
||||
{
|
||||
@@ -122,7 +125,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return string.IsNullOrEmpty(user.EasyPassword)
|
||||
? null
|
||||
: ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
|
||||
: Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Net;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
|
||||
@@ -519,7 +519,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
|
||||
=> ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent);
|
||||
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
|
||||
|
||||
private BaseItem ResolvePath(
|
||||
FileSystemMetadata fileInfo,
|
||||
@@ -1045,7 +1045,7 @@ namespace Emby.Server.Implementations.Library
|
||||
await RootFolder.ValidateChildren(
|
||||
new SimpleProgress<double>(),
|
||||
cancellationToken,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
|
||||
recursive: false).ConfigureAwait(false);
|
||||
|
||||
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
@@ -1053,7 +1053,7 @@ namespace Emby.Server.Implementations.Library
|
||||
await GetUserRootFolder().ValidateChildren(
|
||||
new SimpleProgress<double>(),
|
||||
cancellationToken,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
|
||||
recursive: false).ConfigureAwait(false);
|
||||
|
||||
// Quickly scan CollectionFolders for changes
|
||||
@@ -1074,7 +1074,7 @@ namespace Emby.Server.Implementations.Library
|
||||
innerProgress.RegisterAction(pct => progress.Report(pct * .96));
|
||||
|
||||
// Now validate the entire media library
|
||||
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: true).ConfigureAwait(false);
|
||||
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
|
||||
|
||||
progress.Report(96);
|
||||
|
||||
@@ -1899,7 +1899,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
{
|
||||
UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
|
||||
UpdateItems(new[] { item }, parent, updateReason, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2135,7 +2135,7 @@ namespace Emby.Server.Implementations.Library
|
||||
if (refresh)
|
||||
{
|
||||
item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
|
||||
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
|
||||
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -2175,7 +2175,6 @@ namespace Emby.Server.Implementations.Library
|
||||
DisplayParentId = parentId
|
||||
};
|
||||
|
||||
|
||||
CreateItem(item, null);
|
||||
|
||||
isNew = true;
|
||||
@@ -2193,11 +2192,10 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
_providerManagerFactory().QueueRefresh(
|
||||
item.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
|
||||
},
|
||||
RefreshPriority.Normal);
|
||||
}
|
||||
@@ -2261,7 +2259,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
_providerManagerFactory().QueueRefresh(
|
||||
item.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
@@ -2338,7 +2336,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
_providerManagerFactory().QueueRefresh(
|
||||
item.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
@@ -2487,6 +2485,15 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
episode.ParentIndexNumber = season.IndexNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
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
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
|
||||
if (episode.ParentIndexNumber.HasValue)
|
||||
{
|
||||
|
||||
@@ -134,12 +134,13 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
|
||||
{
|
||||
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
EnableRemoteContentProbe = true,
|
||||
MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode.FullRefresh
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
await item.RefreshMetadata(
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
EnableRemoteContentProbe = true,
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(searchTerm));
|
||||
throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm));
|
||||
}
|
||||
|
||||
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
||||
|
||||
@@ -24,6 +24,7 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
@@ -31,7 +32,6 @@ using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static MediaBrowser.Common.HexHelper;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
@@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly Func<IDtoService> _dtoServiceFactory;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ICryptoProvider _cryptoProvider;
|
||||
|
||||
private ConcurrentDictionary<Guid, User> _users;
|
||||
|
||||
@@ -80,7 +81,8 @@ namespace Emby.Server.Implementations.Library
|
||||
Func<IDtoService> dtoServiceFactory,
|
||||
IServerApplicationHost appHost,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IFileSystem fileSystem)
|
||||
IFileSystem fileSystem,
|
||||
ICryptoProvider cryptoProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_userRepository = userRepository;
|
||||
@@ -91,6 +93,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_appHost = appHost;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
_cryptoProvider = cryptoProvider;
|
||||
_users = null;
|
||||
}
|
||||
|
||||
@@ -179,12 +182,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a User by Id.
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <returns>User.</returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <inheritdoc />
|
||||
public User GetUserById(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
@@ -196,11 +194,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user by identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <returns>User.</returns>
|
||||
/// <inheritdoc />
|
||||
public User GetUserById(string id)
|
||||
=> GetUserById(new Guid(id));
|
||||
|
||||
@@ -428,7 +422,6 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
|
||||
? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
|
||||
: await provider.Authenticate(username, password).ConfigureAwait(false);
|
||||
@@ -475,24 +468,21 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (!success
|
||||
&& _networkManager.IsInLocalNetwork(remoteEndPoint)
|
||||
&& user.Configuration.EnableLocalPassword)
|
||||
&& user.Configuration.EnableLocalPassword
|
||||
&& !string.IsNullOrEmpty(user.EasyPassword))
|
||||
{
|
||||
success = string.Equals(
|
||||
GetLocalPasswordHash(user),
|
||||
_defaultAuthenticationProvider.GetHashedString(user, password),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
// Check easy password
|
||||
var passwordHash = PasswordHash.Parse(user.EasyPassword);
|
||||
var hash = _cryptoProvider.ComputeHash(
|
||||
passwordHash.Id,
|
||||
Encoding.UTF8.GetBytes(password),
|
||||
passwordHash.Salt);
|
||||
success = passwordHash.Hash.SequenceEqual(hash);
|
||||
}
|
||||
|
||||
return (authenticationProvider, username, success);
|
||||
}
|
||||
|
||||
private string GetLocalPasswordHash(User user)
|
||||
{
|
||||
return string.IsNullOrEmpty(user.EasyPassword)
|
||||
? null
|
||||
: ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
|
||||
}
|
||||
|
||||
private void ResetInvalidLoginAttemptCount(User user)
|
||||
{
|
||||
user.Policy.InvalidLoginAttemptCount = 0;
|
||||
@@ -538,6 +528,8 @@ namespace Emby.Server.Implementations.Library
|
||||
defaultName = "MyJellyfinUser";
|
||||
}
|
||||
|
||||
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
|
||||
|
||||
var name = MakeValidUsername(defaultName);
|
||||
|
||||
var user = InstantiateNewUser(name);
|
||||
@@ -601,7 +593,7 @@ namespace Emby.Server.Implementations.Library
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
|
||||
_logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name);
|
||||
_logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {User}", user.Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +617,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path);
|
||||
_logger.LogError(ex, "Error getting {ImageType} image info for {ImagePath}", image.Type, image.Path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -639,7 +631,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
foreach (var user in Users)
|
||||
{
|
||||
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var user = _userManager.GetUserById(query.UserId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentException("User Id specified in the query does not exist.", nameof(query));
|
||||
}
|
||||
|
||||
var folders = _libraryManager.GetUserRootFolder()
|
||||
.GetChildren(user, true)
|
||||
.OfType<Folder>()
|
||||
@@ -54,7 +59,7 @@ namespace Emby.Server.Implementations.Library
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
var collectionFolder = folder as ICollectionFolder;
|
||||
var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
|
||||
var folderViewType = collectionFolder?.CollectionType;
|
||||
|
||||
if (UserView.IsUserSpecific(folder))
|
||||
{
|
||||
@@ -130,16 +135,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
if (index == -1)
|
||||
if (index == -1
|
||||
&& i is UserView view
|
||||
&& view.DisplayParentId != Guid.Empty)
|
||||
{
|
||||
var view = i as UserView;
|
||||
if (view != null)
|
||||
{
|
||||
if (!view.DisplayParentId.Equals(Guid.Empty))
|
||||
{
|
||||
index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return index == -1 ? int.MaxValue : index;
|
||||
|
||||
@@ -28,10 +28,11 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
|
||||
/// Initializes a new instance of the <see cref="ArtistsValidator" /> class.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -10,17 +10,18 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
public class GenresPostScanTask : ILibraryPostScanTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _library manager
|
||||
/// The _library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
|
||||
/// Initializes a new instance of the <see cref="GenresPostScanTask" /> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -20,10 +20,11 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
|
||||
/// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -11,16 +11,17 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Server.Implementations.Library.Validators
|
||||
{
|
||||
/// <summary>
|
||||
/// Class PeopleValidator
|
||||
/// Class PeopleValidator.
|
||||
/// </summary>
|
||||
public class PeopleValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// The _library manager
|
||||
/// The _library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
/// The _logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
{
|
||||
var item = _libraryManager.GetPerson(person);
|
||||
|
||||
var options = new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
|
||||
MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
|
||||
@@ -96,12 +97,19 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
|
||||
foreach (var item in deadEntities)
|
||||
{
|
||||
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
|
||||
_logger.LogInformation(
|
||||
"Deleting dead {2} {0} {1}.",
|
||||
item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
item.Name,
|
||||
item.GetType().Name);
|
||||
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
}, false);
|
||||
_libraryManager.DeleteItem(
|
||||
item,
|
||||
new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
|
||||
@@ -21,9 +21,11 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
|
||||
/// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">Th item repository.</param>
|
||||
public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (requiresRefresh)
|
||||
{
|
||||
await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None);
|
||||
await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1489,16 +1489,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
_logger.LogInformation("Refreshing recording parent {path}", item.Path);
|
||||
|
||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
RefreshPaths = new string[]
|
||||
_providerManager.QueueRefresh(
|
||||
item.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
path,
|
||||
Path.GetDirectoryName(path),
|
||||
Path.GetDirectoryName(Path.GetDirectoryName(path))
|
||||
}
|
||||
|
||||
}, RefreshPriority.High);
|
||||
RefreshPaths = new string[]
|
||||
{
|
||||
path,
|
||||
Path.GetDirectoryName(path),
|
||||
Path.GetDirectoryName(Path.GetDirectoryName(path))
|
||||
}
|
||||
},
|
||||
RefreshPriority.High);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken)
|
||||
{
|
||||
var token = await GetToken(info, cancellationToken);
|
||||
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var lineups = new List<NameIdPair>();
|
||||
|
||||
@@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var token = await GetToken(info, cancellationToken);
|
||||
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
@@ -738,7 +738,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
httpOptions.RequestHeaders["token"] = token;
|
||||
|
||||
using (await _httpClient.SendAsync(httpOptions, "PUT"))
|
||||
using (await _httpClient.SendAsync(httpOptions, "PUT").ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -750,7 +750,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
throw new ArgumentException("Listings Id required");
|
||||
}
|
||||
|
||||
var token = await GetToken(info, cancellationToken);
|
||||
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
@@ -833,7 +833,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
throw new Exception("ListingsId required");
|
||||
}
|
||||
|
||||
var token = await GetToken(info, cancellationToken);
|
||||
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
|
||||
@@ -1226,12 +1226,13 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
currentChannel.AddTag("Kids");
|
||||
}
|
||||
|
||||
//currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
|
||||
await currentChannel.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
ForceSave = true
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
|
||||
await currentChannel.RefreshMetadata(
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
ForceSave = true
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -1245,7 +1246,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
numComplete++;
|
||||
double percent = numComplete / (double)allChannelsList.Count;
|
||||
|
||||
progress.Report(85 * percent + 15);
|
||||
progress.Report((85 * percent) + 15);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
@@ -1278,12 +1279,14 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false,
|
||||
DeleteFromExternalProvider = false
|
||||
|
||||
}, false);
|
||||
_libraryManager.DeleteItem(
|
||||
item,
|
||||
new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false,
|
||||
DeleteFromExternalProvider = false
|
||||
},
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2301,8 +2304,10 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
if (provider == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(
|
||||
string.Format("Couldn't find provider of type: '{0}'", info.Type)
|
||||
);
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Couldn't find provider of type: '{0}'",
|
||||
info.Type));
|
||||
}
|
||||
|
||||
await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
|
||||
|
||||
@@ -38,8 +38,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] {
|
||||
|
||||
return new[]
|
||||
{
|
||||
// Every so often
|
||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||
};
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = false
|
||||
}, HttpMethod.Get))
|
||||
}, HttpMethod.Get).ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
|
||||
{
|
||||
@@ -259,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
for (int i = 0; i < model.TunerCount; ++i)
|
||||
{
|
||||
var name = string.Format("Tuner {0}", i + 1);
|
||||
var currentChannel = "none"; /// @todo Get current channel and map back to Station Id
|
||||
var currentChannel = "none"; // @todo Get current channel and map back to Station Id
|
||||
var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
|
||||
var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
|
||||
tuners.Add(new LiveTvTunerInfo
|
||||
@@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO Need faster way to determine UDP vs HTTP
|
||||
var channels = await GetChannels(info, true, cancellationToken);
|
||||
var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo;
|
||||
|
||||
@@ -582,11 +582,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
modelInfo.TunerCount,
|
||||
FileSystem,
|
||||
Logger,
|
||||
Config.ApplicationPaths,
|
||||
Config,
|
||||
_appHost,
|
||||
_networkManager,
|
||||
_streamHelper);
|
||||
|
||||
}
|
||||
|
||||
var enableHttpStream = true;
|
||||
@@ -611,7 +610,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
FileSystem,
|
||||
_httpClient,
|
||||
Logger,
|
||||
Config.ApplicationPaths,
|
||||
Config,
|
||||
_appHost,
|
||||
_streamHelper);
|
||||
}
|
||||
@@ -624,7 +623,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
modelInfo.TunerCount,
|
||||
FileSystem,
|
||||
Logger,
|
||||
Config.ApplicationPaths,
|
||||
Config,
|
||||
_appHost,
|
||||
_networkManager,
|
||||
_streamHelper);
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
@@ -33,11 +34,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
int numTuners,
|
||||
IFileSystem fileSystem,
|
||||
ILogger logger,
|
||||
IServerApplicationPaths appPaths,
|
||||
IConfigurationManager configurationManager,
|
||||
IServerApplicationHost appHost,
|
||||
INetworkManager networkManager,
|
||||
IStreamHelper streamHelper)
|
||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
|
||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_networkManager = networkManager;
|
||||
|
||||
@@ -5,8 +5,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
@@ -16,8 +16,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
public class LiveStream : ILiveStream
|
||||
{
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
|
||||
protected readonly IFileSystem FileSystem;
|
||||
protected readonly IServerApplicationPaths AppPaths;
|
||||
|
||||
protected readonly IStreamHelper StreamHelper;
|
||||
|
||||
protected string TempFilePath;
|
||||
@@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
TunerHostInfo tuner,
|
||||
IFileSystem fileSystem,
|
||||
ILogger logger,
|
||||
IServerApplicationPaths appPaths,
|
||||
IConfigurationManager configurationManager,
|
||||
IStreamHelper streamHelper)
|
||||
{
|
||||
OriginalMediaSource = mediaSource;
|
||||
@@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
TunerHostId = tuner.Id;
|
||||
}
|
||||
|
||||
AppPaths = appPaths;
|
||||
_configurationManager = configurationManager;
|
||||
StreamHelper = streamHelper;
|
||||
|
||||
ConsumerCount = 1;
|
||||
@@ -68,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
protected void SetTempFilePath(string extension)
|
||||
{
|
||||
TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
|
||||
TempFilePath = Path.Combine(_configurationManager.GetTranscodePath(), UniqueId + "." + extension);
|
||||
}
|
||||
|
||||
public virtual Task Open(CancellationToken openCancellationToken)
|
||||
|
||||
@@ -114,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
|
||||
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper);
|
||||
}
|
||||
}
|
||||
|
||||
return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper);
|
||||
return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper);
|
||||
}
|
||||
|
||||
public async Task Validate(TunerHostInfo info)
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
IFileSystem fileSystem,
|
||||
IHttpClient httpClient,
|
||||
ILogger logger,
|
||||
IServerApplicationPaths appPaths,
|
||||
IConfigurationManager configurationManager,
|
||||
IServerApplicationHost appHost,
|
||||
IStreamHelper streamHelper)
|
||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
|
||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_appHost = appHost;
|
||||
|
||||
96
Emby.Server.Implementations/Localization/Core/af.json
Normal file
96
Emby.Server.Implementations/Localization/Core/af.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"Artists": "Kunstenare",
|
||||
"Channels": "Kanale",
|
||||
"Folders": "Fouers",
|
||||
"Favorites": "Gunstelinge",
|
||||
"HeaderFavoriteShows": "Gunsteling Vertonings",
|
||||
"ValueSpecialEpisodeName": "Spesiaal - {0}",
|
||||
"HeaderAlbumArtists": "Album Kunstenaars",
|
||||
"Books": "Boeke",
|
||||
"HeaderNextUp": "Volgende",
|
||||
"Movies": "Rolprente",
|
||||
"Shows": "Program",
|
||||
"HeaderContinueWatching": "Hou Aan Kyk",
|
||||
"HeaderFavoriteEpisodes": "Gunsteling Episodes",
|
||||
"Photos": "Fotos",
|
||||
"Playlists": "Speellysse",
|
||||
"HeaderFavoriteArtists": "Gunsteling Kunstenaars",
|
||||
"HeaderFavoriteAlbums": "Gunsteling Albums",
|
||||
"Sync": "Sinkroniseer",
|
||||
"HeaderFavoriteSongs": "Gunsteling Liedjies",
|
||||
"Songs": "Liedjies",
|
||||
"DeviceOnlineWithName": "{0} is verbind",
|
||||
"DeviceOfflineWithName": "{0} het afgesluit",
|
||||
"Collections": "Versamelings",
|
||||
"Inherit": "Ontvang",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"Application": "Program",
|
||||
"AppDeviceValues": "App: {0}, Toestel: {1}",
|
||||
"VersionNumber": "Weergawe {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} is by jou media biblioteek bygevoeg",
|
||||
"UserStoppedPlayingItemWithValues": "{0} het klaar {1} op {2} gespeel",
|
||||
"UserStartedPlayingItemWithValues": "{0} is besig om {1} op {2} te speel",
|
||||
"UserPolicyUpdatedWithName": "Gebruiker beleid is verander vir {0}",
|
||||
"UserPasswordChangedWithName": "Gebruiker {0} se wagwoord is verander",
|
||||
"UserOnlineFromDevice": "{0} is aanlyn van {1}",
|
||||
"UserOfflineFromDevice": "{0} is ontkoppel van {1}",
|
||||
"UserLockedOutWithName": "Gebruiker {0} is uitgesluit",
|
||||
"UserDownloadingItemWithValues": "{0} is besig om {1} af te laai",
|
||||
"UserDeletedWithName": "Gebruiker {0} is verwyder",
|
||||
"UserCreatedWithName": "Gebruiker {0} is geskep",
|
||||
"User": "Gebruiker",
|
||||
"TvShows": "TV Programme",
|
||||
"System": "Stelsel",
|
||||
"SubtitlesDownloadedForItem": "Ondertitels afgelaai vir {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.",
|
||||
"ServerNameNeedsToBeRestarted": "{0} moet herbegin word",
|
||||
"ScheduledTaskStartedWithName": "{0} het begin",
|
||||
"ScheduledTaskFailedWithName": "{0} het misluk",
|
||||
"ProviderValue": "Voorsiener: {0}",
|
||||
"PluginUpdatedWithName": "{0} was opgedateer",
|
||||
"PluginUninstalledWithName": "{0} was verwyder",
|
||||
"PluginInstalledWithName": "{0} is geïnstalleer",
|
||||
"Plugin": "Inprop module",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video terugspeel het gestop",
|
||||
"NotificationOptionVideoPlayback": "Video terugspeel het begin",
|
||||
"NotificationOptionUserLockedOut": "Gebruiker uitgeslyt",
|
||||
"NotificationOptionTaskFailed": "Geskeduleerde taak het misluk",
|
||||
"NotificationOptionServerRestartRequired": "Bediener herbegin nodig",
|
||||
"NotificationOptionPluginUpdateInstalled": "Nuwe inprop module geïnstalleer",
|
||||
"NotificationOptionPluginUninstalled": "Inprop module verwyder",
|
||||
"NotificationOptionPluginInstalled": "Inprop module geïnstalleer",
|
||||
"NotificationOptionPluginError": "Inprop module het misluk",
|
||||
"NotificationOptionNewLibraryContent": "Nuwe inhoud bygevoeg",
|
||||
"NotificationOptionInstallationFailed": "Installering het misluk",
|
||||
"NotificationOptionCameraImageUploaded": "Kamera foto is opgelaai",
|
||||
"NotificationOptionAudioPlaybackStopped": "Oudio terugspeel het gestop",
|
||||
"NotificationOptionAudioPlayback": "Oudio terugspeel het begin",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Nuwe program weergawe geïnstalleer",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Nuwe program weergawe beskikbaar",
|
||||
"NewVersionIsAvailable": "'n Nuwe Jellyfin Bedienaar weergawe kan afgelaai word.",
|
||||
"NameSeasonUnknown": "Seisoen Onbekend",
|
||||
"NameSeasonNumber": "Seisoen {0}",
|
||||
"NameInstallFailed": "{0} installering het misluk",
|
||||
"MusicVideos": "Musiek videos",
|
||||
"Music": "Musiek",
|
||||
"MixedContent": "Gemengde inhoud",
|
||||
"MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Bediener konfigurasie seksie {0} is opgedateer",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
|
||||
"Latest": "Nuutste",
|
||||
"LabelRunningTimeValue": "Lopende tyd: {0}",
|
||||
"LabelIpAddressValue": "IP adres: {0}",
|
||||
"ItemRemovedWithName": "{0} is uit versameling verwyder",
|
||||
"ItemAddedWithName": "{0} is in die versameling",
|
||||
"HomeVideos": "Tuis opnames",
|
||||
"HeaderRecordingGroups": "Groep Opnames",
|
||||
"HeaderCameraUploads": "Kamera Oplaai",
|
||||
"Genres": "Genres",
|
||||
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
|
||||
"ChapterNameValue": "Hoofstuk",
|
||||
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
|
||||
"Albums": "Albums"
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"Albums": "Албуми",
|
||||
"AppDeviceValues": "Програма: {0}, Устройство: {1}",
|
||||
"AppDeviceValues": "Програма: {0}, устройство: {1}",
|
||||
"Application": "Програма",
|
||||
"Artists": "Изпълнители",
|
||||
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
|
||||
"Books": "Книги",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "",
|
||||
"Channels": "Канали",
|
||||
"ChapterNameValue": "Глава {0}",
|
||||
"Collections": "Колекции",
|
||||
"DeviceOfflineWithName": "{0} се разкачи",
|
||||
"DeviceOnlineWithName": "{0} е свързан",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"FailedLoginAttemptWithUserName": "",
|
||||
"Favorites": "Любими",
|
||||
"Folders": "Папки",
|
||||
"Genres": "Жанрове",
|
||||
"HeaderAlbumArtists": "Изпълнители на албуми",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderCameraUploads": "",
|
||||
"HeaderContinueWatching": "Продължаване на гледането",
|
||||
"HeaderFavoriteAlbums": "Любими албуми",
|
||||
"HeaderFavoriteArtists": "Любими изпълнители",
|
||||
@@ -25,26 +25,26 @@
|
||||
"HeaderFavoriteSongs": "Любими песни",
|
||||
"HeaderLiveTV": "Телевизия на живо",
|
||||
"HeaderNextUp": "Следва",
|
||||
"HeaderRecordingGroups": "Recording Groups",
|
||||
"HeaderRecordingGroups": "",
|
||||
"HomeVideos": "Домашни клипове",
|
||||
"Inherit": "Наследяване",
|
||||
"ItemAddedWithName": "{0} е добавено към библиотеката",
|
||||
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
|
||||
"LabelIpAddressValue": "ИП адрес: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"LabelRunningTimeValue": "",
|
||||
"Latest": "Последни",
|
||||
"MessageApplicationUpdated": "Сървърът е обновен",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MessageApplicationUpdatedTo": "",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "",
|
||||
"MessageServerConfigurationUpdated": "",
|
||||
"MixedContent": "Смесено съдържание",
|
||||
"Movies": "Филми",
|
||||
"Music": "Музика",
|
||||
"MusicVideos": "Музикални клипове",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "",
|
||||
"NameSeasonNumber": "Сезон {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NameSeasonUnknown": "Неразпознат сезон",
|
||||
"NewVersionIsAvailable": "",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
|
||||
"NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
|
||||
@@ -58,7 +58,7 @@
|
||||
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
|
||||
"NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
|
||||
"NotificationOptionTaskFailed": "Грешка в планирана задача",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionUserLockedOut": "",
|
||||
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
|
||||
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
|
||||
"Photos": "Снимки",
|
||||
@@ -70,12 +70,12 @@
|
||||
"ProviderValue": "Доставчик: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} се провали",
|
||||
"ScheduledTaskStartedWithName": "{0} започна",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ServerNameNeedsToBeRestarted": "",
|
||||
"Shows": "Сериали",
|
||||
"Songs": "Песни",
|
||||
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
|
||||
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "",
|
||||
"SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}",
|
||||
"Sync": "Синхронизиране",
|
||||
"System": "Система",
|
||||
@@ -83,15 +83,15 @@
|
||||
"User": "Потребител",
|
||||
"UserCreatedWithName": "Потребителят {0} е създаден",
|
||||
"UserDeletedWithName": "Потребителят {0} е изтрит",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserDownloadingItemWithValues": "",
|
||||
"UserLockedOutWithName": "",
|
||||
"UserOfflineFromDevice": "{0} се разкачи от {1}",
|
||||
"UserOnlineFromDevice": "{0} е на линия от {1}",
|
||||
"UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserPolicyUpdatedWithName": "",
|
||||
"UserStartedPlayingItemWithValues": "{0} пусна {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} спря {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueHasBeenAddedToLibrary": "",
|
||||
"ValueSpecialEpisodeName": "Специални - {0}",
|
||||
"VersionNumber": "Версия {0}"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"HeaderFavoriteEpisodes": "Oblíbené epizody",
|
||||
"HeaderFavoriteShows": "Oblíbené seriály",
|
||||
"HeaderFavoriteSongs": "Oblíbená hudba",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderLiveTV": "Živá TV",
|
||||
"HeaderNextUp": "Nadcházející",
|
||||
"HeaderRecordingGroups": "Skupiny nahrávek",
|
||||
"HomeVideos": "Domáci videa",
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||
"Application": "Anwendung",
|
||||
"Artists": "Interpreten",
|
||||
"AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
|
||||
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
|
||||
"Books": "Bücher",
|
||||
"CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
|
||||
"Channels": "Kanäle",
|
||||
"ChapterNameValue": "Kapitel {0}",
|
||||
"Collections": "Sammlungen",
|
||||
"DeviceOfflineWithName": "{0} wurde getrennt",
|
||||
"DeviceOnlineWithName": "{0} hat sich verbunden",
|
||||
"DeviceOnlineWithName": "{0} ist verbunden",
|
||||
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
||||
"Favorites": "Favoriten",
|
||||
"Folders": "Verzeichnisse",
|
||||
@@ -23,7 +23,7 @@
|
||||
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
|
||||
"HeaderFavoriteShows": "Lieblingsserien",
|
||||
"HeaderFavoriteSongs": "Lieblingslieder",
|
||||
"HeaderLiveTV": "Live-TV",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Als Nächstes",
|
||||
"HeaderRecordingGroups": "Aufnahme-Gruppen",
|
||||
"HomeVideos": "Heimvideos",
|
||||
@@ -35,7 +35,7 @@
|
||||
"Latest": "Neueste",
|
||||
"MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert",
|
||||
"MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
|
||||
"MixedContent": "Gemischte Inhalte",
|
||||
"Movies": "Filme",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
|
||||
"Books": "Livres",
|
||||
"CameraImageUploadedFrom": "Une image de caméra a été chargée depuis {0}",
|
||||
"CameraImageUploadedFrom": "Une nouvelle image de caméra a été chargée depuis {0}",
|
||||
"Channels": "Chaînes",
|
||||
"ChapterNameValue": "Chapitre {0}",
|
||||
"Collections": "Collections",
|
||||
@@ -17,7 +17,7 @@
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Artistes de l'album",
|
||||
"HeaderCameraUploads": "Photos transférées",
|
||||
"HeaderContinueWatching": "Reprendre",
|
||||
"HeaderContinueWatching": "Continuer à regarder",
|
||||
"HeaderFavoriteAlbums": "Albums favoris",
|
||||
"HeaderFavoriteArtists": "Artistes favoris",
|
||||
"HeaderFavoriteEpisodes": "Épisodes favoris",
|
||||
@@ -34,14 +34,14 @@
|
||||
"LabelRunningTimeValue": "Durée : {0}",
|
||||
"Latest": "Derniers",
|
||||
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}",
|
||||
"MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
|
||||
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
|
||||
"MixedContent": "Contenu mixte",
|
||||
"Movies": "Films",
|
||||
"Music": "Musique",
|
||||
"MusicVideos": "Vidéos musicales",
|
||||
"NameInstallFailed": "{0} échec d'installation",
|
||||
"NameInstallFailed": "{0} échec de l'installation",
|
||||
"NameSeasonNumber": "Saison {0}",
|
||||
"NameSeasonUnknown": "Saison Inconnue",
|
||||
"NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.",
|
||||
@@ -50,7 +50,7 @@
|
||||
"NotificationOptionAudioPlayback": "Lecture audio démarrée",
|
||||
"NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée",
|
||||
"NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée",
|
||||
"NotificationOptionInstallationFailed": "Échec d'installation",
|
||||
"NotificationOptionInstallationFailed": "Échec de l'installation",
|
||||
"NotificationOptionNewLibraryContent": "Nouveau contenu ajouté",
|
||||
"NotificationOptionPluginError": "Erreur d'extension",
|
||||
"NotificationOptionPluginInstalled": "Extension installée",
|
||||
@@ -91,7 +91,7 @@
|
||||
"UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre librairie",
|
||||
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
|
||||
"ValueSpecialEpisodeName": "Spécial - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
{
|
||||
"Albums": "אלבומים",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"AppDeviceValues": "יישום: {0}, מכשיר: {1}",
|
||||
"Application": "אפליקציה",
|
||||
"Artists": "אמנים",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה",
|
||||
"Books": "ספרים",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"Channels": "Channels",
|
||||
"ChapterNameValue": "Chapter {0}",
|
||||
"Collections": "Collections",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
"DeviceOnlineWithName": "{0} is connected",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
"CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}",
|
||||
"Channels": "ערוצים",
|
||||
"ChapterNameValue": "פרק {0}",
|
||||
"Collections": "קולקציות",
|
||||
"DeviceOfflineWithName": "{0} התנתק",
|
||||
"DeviceOnlineWithName": "{0} מחובר",
|
||||
"FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}",
|
||||
"Favorites": "אהובים",
|
||||
"Folders": "תיקיות",
|
||||
"Genres": "ז'אנרים",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderContinueWatching": "המשך בצפייה",
|
||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
||||
"HeaderFavoriteArtists": "Favorite Artists",
|
||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
||||
"HeaderFavoriteShows": "Favorite Shows",
|
||||
"HeaderFavoriteSongs": "Favorite Songs",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Next Up",
|
||||
"HeaderAlbumArtists": "אמני האלבום",
|
||||
"HeaderCameraUploads": "העלאות ממצלמה",
|
||||
"HeaderContinueWatching": "המשך לצפות",
|
||||
"HeaderFavoriteAlbums": "אלבומים שאהבתי",
|
||||
"HeaderFavoriteArtists": "אמנים שאהבתי",
|
||||
"HeaderFavoriteEpisodes": "פרקים אהובים",
|
||||
"HeaderFavoriteShows": "תוכניות אהובות",
|
||||
"HeaderFavoriteSongs": "שירים שאהבתי",
|
||||
"HeaderLiveTV": "טלוויזיה בשידור חי",
|
||||
"HeaderNextUp": "הבא",
|
||||
"HeaderRecordingGroups": "קבוצות הקלטה",
|
||||
"HomeVideos": "Home videos",
|
||||
"Inherit": "Inherit",
|
||||
"HomeVideos": "סרטונים בייתים",
|
||||
"Inherit": "הורש",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"ItemRemovedWithName": "{0} נמחק מהספרייה",
|
||||
"LabelIpAddressValue": "Ip כתובת: {0}",
|
||||
"LabelRunningTimeValue": "משך צפייה: {0}",
|
||||
"Latest": "אחרון",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageApplicationUpdated": "שרת הJellyfin עודכן",
|
||||
"MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MixedContent": "תוכן מעורב",
|
||||
"Movies": "סרטים",
|
||||
@@ -50,7 +50,7 @@
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionInstallationFailed": "התקנה נכשלה",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Előadók",
|
||||
"AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
|
||||
"Books": "Könyvek",
|
||||
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre {0}",
|
||||
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre innen: {0}",
|
||||
"Channels": "Csatornák",
|
||||
"ChapterNameValue": "Jelenet {0}",
|
||||
"Collections": "Gyűjtemények",
|
||||
@@ -15,14 +15,14 @@
|
||||
"Favorites": "Kedvencek",
|
||||
"Folders": "Könyvtárak",
|
||||
"Genres": "Műfajok",
|
||||
"HeaderAlbumArtists": "Album Előadók",
|
||||
"HeaderAlbumArtists": "Album előadók",
|
||||
"HeaderCameraUploads": "Kamera feltöltések",
|
||||
"HeaderContinueWatching": "Folyamatban lévő filmek",
|
||||
"HeaderFavoriteAlbums": "Kedvenc Albumok",
|
||||
"HeaderFavoriteArtists": "Kedvenc Előadók",
|
||||
"HeaderFavoriteEpisodes": "Kedvenc Epizódok",
|
||||
"HeaderFavoriteShows": "Kedvenc Sorozatok",
|
||||
"HeaderFavoriteSongs": "Kedvenc Dalok",
|
||||
"HeaderFavoriteAlbums": "Kedvenc albumok",
|
||||
"HeaderFavoriteArtists": "Kedvenc előadók",
|
||||
"HeaderFavoriteEpisodes": "Kedvenc epizódok",
|
||||
"HeaderFavoriteShows": "Kedvenc sorozatok",
|
||||
"HeaderFavoriteSongs": "Kedvenc dalok",
|
||||
"HeaderLiveTV": "Élő TV",
|
||||
"HeaderNextUp": "Következik",
|
||||
"HeaderRecordingGroups": "Felvételi csoportok",
|
||||
@@ -34,21 +34,21 @@
|
||||
"LabelRunningTimeValue": "Futási idő: {0}",
|
||||
"Latest": "Legújabb",
|
||||
"MessageApplicationUpdated": "Jellyfin Szerver frissítve",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre: {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész frissítve: {0}",
|
||||
"MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
|
||||
"MixedContent": "Vegyes tartalom",
|
||||
"Movies": "Filmek",
|
||||
"Music": "Zene",
|
||||
"MusicVideos": "Zenei Videók",
|
||||
"MusicVideos": "Zenei videók",
|
||||
"NameInstallFailed": "{0} sikertelen telepítés",
|
||||
"NameSeasonNumber": "Évad {0}",
|
||||
"NameSeasonUnknown": "Ismeretlen évad",
|
||||
"NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Új programfrissítés érhető el",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Programfrissítés telepítve",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve",
|
||||
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva",
|
||||
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
|
||||
"NotificationOptionInstallationFailed": "Telepítési hiba",
|
||||
"NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
|
||||
@@ -60,15 +60,15 @@
|
||||
"NotificationOptionTaskFailed": "Ütemezett feladat hiba",
|
||||
"NotificationOptionUserLockedOut": "Felhasználó tiltva",
|
||||
"NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videó lejátszás leállítva",
|
||||
"Photos": "Fényképek",
|
||||
"Playlists": "Lejátszási listák",
|
||||
"Plugin": "Bővítmény",
|
||||
"PluginInstalledWithName": "{0} telepítve",
|
||||
"PluginUninstalledWithName": "{0} eltávolítva",
|
||||
"PluginUpdatedWithName": "{0} frissítve",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} hiba",
|
||||
"ProviderValue": "Szolgáltató: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} sikertelen",
|
||||
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
||||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||
"Shows": "Műsorok",
|
||||
@@ -76,10 +76,10 @@
|
||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}",
|
||||
"SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz {0}",
|
||||
"SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}",
|
||||
"Sync": "Szinkronizál",
|
||||
"System": "Rendszer",
|
||||
"TvShows": "TV Műsorok",
|
||||
"TvShows": "TV műsorok",
|
||||
"User": "Felhasználó",
|
||||
"UserCreatedWithName": "{0} felhasználó létrehozva",
|
||||
"UserDeletedWithName": "{0} felhasználó törölve",
|
||||
@@ -88,7 +88,7 @@
|
||||
"UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
|
||||
"UserOnlineFromDevice": "{0} online itt: {1}",
|
||||
"UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
|
||||
"UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett {0}",
|
||||
"UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt: {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
|
||||
|
||||
1
Emby.Server.Implementations/Localization/Core/is.json
Normal file
1
Emby.Server.Implementations/Localization/Core/is.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -9,13 +9,13 @@
|
||||
"Channels": "Canali",
|
||||
"ChapterNameValue": "Capitolo {0}",
|
||||
"Collections": "Collezioni",
|
||||
"DeviceOfflineWithName": "{0} è stato disconnesso",
|
||||
"DeviceOfflineWithName": "{0} ha disconnesso",
|
||||
"DeviceOnlineWithName": "{0} è connesso",
|
||||
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
||||
"Favorites": "Preferiti",
|
||||
"Folders": "Cartelle",
|
||||
"Genres": "Generi",
|
||||
"HeaderAlbumArtists": "Artisti Album",
|
||||
"HeaderAlbumArtists": "Artisti dell' Album",
|
||||
"HeaderCameraUploads": "Caricamenti Fotocamera",
|
||||
"HeaderContinueWatching": "Continua a guardare",
|
||||
"HeaderFavoriteAlbums": "Album preferiti",
|
||||
@@ -32,7 +32,7 @@
|
||||
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
|
||||
"LabelIpAddressValue": "Indirizzo IP: {0}",
|
||||
"LabelRunningTimeValue": "Durata: {0}",
|
||||
"Latest": "Più recenti",
|
||||
"Latest": "Novità",
|
||||
"MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
|
||||
@@ -43,7 +43,7 @@
|
||||
"MusicVideos": "Video musicali",
|
||||
"NameInstallFailed": "{0} installazione fallita",
|
||||
"NameSeasonNumber": "Stagione {0}",
|
||||
"NameSeasonUnknown": "Stagione sconosciuto",
|
||||
"NameSeasonUnknown": "Stagione sconosciuta",
|
||||
"NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato",
|
||||
@@ -88,9 +88,9 @@
|
||||
"UserOfflineFromDevice": "{0} è stato disconnesso da {1}",
|
||||
"UserOnlineFromDevice": "{0} è online da {1}",
|
||||
"UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}",
|
||||
"UserPolicyUpdatedWithName": "La politica dell'utente è stata aggiornata per {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1}",
|
||||
"UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
|
||||
"ValueSpecialEpisodeName": "Speciale - {0}",
|
||||
"VersionNumber": "Versione {0}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
||||
"Application": "Toepassing",
|
||||
"Application": "Applicatie",
|
||||
"Artists": "Artiesten",
|
||||
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
||||
"Books": "Boeken",
|
||||
@@ -30,7 +30,7 @@
|
||||
"Inherit": "Overerven",
|
||||
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
|
||||
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
|
||||
"LabelIpAddressValue": "IP adres: {0}",
|
||||
"LabelIpAddressValue": "IP-adres: {0}",
|
||||
"LabelRunningTimeValue": "Looptijd: {0}",
|
||||
"Latest": "Nieuwste",
|
||||
"MessageApplicationUpdated": "Jellyfin Server is bijgewerkt",
|
||||
@@ -50,7 +50,7 @@
|
||||
"NotificationOptionAudioPlayback": "Muziek gestart",
|
||||
"NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
|
||||
"NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
|
||||
"NotificationOptionInstallationFailed": "Installatie mislukt",
|
||||
"NotificationOptionInstallationFailed": "Installatie mislukking",
|
||||
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
|
||||
"NotificationOptionPluginError": "Plug-in fout",
|
||||
"NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Umelci",
|
||||
"AuthenticationSucceededWithUserName": "{0} úspešne overený",
|
||||
"Books": "Knihy",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Z {0} bola nahraná nová fotografia",
|
||||
"Channels": "Kanály",
|
||||
"ChapterNameValue": "Kapitola {0}",
|
||||
"Collections": "Zbierky",
|
||||
@@ -15,9 +15,9 @@
|
||||
"Favorites": "Obľúbené",
|
||||
"Folders": "Priečinky",
|
||||
"Genres": "Žánre",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderAlbumArtists": "Albumy umelcov",
|
||||
"HeaderCameraUploads": "Nahrané fotografie",
|
||||
"HeaderContinueWatching": "Pokračujte v pozeraní",
|
||||
"HeaderContinueWatching": "Pokračovať v pozeraní",
|
||||
"HeaderFavoriteAlbums": "Obľúbené albumy",
|
||||
"HeaderFavoriteArtists": "Obľúbení umelci",
|
||||
"HeaderFavoriteEpisodes": "Obľúbené epizódy",
|
||||
|
||||
@@ -5,93 +5,93 @@
|
||||
"Artists": "Sanatçılar",
|
||||
"AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı",
|
||||
"Books": "Kitaplar",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi",
|
||||
"Channels": "Kanallar",
|
||||
"ChapterNameValue": "Chapter {0}",
|
||||
"Collections": "Collections",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
"DeviceOnlineWithName": "{0} is connected",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"ChapterNameValue": "Bölüm {0}",
|
||||
"Collections": "Koleksiyonlar",
|
||||
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
|
||||
"DeviceOnlineWithName": "{0} bağlı",
|
||||
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
|
||||
"Favorites": "Favoriler",
|
||||
"Folders": "Klasörler",
|
||||
"Genres": "Türler",
|
||||
"HeaderAlbumArtists": "Albüm Sanatçıları",
|
||||
"HeaderCameraUploads": "Kamera Yüklemeleri",
|
||||
"HeaderContinueWatching": "İzlemeye Devam Et",
|
||||
"HeaderFavoriteAlbums": "Favori Albümler",
|
||||
"HeaderFavoriteArtists": "Favorite Artists",
|
||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
||||
"HeaderFavoriteShows": "Favori Showlar",
|
||||
"HeaderFavoriteSongs": "Favorite Songs",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Next Up",
|
||||
"HeaderRecordingGroups": "Recording Groups",
|
||||
"HomeVideos": "Home videos",
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip adresi: {0}",
|
||||
"HeaderFavoriteArtists": "Favori Sanatçılar",
|
||||
"HeaderFavoriteEpisodes": "Favori Bölümler",
|
||||
"HeaderFavoriteShows": "Favori Diziler",
|
||||
"HeaderFavoriteSongs": "Favori Şarkılar",
|
||||
"HeaderLiveTV": "Canlı TV",
|
||||
"HeaderNextUp": "Sonraki hafta",
|
||||
"HeaderRecordingGroups": "Kayıt Grupları",
|
||||
"HomeVideos": "Ev videoları",
|
||||
"Inherit": "Devral",
|
||||
"ItemAddedWithName": "{0} kütüphaneye eklendi",
|
||||
"ItemRemovedWithName": "{0} kütüphaneden silindi",
|
||||
"LabelIpAddressValue": "IP adresi: {0}",
|
||||
"LabelRunningTimeValue": "Çalışma süresi: {0}",
|
||||
"Latest": "Latest",
|
||||
"Latest": "En son",
|
||||
"MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MixedContent": "Mixed content",
|
||||
"Movies": "Movies",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} olarak güncellendi",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayarları kısım {0} güncellendi",
|
||||
"MessageServerConfigurationUpdated": "Sunucu ayarları güncellendi",
|
||||
"MixedContent": "Karışık içerik",
|
||||
"Movies": "Filmler",
|
||||
"Music": "Müzik",
|
||||
"MusicVideos": "Müzik videoları",
|
||||
"NameInstallFailed": "{0} kurulum başarısız",
|
||||
"NameInstallFailed": "{0} kurulumu başarısız",
|
||||
"NameSeasonNumber": "Sezon {0}",
|
||||
"NameSeasonUnknown": "Bilinmeyen Sezon",
|
||||
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionInstallationFailed": "Yükleme hatası",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
||||
"Photos": "Photos",
|
||||
"Playlists": "Playlists",
|
||||
"Plugin": "Plugin",
|
||||
"PluginInstalledWithName": "{0} was installed",
|
||||
"PluginUninstalledWithName": "{0} was uninstalled",
|
||||
"PluginUpdatedWithName": "{0} was updated",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} failed",
|
||||
"ScheduledTaskStartedWithName": "{0} started",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"Shows": "Shows",
|
||||
"Songs": "Songs",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
|
||||
"NotificationOptionAudioPlayback": "Ses çalma başladı",
|
||||
"NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu",
|
||||
"NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi",
|
||||
"NotificationOptionInstallationFailed": "Yükleme başarısız oldu",
|
||||
"NotificationOptionNewLibraryContent": "Yeni içerik eklendi",
|
||||
"NotificationOptionPluginError": "Eklenti hatası",
|
||||
"NotificationOptionPluginInstalled": "Eklenti yüklendi",
|
||||
"NotificationOptionPluginUninstalled": "Eklenti kaldırıldı",
|
||||
"NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi",
|
||||
"NotificationOptionServerRestartRequired": "Sunucu yeniden başlatma gerekli",
|
||||
"NotificationOptionTaskFailed": "Zamanlanmış görev hatası",
|
||||
"NotificationOptionUserLockedOut": "Kullanıcı kitlendi",
|
||||
"NotificationOptionVideoPlayback": "Video oynatma başladı",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu",
|
||||
"Photos": "Fotoğraflar",
|
||||
"Playlists": "Çalma listeleri",
|
||||
"Plugin": "Eklenti",
|
||||
"PluginInstalledWithName": "{0} yüklendi",
|
||||
"PluginUninstalledWithName": "{0} kaldırıldı",
|
||||
"PluginUpdatedWithName": "{0} güncellendi",
|
||||
"ProviderValue": "Sağlayıcı: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} başarısız oldu",
|
||||
"ScheduledTaskStartedWithName": "{0} başladı",
|
||||
"ServerNameNeedsToBeRestarted": "{0} yeniden başlatılması gerekiyor",
|
||||
"Shows": "Diziler",
|
||||
"Songs": "Şarkılar",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
||||
"Sync": "Sync",
|
||||
"System": "System",
|
||||
"TvShows": "TV Shows",
|
||||
"User": "User",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserDeletedWithName": "User {0} has been deleted",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
|
||||
"SubtitlesDownloadedForItem": "{0} için altyazılar indirildi",
|
||||
"Sync": "Eşitle",
|
||||
"System": "Sistem",
|
||||
"TvShows": "Diziler",
|
||||
"User": "Kullanıcı",
|
||||
"UserCreatedWithName": "{0} kullanıcısı oluşturuldu",
|
||||
"UserDeletedWithName": "Kullanıcı {0} silindi",
|
||||
"UserDownloadingItemWithValues": "{0} indiriliyor {1}",
|
||||
"UserLockedOutWithName": "Kullanıcı {0} kitlendi",
|
||||
"UserOfflineFromDevice": "{0}, {1} ile bağlantısı kesildi",
|
||||
"UserOnlineFromDevice": "{0}, {1} çevrimiçi",
|
||||
"UserPasswordChangedWithName": "{0} kullanıcısı için şifre değiştirildi",
|
||||
"UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
|
||||
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
|
||||
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
|
||||
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
|
||||
"ValueSpecialEpisodeName": "Özel - {0}",
|
||||
"VersionNumber": "Versiyon {0}"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "艺术家",
|
||||
"AuthenticationSucceededWithUserName": "{0} 认证成功",
|
||||
"Books": "书籍",
|
||||
"CameraImageUploadedFrom": "已从 {0} 上传了一张新的相机图像",
|
||||
"CameraImageUploadedFrom": "已从 {0} 上传了一张新的相片",
|
||||
"Channels": "频道",
|
||||
"ChapterNameValue": "章节 {0}",
|
||||
"Collections": "合集",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"Application": "Application",
|
||||
"Artists": "Artists",
|
||||
"Artists": "藝人",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Books": "Books",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Albums": "專輯",
|
||||
"AppDeviceValues": "應用: {0}, 裝置: {1}",
|
||||
"AppDeviceValues": "軟體: {0}, 裝置: {1}",
|
||||
"Application": "應用程式",
|
||||
"Artists": "演出者",
|
||||
"AuthenticationSucceededWithUserName": "{0} 成功授權",
|
||||
|
||||
@@ -5,11 +5,9 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -27,12 +27,12 @@ namespace Emby.Server.Implementations.Middleware
|
||||
var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
|
||||
if (webSocketContext != null)
|
||||
{
|
||||
await _webSocketManager.OnWebSocketConnected(webSocketContext);
|
||||
await _webSocketManager.OnWebSocketConnected(webSocketContext).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _next.Invoke(httpContext);
|
||||
await _next.Invoke(httpContext).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Networking
|
||||
@@ -20,6 +18,9 @@ namespace Emby.Server.Implementations.Networking
|
||||
private IPAddress[] _localIpAddresses;
|
||||
private readonly object _localIpAddressSyncLock = new object();
|
||||
|
||||
private readonly object _subnetLookupLock = new object();
|
||||
private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
|
||||
|
||||
public NetworkManager(ILogger<NetworkManager> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -28,10 +29,10 @@ namespace Emby.Server.Implementations.Networking
|
||||
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
||||
}
|
||||
|
||||
public Func<string[]> LocalSubnetsFn { get; set; }
|
||||
|
||||
public event EventHandler NetworkChanged;
|
||||
|
||||
public Func<string[]> LocalSubnetsFn { get; set; }
|
||||
|
||||
private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
||||
{
|
||||
_logger.LogDebug("NetworkAvailabilityChanged");
|
||||
@@ -52,10 +53,7 @@ namespace Emby.Server.Implementations.Networking
|
||||
_macAddresses = null;
|
||||
}
|
||||
|
||||
if (NetworkChanged != null)
|
||||
{
|
||||
NetworkChanged(this, EventArgs.Empty);
|
||||
}
|
||||
NetworkChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
||||
@@ -179,10 +177,9 @@ namespace Emby.Server.Implementations.Networking
|
||||
return false;
|
||||
}
|
||||
|
||||
private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
|
||||
private List<string> GetSubnets(string endpointFirstPart)
|
||||
{
|
||||
lock (_subnetLookup)
|
||||
lock (_subnetLookupLock)
|
||||
{
|
||||
if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets))
|
||||
{
|
||||
@@ -200,7 +197,11 @@ namespace Emby.Server.Implementations.Networking
|
||||
int subnet_Test = 0;
|
||||
foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.'))
|
||||
{
|
||||
if (part.Equals("0")) break;
|
||||
if (part.Equals("0", StringComparison.Ordinal))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
subnet_Test++;
|
||||
}
|
||||
|
||||
@@ -255,10 +256,10 @@ namespace Emby.Server.Implementations.Networking
|
||||
return true;
|
||||
}
|
||||
|
||||
if (normalizedSubnet.IndexOf('/') != -1)
|
||||
if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
|
||||
{
|
||||
var ipnetwork = IPNetwork.Parse(normalizedSubnet);
|
||||
if (ipnetwork.Contains(address))
|
||||
var ipNetwork = IPNetwork.Parse(normalizedSubnet);
|
||||
if (ipNetwork.Contains(address))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -447,119 +448,11 @@ namespace Emby.Server.Implementations.Networking
|
||||
.Select(x => x.GetPhysicalAddress())
|
||||
.Where(x => x != null && x != PhysicalAddress.None);
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified endpointstring.
|
||||
/// </summary>
|
||||
/// <param name="endpointstring">The endpointstring.</param>
|
||||
/// <returns>IPEndPoint.</returns>
|
||||
public IPEndPoint Parse(string endpointstring)
|
||||
{
|
||||
return Parse(endpointstring, -1).Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified endpointstring.
|
||||
/// </summary>
|
||||
/// <param name="endpointstring">The endpointstring.</param>
|
||||
/// <param name="defaultport">The defaultport.</param>
|
||||
/// <returns>IPEndPoint.</returns>
|
||||
/// <exception cref="ArgumentException">Endpoint descriptor may not be empty.</exception>
|
||||
/// <exception cref="FormatException"></exception>
|
||||
private static async Task<IPEndPoint> Parse(string endpointstring, int defaultport)
|
||||
{
|
||||
if (string.IsNullOrEmpty(endpointstring)
|
||||
|| endpointstring.Trim().Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Endpoint descriptor may not be empty.");
|
||||
}
|
||||
|
||||
if (defaultport != -1 &&
|
||||
(defaultport < IPEndPoint.MinPort
|
||||
|| defaultport > IPEndPoint.MaxPort))
|
||||
{
|
||||
throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
|
||||
}
|
||||
|
||||
string[] values = endpointstring.Split(new char[] { ':' });
|
||||
IPAddress ipaddy;
|
||||
int port = -1;
|
||||
|
||||
//check if we have an IPv6 or ports
|
||||
if (values.Length <= 2) // ipv4 or hostname
|
||||
{
|
||||
port = values.Length == 1 ? defaultport : GetPort(values[1]);
|
||||
|
||||
//try to use the address as IPv4, otherwise get hostname
|
||||
if (!IPAddress.TryParse(values[0], out ipaddy))
|
||||
ipaddy = await GetIPfromHost(values[0]).ConfigureAwait(false);
|
||||
}
|
||||
else if (values.Length > 2) //ipv6
|
||||
{
|
||||
//could [a:b:c]:d
|
||||
if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
|
||||
{
|
||||
string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
|
||||
ipaddy = IPAddress.Parse(ipaddressstring);
|
||||
port = GetPort(values[values.Length - 1]);
|
||||
}
|
||||
else //[a:b:c] or a:b:c
|
||||
{
|
||||
ipaddy = IPAddress.Parse(endpointstring);
|
||||
port = defaultport;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
|
||||
}
|
||||
|
||||
if (port == -1)
|
||||
throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
|
||||
|
||||
return new IPEndPoint(ipaddy, port);
|
||||
}
|
||||
|
||||
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port.
|
||||
/// </summary>
|
||||
/// <param name="p">The p.</param>
|
||||
/// <returns>System.Int32.</returns>
|
||||
/// <exception cref="FormatException"></exception>
|
||||
private static int GetPort(string p)
|
||||
{
|
||||
if (!int.TryParse(p, out var port)
|
||||
|| port < IPEndPoint.MinPort
|
||||
|| port > IPEndPoint.MaxPort)
|
||||
{
|
||||
throw new FormatException(string.Format("Invalid end point port '{0}'", p));
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the I pfrom host.
|
||||
/// </summary>
|
||||
/// <param name="p">The p.</param>
|
||||
/// <returns>IPAddress.</returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private static async Task<IPAddress> GetIPfromHost(string p)
|
||||
{
|
||||
var hosts = await Dns.GetHostAddressesAsync(p).ConfigureAwait(false);
|
||||
|
||||
if (hosts == null || hosts.Length == 0)
|
||||
throw new ArgumentException(string.Format("Host not found: {0}", p));
|
||||
|
||||
return hosts[0];
|
||||
}
|
||||
|
||||
public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
|
||||
{
|
||||
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
|
||||
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
|
||||
return network1.Equals(network2);
|
||||
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
|
||||
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
|
||||
return network1.Equals(network2);
|
||||
}
|
||||
|
||||
private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
|
||||
@@ -575,7 +468,7 @@ namespace Emby.Server.Implementations.Networking
|
||||
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
|
||||
for (int i = 0; i < broadcastAddress.Length; i++)
|
||||
{
|
||||
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
|
||||
broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]);
|
||||
}
|
||||
|
||||
return new IPAddress(broadcastAddress);
|
||||
@@ -615,24 +508,5 @@ namespace Emby.Server.Implementations.Networking
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network shares.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>IEnumerable{NetworkShare}.</returns>
|
||||
public virtual IEnumerable<NetworkShare> GetNetworkShares(string path)
|
||||
{
|
||||
return new List<NetworkShare>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets available devices within the domain
|
||||
/// </summary>
|
||||
/// <returns>PC's in the Domain</returns>
|
||||
public virtual IEnumerable<FileSystemEntryInfo> GetNetworkDevices()
|
||||
{
|
||||
return new List<FileSystemEntryInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace Emby.Server.Implementations.Playlists
|
||||
{
|
||||
@@ -24,13 +24,13 @@ namespace Emby.Server.Implementations.Playlists
|
||||
return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
[JsonIgnore]
|
||||
public override bool IsHidden => true;
|
||||
|
||||
[IgnoreDataMember]
|
||||
[JsonIgnore]
|
||||
public override bool SupportsInheritedParentImages => false;
|
||||
|
||||
[IgnoreDataMember]
|
||||
[JsonIgnore]
|
||||
public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists;
|
||||
|
||||
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
|
||||
@@ -48,4 +48,3 @@ namespace Emby.Server.Implementations.Playlists
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,8 +90,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
}
|
||||
else
|
||||
{
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
if (item is Folder folder)
|
||||
{
|
||||
options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist)
|
||||
.Select(i => i.MediaType)
|
||||
@@ -140,7 +139,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
|
||||
parentFolder.AddChild(playlist, CancellationToken.None);
|
||||
|
||||
await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) { ForceSave = true }, CancellationToken.None)
|
||||
await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (options.ItemIdList.Length > 0)
|
||||
@@ -201,7 +200,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
|
||||
var list = new List<LinkedChild>();
|
||||
|
||||
var items = (GetPlaylistItems(itemIds, playlist.MediaType, user, options))
|
||||
var items = GetPlaylistItems(itemIds, playlist.MediaType, user, options)
|
||||
.Where(i => i.SupportsAddingToPlaylist)
|
||||
.ToList();
|
||||
|
||||
@@ -221,18 +220,18 @@ namespace Emby.Server.Implementations.Playlists
|
||||
SavePlaylistFile(playlist);
|
||||
}
|
||||
|
||||
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
ForceSave = true
|
||||
|
||||
}, RefreshPriority.High);
|
||||
_providerManager.QueueRefresh(
|
||||
playlist.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
ForceSave = true
|
||||
},
|
||||
RefreshPriority.High);
|
||||
}
|
||||
|
||||
public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
|
||||
{
|
||||
var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
|
||||
|
||||
if (playlist == null)
|
||||
if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
|
||||
{
|
||||
throw new ArgumentException("No Playlist exists with the supplied Id");
|
||||
}
|
||||
@@ -254,7 +253,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
SavePlaylistFile(playlist);
|
||||
}
|
||||
|
||||
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
ForceSave = true
|
||||
|
||||
@@ -263,9 +262,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
|
||||
public void MoveItem(string playlistId, string entryId, int newIndex)
|
||||
{
|
||||
var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
|
||||
|
||||
if (playlist == null)
|
||||
if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
|
||||
{
|
||||
throw new ArgumentException("No Playlist exists with the supplied Id");
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
public event EventHandler<GenericEventArgs<double>> TaskProgress;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scheduled task.
|
||||
/// Gets the scheduled task.
|
||||
/// </summary>
|
||||
/// <value>The scheduled task.</value>
|
||||
public IScheduledTask ScheduledTask { get; private set; }
|
||||
@@ -215,11 +215,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
public double? CurrentProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The _triggers
|
||||
/// The _triggers.
|
||||
/// </summary>
|
||||
private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triggers that define when the task will run
|
||||
/// Gets the triggers that define when the task will run.
|
||||
/// </summary>
|
||||
/// <value>The triggers.</value>
|
||||
private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
|
||||
@@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triggers that define when the task will run
|
||||
/// Gets the triggers that define when the task will run.
|
||||
/// </summary>
|
||||
/// <value>The triggers.</value>
|
||||
/// <exception cref="ArgumentNullException">value</exception>
|
||||
|
||||
@@ -36,19 +36,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
/// Gets or sets the json serializer.
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
private IJsonSerializer JsonSerializer { get; set; }
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application paths.
|
||||
/// </summary>
|
||||
/// <value>The application paths.</value>
|
||||
private IApplicationPaths ApplicationPaths { get; set; }
|
||||
private readonly IApplicationPaths _applicationPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
private ILogger Logger { get; set; }
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
@@ -57,19 +57,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
/// <param name="applicationPaths">The application paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
/// <exception cref="System.ArgumentException">kernel</exception>
|
||||
/// <param name="fileSystem">The filesystem manager.</param>
|
||||
public TaskManager(
|
||||
IApplicationPaths applicationPaths,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILoggerFactory loggerFactory,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
ApplicationPaths = applicationPaths;
|
||||
JsonSerializer = jsonSerializer;
|
||||
Logger = loggerFactory.CreateLogger(nameof(TaskManager));
|
||||
_applicationPaths = applicationPaths;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = loggerFactory.CreateLogger(nameof(TaskManager));
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
ScheduledTasks = new IScheduledTaskWorker[] { };
|
||||
ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options">Task options.</param>
|
||||
public void CancelIfRunningAndQueue<T>(TaskOptions options)
|
||||
where T : IScheduledTask
|
||||
where T : IScheduledTask
|
||||
{
|
||||
var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
|
||||
((ScheduledTaskWorker)task).CancelIfRunning();
|
||||
@@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
|
||||
if (scheduledTask == null)
|
||||
{
|
||||
Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
|
||||
_logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -147,13 +147,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
|
||||
if (scheduledTask == null)
|
||||
{
|
||||
Logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
|
||||
_logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = scheduledTask.ScheduledTask.GetType();
|
||||
|
||||
Logger.LogInformation("Queueing task {0}", type.Name);
|
||||
_logger.LogInformation("Queueing task {0}", type.Name);
|
||||
|
||||
lock (_taskQueue)
|
||||
{
|
||||
@@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
|
||||
if (scheduledTask == null)
|
||||
{
|
||||
Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
|
||||
_logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
var type = task.ScheduledTask.GetType();
|
||||
|
||||
Logger.LogInformation("Queueing task {0}", type.Name);
|
||||
_logger.LogInformation("Queueing task {0}", type.Name);
|
||||
|
||||
lock (_taskQueue)
|
||||
{
|
||||
@@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
/// <param name="tasks">The tasks.</param>
|
||||
public void AddTasks(IEnumerable<IScheduledTask> tasks)
|
||||
{
|
||||
var list = tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem));
|
||||
var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger, _fileSystem));
|
||||
|
||||
ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
|
||||
}
|
||||
@@ -281,7 +281,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
/// </summary>
|
||||
private void ExecuteQueuedTasks()
|
||||
{
|
||||
Logger.LogInformation("ExecuteQueuedTasks");
|
||||
_logger.LogInformation("ExecuteQueuedTasks");
|
||||
|
||||
// Execute queued tasks
|
||||
lock (_taskQueue)
|
||||
|
||||
@@ -19,16 +19,17 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ChapterImagesTask
|
||||
/// Class ChapterImagesTask.
|
||||
/// </summary>
|
||||
public class ChapterImagesTask : IScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
/// The _logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The _library manager
|
||||
/// The _library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
@@ -53,12 +54,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run
|
||||
/// Creates the triggers that define when the task will run.
|
||||
/// </summary>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] {
|
||||
|
||||
return new[]
|
||||
{
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfo.TriggerDaily,
|
||||
@@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
previouslyFailedImages = new List<string>();
|
||||
}
|
||||
|
||||
var directoryService = new DirectoryService(_logger, _fileSystem);
|
||||
var directoryService = new DirectoryService(_fileSystem);
|
||||
|
||||
foreach (var video in videos)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -15,24 +16,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
/// </summary>
|
||||
public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the application paths.
|
||||
/// </summary>
|
||||
/// <value>The application paths.</value>
|
||||
private ServerApplicationPaths ApplicationPaths { get; set; }
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
|
||||
/// </summary>
|
||||
public DeleteTranscodeFileTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
|
||||
public DeleteTranscodeFileTask(ILogger logger, IFileSystem fileSystem, IConfigurationManager configurationManager)
|
||||
{
|
||||
ApplicationPaths = appPaths;
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,14 +47,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
var minDateModified = DateTime.UtcNow.AddDays(-1);
|
||||
progress.Report(50);
|
||||
|
||||
try
|
||||
{
|
||||
DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
// No biggie here. Nothing to delete
|
||||
}
|
||||
DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -138,13 +126,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
}
|
||||
}
|
||||
|
||||
public string Name => "Transcoding temp cleanup";
|
||||
public string Name => "Transcode file cleanup";
|
||||
|
||||
public string Description => "Deletes transcoding temp files older than 24 hours.";
|
||||
public string Description => "Deletes transcode files more than 24 hours old.";
|
||||
|
||||
public string Category => "Maintenance";
|
||||
|
||||
public string Key => "DeleteTranscodingTempFiles";
|
||||
public string Key => "DeleteTranscodeFiles";
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Model.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin Update Task
|
||||
/// Plugin Update Task.
|
||||
/// </summary>
|
||||
public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
/// The _logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@@ -31,7 +30,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
}
|
||||
|
||||
/// <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()
|
||||
@@ -44,16 +43,18 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update installed plugins
|
||||
/// Update installed plugins.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <returns><see cref="Task" />.</returns>
|
||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
progress.Report(0);
|
||||
|
||||
var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(typeof(PluginUpdateTask).Assembly.GetName().Version, true, cancellationToken).ConfigureAwait(false)).ToList();
|
||||
var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
progress.Report(10);
|
||||
|
||||
@@ -94,18 +95,25 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Check for plugin updates";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => "Application";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "PluginUpdates";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsHidden => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLogged => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -7,12 +6,12 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a task trigger that fires everyday
|
||||
/// Represents a task trigger that fires everyday.
|
||||
/// </summary>
|
||||
public class DailyTrigger : ITaskTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the time of day to trigger the task to run
|
||||
/// Get the time of day to trigger the task to run.
|
||||
/// </summary>
|
||||
/// <value>The time of day.</value>
|
||||
public TimeSpan TimeOfDay { get; set; }
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Serialization
|
||||
{
|
||||
@@ -14,35 +12,13 @@ namespace Emby.Server.Implementations.Serialization
|
||||
/// </summary>
|
||||
public class MyXmlSerializer : IXmlSerializer
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MyXmlSerializer(
|
||||
IFileSystem fileSystem,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_logger = loggerFactory.CreateLogger("XmlSerializer");
|
||||
}
|
||||
|
||||
// Need to cache these
|
||||
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
|
||||
private readonly Dictionary<string, XmlSerializer> _serializers =
|
||||
new Dictionary<string, XmlSerializer>();
|
||||
private static readonly ConcurrentDictionary<string, XmlSerializer> _serializers =
|
||||
new ConcurrentDictionary<string, XmlSerializer>();
|
||||
|
||||
private XmlSerializer GetSerializer(Type type)
|
||||
{
|
||||
var key = type.FullName;
|
||||
lock (_serializers)
|
||||
{
|
||||
if (!_serializers.TryGetValue(key, out var serializer))
|
||||
{
|
||||
serializer = new XmlSerializer(type);
|
||||
_serializers[key] = serializer;
|
||||
}
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
private static XmlSerializer GetSerializer(Type type)
|
||||
=> _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type));
|
||||
|
||||
/// <summary>
|
||||
/// Serializes to writer.
|
||||
@@ -91,7 +67,6 @@ namespace Emby.Server.Implementations.Serialization
|
||||
/// <param name="file">The file.</param>
|
||||
public void SerializeToFile(object obj, string file)
|
||||
{
|
||||
_logger.LogDebug("Serializing to file {0}", file);
|
||||
using (var stream = new FileStream(file, FileMode.Create))
|
||||
{
|
||||
SerializeToStream(obj, stream);
|
||||
@@ -106,7 +81,6 @@ namespace Emby.Server.Implementations.Serialization
|
||||
/// <returns>System.Object.</returns>
|
||||
public object DeserializeFromFile(Type type, string file)
|
||||
{
|
||||
_logger.LogDebug("Deserializing file {0}", file);
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
return DeserializeFromStream(type, stream);
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Emby.Server.Implementations.AppBase;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -6,12 +5,10 @@ using MediaBrowser.Controller;
|
||||
namespace Emby.Server.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends BaseApplicationPaths to add paths that are only applicable on the server
|
||||
/// Extends BaseApplicationPaths to add paths that are only applicable on the server.
|
||||
/// </summary>
|
||||
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
|
||||
{
|
||||
private string _defaultTranscodingTempPath;
|
||||
private string _transcodingTempPath;
|
||||
private string _internalMetadataPath;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,7 +20,8 @@ namespace Emby.Server.Implementations
|
||||
string configurationDirectoryPath,
|
||||
string cacheDirectoryPath,
|
||||
string webDirectoryPath)
|
||||
: base(programDataPath,
|
||||
: base(
|
||||
programDataPath,
|
||||
logDirectoryPath,
|
||||
configurationDirectoryPath,
|
||||
cacheDirectoryPath,
|
||||
@@ -31,8 +29,6 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
}
|
||||
|
||||
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the base root media directory.
|
||||
/// </summary>
|
||||
@@ -45,18 +41,13 @@ namespace Emby.Server.Implementations
|
||||
/// <value>The default user views path.</value>
|
||||
public string DefaultUserViewsPath => Path.Combine(RootFolderPath, "default");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to localization data.
|
||||
/// </summary>
|
||||
/// <value>The localization path.</value>
|
||||
public string LocalizationPath => Path.Combine(ProgramDataPath, "localization");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the People directory.
|
||||
/// </summary>
|
||||
/// <value>The people path.</value>
|
||||
public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
|
||||
|
||||
/// <summary>
|
||||
@@ -107,46 +98,14 @@ namespace Emby.Server.Implementations
|
||||
/// <value>The user configuration directory path.</value>
|
||||
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
|
||||
|
||||
public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
|
||||
|
||||
public string TranscodingTempPath
|
||||
{
|
||||
get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath);
|
||||
set => _transcodingTempPath = value;
|
||||
}
|
||||
|
||||
public string GetTranscodingTempPath()
|
||||
{
|
||||
var path = TranscodingTempPath;
|
||||
|
||||
if (!string.Equals(path, DefaultTranscodingTempPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var testPath = Path.Combine(path, Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(testPath);
|
||||
Directory.Delete(testPath);
|
||||
|
||||
return path;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
path = DefaultTranscodingTempPath;
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string InternalMetadataPath
|
||||
{
|
||||
get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
|
||||
set => _internalMetadataPath = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
@@ -28,6 +30,13 @@ namespace Emby.Server.Implementations.Services
|
||||
private readonly bool[] isWildcard;
|
||||
private readonly int wildcardCount = 0;
|
||||
|
||||
internal static string[] IgnoreAttributesNamed = new[]
|
||||
{
|
||||
nameof(JsonIgnoreAttribute)
|
||||
};
|
||||
|
||||
private static Type _excludeType = typeof(Stream);
|
||||
|
||||
public int VariableArgsCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -37,8 +46,8 @@ namespace Emby.Server.Implementations.Services
|
||||
public int PathComponentsCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of segments after subparts have been exploded ('.')
|
||||
/// e.g. /path/to/here.ext == 4
|
||||
/// Gets or sets the total number of segments after subparts have been exploded ('.')
|
||||
/// e.g. /path/to/here.ext == 4.
|
||||
/// </summary>
|
||||
public int TotalComponentsCount { get; set; }
|
||||
|
||||
@@ -190,21 +199,12 @@ namespace Emby.Server.Implementations.Services
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
internal static string[] IgnoreAttributesNamed = new[]
|
||||
{
|
||||
"IgnoreDataMemberAttribute",
|
||||
"JsonIgnoreAttribute"
|
||||
};
|
||||
|
||||
|
||||
private static Type excludeType = typeof(Stream);
|
||||
|
||||
internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type)
|
||||
{
|
||||
foreach (var prop in GetPublicProperties(type))
|
||||
{
|
||||
if (prop.GetMethod == null
|
||||
|| excludeType == prop.PropertyType)
|
||||
|| _excludeType == prop.PropertyType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provide for quick lookups based on hashes that can be determined from a request url
|
||||
/// Provide for quick lookups based on hashes that can be determined from a request url.
|
||||
/// </summary>
|
||||
public string FirstMatchHashKey { get; private set; }
|
||||
|
||||
@@ -437,9 +437,12 @@ namespace Emby.Server.Implementations.Services
|
||||
&& requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
|
||||
|
||||
if (!isValidWildCardPath)
|
||||
throw new ArgumentException(string.Format(
|
||||
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
|
||||
pathInfo, this.restPath));
|
||||
throw new ArgumentException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
|
||||
pathInfo,
|
||||
this.restPath));
|
||||
}
|
||||
|
||||
var requestKeyValuesMap = new Dictionary<string, string>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Net;
|
||||
@@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.Services
|
||||
|
||||
private SwaggerTag[] GetTags()
|
||||
{
|
||||
return new SwaggerTag[] { };
|
||||
return Array.Empty<SwaggerTag>();
|
||||
}
|
||||
|
||||
private Dictionary<string, SwaggerDefinition> GetDefinitions()
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return SendMessage(command.Command.ToString(), messageId, args, cancellationToken);
|
||||
}
|
||||
|
||||
private string[] _supportedMessages = new string[] { };
|
||||
private string[] _supportedMessages = Array.Empty<string>();
|
||||
public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!IsSessionActive)
|
||||
|
||||
@@ -1386,27 +1386,28 @@ namespace Emby.Server.Implementations.Session
|
||||
if (user != null)
|
||||
{
|
||||
// TODO: Move this to userManager?
|
||||
if (!string.IsNullOrEmpty(request.DeviceId))
|
||||
if (!string.IsNullOrEmpty(request.DeviceId)
|
||||
&& !_deviceManager.CanAccessDevice(user, request.DeviceId))
|
||||
{
|
||||
if (!_deviceManager.CanAccessDevice(user, request.DeviceId))
|
||||
{
|
||||
throw new SecurityException("User is not allowed access from this device.");
|
||||
}
|
||||
throw new SecurityException("User is not allowed access from this device.");
|
||||
}
|
||||
}
|
||||
|
||||
if (enforcePassword)
|
||||
{
|
||||
var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.RemoteEndPoint, true).ConfigureAwait(false);
|
||||
user = await _userManager.AuthenticateUser(
|
||||
request.Username,
|
||||
request.Password,
|
||||
request.PasswordSha1,
|
||||
request.RemoteEndPoint,
|
||||
true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request));
|
||||
if (user == null)
|
||||
{
|
||||
AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request));
|
||||
|
||||
throw new SecurityException("Invalid user or password entered.");
|
||||
}
|
||||
|
||||
user = result;
|
||||
throw new SecurityException("Invalid user or password entered.");
|
||||
}
|
||||
|
||||
var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName);
|
||||
|
||||
@@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -67,7 +66,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
if (queryString == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(queryString));
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = queryString["api_key"];
|
||||
@@ -75,6 +74,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var deviceId = queryString["deviceId"];
|
||||
return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state.
|
||||
/// Gets the state.
|
||||
/// </summary>
|
||||
/// <value>The state.</value>
|
||||
public WebSocketState State => _webSocket.State;
|
||||
|
||||
@@ -81,8 +81,10 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
|
||||
if (webSocketContext.State == WebSocketState.Open)
|
||||
{
|
||||
await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
|
||||
result.CloseStatusDescription, _disposeCancellationToken);
|
||||
await webSocketContext.CloseAsync(
|
||||
result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
|
||||
result.CloseStatusDescription,
|
||||
_disposeCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
socket.Dispose();
|
||||
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using MediaBrowser.Common.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
@@ -13,11 +13,11 @@ using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest;
|
||||
|
||||
namespace Emby.Server.Implementations.SocketSharp
|
||||
{
|
||||
public partial class WebSocketSharpRequest : IHttpRequest
|
||||
public class WebSocketSharpRequest : IHttpRequest
|
||||
{
|
||||
public const string FormUrlEncoded = "application/x-www-form-urlencoded";
|
||||
public const string MultiPartFormData = "multipart/form-data";
|
||||
public const string Soap11 = "text/xml; charset=utf-8";
|
||||
private const string FormUrlEncoded = "application/x-www-form-urlencoded";
|
||||
private const string MultiPartFormData = "multipart/form-data";
|
||||
private const string Soap11 = "text/xml; charset=utf-8";
|
||||
|
||||
private string _remoteIp;
|
||||
private Dictionary<string, object> _items;
|
||||
@@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
get =>
|
||||
_responseContentType
|
||||
?? (_responseContentType = GetResponseContentType(Request));
|
||||
set => this._responseContentType = value;
|
||||
set => _responseContentType = value;
|
||||
}
|
||||
|
||||
public string PathInfo => Request.Path.Value;
|
||||
@@ -91,7 +91,6 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
|
||||
public bool IsLocal => Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress);
|
||||
|
||||
|
||||
public string HttpMethod => Request.Method;
|
||||
|
||||
public string Verb => HttpMethod;
|
||||
@@ -124,24 +123,29 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
return specifiedContentType;
|
||||
}
|
||||
|
||||
const string serverDefaultContentType = "application/json";
|
||||
const string ServerDefaultContentType = MediaTypeNames.Application.Json;
|
||||
|
||||
var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
|
||||
string defaultContentType = null;
|
||||
if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData))
|
||||
{
|
||||
defaultContentType = serverDefaultContentType;
|
||||
defaultContentType = ServerDefaultContentType;
|
||||
}
|
||||
|
||||
var acceptsAnything = false;
|
||||
var hasDefaultContentType = defaultContentType != null;
|
||||
if (acceptContentTypes != null)
|
||||
{
|
||||
foreach (var acceptsType in acceptContentTypes)
|
||||
foreach (ReadOnlySpan<char> acceptsType in acceptContentTypes)
|
||||
{
|
||||
// TODO: @bond move to Span when Span.Split lands
|
||||
// https://github.com/dotnet/corefx/issues/26528
|
||||
var contentType = acceptsType?.Split(';')[0].Trim();
|
||||
ReadOnlySpan<char> contentType = acceptsType;
|
||||
var index = contentType.IndexOf(';');
|
||||
if (index != -1)
|
||||
{
|
||||
contentType = contentType.Slice(0, index);
|
||||
}
|
||||
|
||||
contentType = contentType.Trim();
|
||||
acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (acceptsAnything)
|
||||
@@ -158,7 +162,7 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
}
|
||||
else
|
||||
{
|
||||
return serverDefaultContentType;
|
||||
return ServerDefaultContentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,7 +173,7 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
}
|
||||
|
||||
// We could also send a '406 Not Acceptable', but this is allowed also
|
||||
return serverDefaultContentType;
|
||||
return ServerDefaultContentType;
|
||||
}
|
||||
|
||||
public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes)
|
||||
@@ -197,12 +201,12 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
|
||||
private static string GetQueryStringContentType(HttpRequest httpReq)
|
||||
{
|
||||
ReadOnlySpan<char> format = httpReq.Query["format"].ToString().AsSpan();
|
||||
ReadOnlySpan<char> format = httpReq.Query["format"].ToString();
|
||||
if (format == null)
|
||||
{
|
||||
const int formatMaxLength = 4;
|
||||
ReadOnlySpan<char> pi = httpReq.Path.ToString().AsSpan();
|
||||
if (pi == null || pi.Length <= formatMaxLength)
|
||||
const int FormatMaxLength = 4;
|
||||
ReadOnlySpan<char> pi = httpReq.Path.ToString();
|
||||
if (pi == null || pi.Length <= FormatMaxLength)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -213,18 +217,18 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||
}
|
||||
|
||||
format = LeftPart(pi, '/');
|
||||
if (format.Length > formatMaxLength)
|
||||
if (format.Length > FormatMaxLength)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
format = LeftPart(format, '.');
|
||||
if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase))
|
||||
if (format.Contains("json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "application/json";
|
||||
}
|
||||
else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase))
|
||||
else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "application/xml";
|
||||
}
|
||||
|
||||
@@ -19,10 +19,14 @@ namespace Emby.Server.Implementations.Sorting
|
||||
public int Compare(BaseItem x, BaseItem y)
|
||||
{
|
||||
if (x == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(x));
|
||||
}
|
||||
|
||||
if (y == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(y));
|
||||
}
|
||||
|
||||
return string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
@@ -24,10 +24,14 @@ namespace Emby.Server.Implementations.Sorting
|
||||
public int Compare(BaseItem x, BaseItem y)
|
||||
{
|
||||
if (x == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(x));
|
||||
}
|
||||
|
||||
if (y == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(y));
|
||||
}
|
||||
|
||||
var levelX = string.IsNullOrEmpty(x.OfficialRating) ? 0 : _localization.GetRatingLevel(x.OfficialRating) ?? 0;
|
||||
var levelY = string.IsNullOrEmpty(y.OfficialRating) ? 0 : _localization.GetRatingLevel(y.OfficialRating) ?? 0;
|
||||
|
||||
@@ -17,10 +17,15 @@ namespace Emby.Server.Implementations.Sorting
|
||||
public int Compare(BaseItem x, BaseItem y)
|
||||
{
|
||||
if (x == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(x));
|
||||
}
|
||||
|
||||
if (y == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(y));
|
||||
}
|
||||
|
||||
return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,52 +19,18 @@ using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static MediaBrowser.Common.HexHelper;
|
||||
|
||||
namespace Emby.Server.Implementations.Updates
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages all install, uninstall and update operations (both plugins and system)
|
||||
/// Manages all install, uninstall and update operations (both plugins and system).
|
||||
/// </summary>
|
||||
public class InstallationManager : IInstallationManager
|
||||
{
|
||||
public event EventHandler<InstallationEventArgs> PackageInstalling;
|
||||
public event EventHandler<InstallationEventArgs> PackageInstallationCompleted;
|
||||
public event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed;
|
||||
public event EventHandler<InstallationEventArgs> PackageInstallationCancelled;
|
||||
|
||||
/// <summary>
|
||||
/// The current installations
|
||||
/// </summary>
|
||||
private List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The completed installations
|
||||
/// </summary>
|
||||
private ConcurrentBag<InstallationInfo> _completedInstallationsInternal;
|
||||
|
||||
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [plugin uninstalled].
|
||||
/// </summary>
|
||||
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [plugin updated].
|
||||
/// </summary>
|
||||
public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [plugin updated].
|
||||
/// </summary>
|
||||
public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
|
||||
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
/// The _logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
@@ -79,6 +45,18 @@ namespace Emby.Server.Implementations.Updates
|
||||
|
||||
private readonly IZipClient _zipClient;
|
||||
|
||||
private readonly object _currentInstallationsLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The current installations.
|
||||
/// </summary>
|
||||
private readonly List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations;
|
||||
|
||||
/// <summary>
|
||||
/// The completed installations.
|
||||
/// </summary>
|
||||
private readonly ConcurrentBag<InstallationInfo> _completedInstallationsInternal;
|
||||
|
||||
public InstallationManager(
|
||||
ILogger<InstallationManager> logger,
|
||||
IApplicationHost appHost,
|
||||
@@ -107,26 +85,32 @@ namespace Emby.Server.Implementations.Updates
|
||||
_zipClient = zipClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available packages.
|
||||
/// </summary>
|
||||
/// <returns>Task{List{PackageInfo}}.</returns>
|
||||
public async Task<List<PackageInfo>> GetAvailablePackages(
|
||||
CancellationToken cancellationToken,
|
||||
bool withRegistration = true,
|
||||
string packageType = null,
|
||||
Version applicationVersion = null)
|
||||
{
|
||||
var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
|
||||
return FilterPackages(packages, packageType, applicationVersion);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationEventArgs> PackageInstalling;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available packages.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{List{PackageInfo}}.</returns>
|
||||
public async Task<List<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationEventArgs> PackageInstallationCompleted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationEventArgs> PackageInstallationCancelled;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var response = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
@@ -134,178 +118,95 @@ namespace Emby.Server.Implementations.Updates
|
||||
Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
|
||||
CancellationToken = cancellationToken,
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
CacheLength = GetCacheLength()
|
||||
CacheLength = TimeSpan.FromMinutes(3)
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (Stream stream = response.Content)
|
||||
{
|
||||
return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync<PackageInfo[]>(stream).ConfigureAwait(false));
|
||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(
|
||||
stream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static TimeSpan GetCacheLength()
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PackageInfo> FilterPackages(
|
||||
IEnumerable<PackageInfo> availablePackages,
|
||||
string name = null,
|
||||
Guid guid = default)
|
||||
{
|
||||
return TimeSpan.FromMinutes(3);
|
||||
}
|
||||
|
||||
protected List<PackageInfo> FilterPackages(IEnumerable<PackageInfo> packages)
|
||||
{
|
||||
var list = new List<PackageInfo>();
|
||||
|
||||
foreach (var package in packages)
|
||||
if (name != null)
|
||||
{
|
||||
var versions = new List<PackageVersionInfo>();
|
||||
foreach (var version in package.versions)
|
||||
{
|
||||
if (string.IsNullOrEmpty(version.sourceUrl))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
versions.Add(version);
|
||||
}
|
||||
|
||||
package.versions = versions
|
||||
.OrderByDescending(x => x.Version)
|
||||
.ToArray();
|
||||
|
||||
if (package.versions.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(package);
|
||||
availablePackages = availablePackages.Where(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
// Remove packages with no versions
|
||||
return list;
|
||||
}
|
||||
|
||||
protected List<PackageInfo> FilterPackages(IEnumerable<PackageInfo> packages, string packageType, Version applicationVersion)
|
||||
{
|
||||
var packagesList = FilterPackages(packages);
|
||||
|
||||
var returnList = new List<PackageInfo>();
|
||||
|
||||
var filterOnPackageType = !string.IsNullOrEmpty(packageType);
|
||||
|
||||
foreach (var p in packagesList)
|
||||
if (guid != Guid.Empty)
|
||||
{
|
||||
if (filterOnPackageType && !string.Equals(p.type, packageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If an app version was supplied, filter the versions for each package to only include supported versions
|
||||
if (applicationVersion != null)
|
||||
{
|
||||
p.versions = p.versions.Where(v => IsPackageVersionUpToDate(v, applicationVersion)).ToArray();
|
||||
}
|
||||
|
||||
if (p.versions.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
returnList.Add(p);
|
||||
var strGuid = guid.ToString("N", CultureInfo.InvariantCulture);
|
||||
availablePackages = availablePackages.Where(x => x.guid.Equals(strGuid, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
return returnList;
|
||||
return availablePackages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is package version up to date] [the specified package version info].
|
||||
/// </summary>
|
||||
/// <param name="packageVersionInfo">The package version info.</param>
|
||||
/// <param name="currentServerVersion">The current server version.</param>
|
||||
/// <returns><c>true</c> if [is package version up to date] [the specified package version info]; otherwise, <c>false</c>.</returns>
|
||||
private static bool IsPackageVersionUpToDate(PackageVersionInfo packageVersionInfo, Version currentServerVersion)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
|
||||
IEnumerable<PackageVersionInfo> availableVersions,
|
||||
Version minVersion = null,
|
||||
PackageVersionClass classification = PackageVersionClass.Release)
|
||||
{
|
||||
if (string.IsNullOrEmpty(packageVersionInfo.requiredVersionStr))
|
||||
var appVer = _applicationHost.ApplicationVersion;
|
||||
availableVersions = availableVersions
|
||||
.Where(x => x.classification == classification
|
||||
&& Version.Parse(x.requiredVersionStr) <= appVer);
|
||||
|
||||
if (minVersion != null)
|
||||
{
|
||||
return true;
|
||||
availableVersions = availableVersions.Where(x => x.Version >= minVersion);
|
||||
}
|
||||
|
||||
return Version.TryParse(packageVersionInfo.requiredVersionStr, out var requiredVersion) && currentServerVersion >= requiredVersion;
|
||||
return availableVersions.OrderByDescending(x => x.Version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the package.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="guid">The assembly guid</param>
|
||||
/// <param name="classification">The classification.</param>
|
||||
/// <param name="version">The version.</param>
|
||||
/// <returns>Task{PackageVersionInfo}.</returns>
|
||||
public async Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
|
||||
IEnumerable<PackageInfo> availablePackages,
|
||||
string name = null,
|
||||
Guid guid = default,
|
||||
Version minVersion = null,
|
||||
PackageVersionClass classification = PackageVersionClass.Release)
|
||||
{
|
||||
var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false);
|
||||
|
||||
var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase))
|
||||
?? packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
|
||||
|
||||
// Package not found.
|
||||
if (package == null)
|
||||
{
|
||||
return null;
|
||||
return Enumerable.Empty<PackageVersionInfo>();
|
||||
}
|
||||
|
||||
return package.versions.FirstOrDefault(v => v.Version == version && v.classification == classification);
|
||||
return GetCompatibleVersions(
|
||||
package.versions,
|
||||
minVersion,
|
||||
classification);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest compatible version.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="guid">The assembly guid if this is a plug-in</param>
|
||||
/// <param name="currentServerVersion">The current server version.</param>
|
||||
/// <param name="classification">The classification.</param>
|
||||
/// <returns>Task{PackageVersionInfo}.</returns>
|
||||
public async Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release)
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false);
|
||||
|
||||
return GetLatestCompatibleVersion(packages, name, guid, currentServerVersion, classification);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest compatible version.
|
||||
/// </summary>
|
||||
/// <param name="availablePackages">The available packages.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="currentServerVersion">The current server version.</param>
|
||||
/// <param name="classification">The classification.</param>
|
||||
/// <returns>PackageVersionInfo.</returns>
|
||||
public PackageVersionInfo GetLatestCompatibleVersion(IEnumerable<PackageInfo> availablePackages, string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release)
|
||||
{
|
||||
var package = availablePackages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase))
|
||||
?? availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return package?.versions
|
||||
.OrderByDescending(x => x.Version)
|
||||
.FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available plugin updates.
|
||||
/// </summary>
|
||||
/// <param name="applicationVersion">The current server version.</param>
|
||||
/// <param name="withAutoUpdateEnabled">if set to <c>true</c> [with auto update enabled].</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{IEnumerable{PackageVersionInfo}}.</returns>
|
||||
public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(Version applicationVersion, bool withAutoUpdateEnabled, CancellationToken cancellationToken)
|
||||
{
|
||||
var catalog = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
|
||||
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
|
||||
|
||||
// Figure out what needs to be installed
|
||||
return _applicationHost.Plugins.Select(p =>
|
||||
foreach (var plugin in _applicationHost.Plugins)
|
||||
{
|
||||
var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, systemUpdateLevel);
|
||||
|
||||
return latestPluginInfo != null && latestPluginInfo.Version > p.Version ? latestPluginInfo : null;
|
||||
}).Where(i => i != null)
|
||||
.Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase)));
|
||||
var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel);
|
||||
var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
|
||||
if (version != null
|
||||
&& !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
yield return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -330,7 +231,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
var tuple = (installationInfo, innerCancellationTokenSource);
|
||||
|
||||
// Add it to the in-progress list
|
||||
lock (_currentInstallations)
|
||||
lock (_currentInstallationsLock)
|
||||
{
|
||||
_currentInstallations.Add(tuple);
|
||||
}
|
||||
@@ -349,7 +250,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
{
|
||||
await InstallPackageInternal(package, linkedToken).ConfigureAwait(false);
|
||||
|
||||
lock (_currentInstallations)
|
||||
lock (_currentInstallationsLock)
|
||||
{
|
||||
_currentInstallations.Remove(tuple);
|
||||
}
|
||||
@@ -360,7 +261,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
lock (_currentInstallations)
|
||||
lock (_currentInstallationsLock)
|
||||
{
|
||||
_currentInstallations.Remove(tuple);
|
||||
}
|
||||
@@ -375,7 +276,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
{
|
||||
_logger.LogError(ex, "Package installation failed");
|
||||
|
||||
lock (_currentInstallations)
|
||||
lock (_currentInstallationsLock)
|
||||
{
|
||||
_currentInstallations.Remove(tuple);
|
||||
}
|
||||
@@ -391,7 +292,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
finally
|
||||
{
|
||||
// Dispose the progress object and remove the installation from the in-progress list
|
||||
tuple.Item2.Dispose();
|
||||
tuple.innerCancellationTokenSource.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +340,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
// Always override the passed-in target (which is a file) and figure it out again
|
||||
string targetDir = Path.Combine(_appPaths.PluginsPath, package.name);
|
||||
|
||||
// CA5351: Do Not Use Broken Cryptographic Algorithms
|
||||
// CA5351: Do Not Use Broken Cryptographic Algorithms
|
||||
#pragma warning disable CA5351
|
||||
using (var res = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
@@ -455,7 +356,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var hash = ToHexString(md5.ComputeHash(stream));
|
||||
var hash = Hex.Encode(md5.ComputeHash(stream));
|
||||
if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogError(
|
||||
@@ -535,20 +436,21 @@ namespace Emby.Server.Implementations.Updates
|
||||
/// <inheritdoc/>
|
||||
public bool CancelInstallation(Guid id)
|
||||
{
|
||||
lock (_currentInstallations)
|
||||
lock (_currentInstallationsLock)
|
||||
{
|
||||
var install = _currentInstallations.Find(x => x.Item1.Id == id);
|
||||
var install = _currentInstallations.Find(x => x.info.Id == id);
|
||||
if (install == default((InstallationInfo, CancellationTokenSource)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
install.Item2.Cancel();
|
||||
install.token.Cancel();
|
||||
_currentInstallations.Remove(install);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
@@ -563,11 +465,11 @@ namespace Emby.Server.Implementations.Updates
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
lock (_currentInstallations)
|
||||
lock (_currentInstallationsLock)
|
||||
{
|
||||
foreach (var tuple in _currentInstallations)
|
||||
{
|
||||
tuple.Item2.Dispose();
|
||||
tuple.token.Dispose();
|
||||
}
|
||||
|
||||
_currentInstallations.Clear();
|
||||
|
||||
@@ -39,12 +39,12 @@ namespace Emby.Server.Implementations.WebSockets
|
||||
do
|
||||
{
|
||||
var buffer = WebSocket.CreateServerBuffer(BufferSize);
|
||||
result = await webSocket.ReceiveAsync(buffer, cancellationToken);
|
||||
result = await webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
message.AddRange(buffer.Array.Take(result.Count));
|
||||
|
||||
if (result.EndOfMessage)
|
||||
{
|
||||
await ProcessMessage(message.ToArray(), taskCompletionSource);
|
||||
await ProcessMessage(message.ToArray(), taskCompletionSource).ConfigureAwait(false);
|
||||
message.Clear();
|
||||
}
|
||||
} while (!taskCompletionSource.Task.IsCompleted &&
|
||||
@@ -53,8 +53,10 @@ namespace Emby.Server.Implementations.WebSockets
|
||||
|
||||
if (webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
await webSocket.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
|
||||
result.CloseStatusDescription, cancellationToken);
|
||||
await webSocket.CloseAsync(
|
||||
result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
|
||||
result.CloseStatusDescription,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user