diff options
Diffstat (limited to 'Emby.Server.Implementations')
66 files changed, 841 insertions, 1438 deletions
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index f26cc4f62..65cdccfa5 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -73,12 +73,6 @@ namespace Emby.Server.Implementations.AppBase public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations"); /// <summary> - /// Gets the path to where temporary update files will be stored - /// </summary> - /// <value>The plugin configurations path.</value> - public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates"); - - /// <summary> /// Gets the path to the log directory /// </summary> /// <value>The log directory path.</value> diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 5feac1adf..af60a8dce 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.AppBase get { // Lazy load - LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem)); + LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer)); return _configuration; } protected set diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 3faad76e7..90b97061f 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Linq; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.AppBase @@ -18,9 +17,8 @@ namespace Emby.Server.Implementations.AppBase /// <param name="type">The type.</param> /// <param name="path">The path.</param> /// <param name="xmlSerializer">The XML serializer.</param> - /// <param name="fileSystem">The file system</param> /// <returns>System.Object.</returns> - public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem) + public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer) { object configuration; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index bb475eb2c..45a819f26 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -102,11 +102,13 @@ using MediaBrowser.Model.Xml; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Subtitles; +using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; using ServiceStack; -using ServiceStack.Text.Jsv; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations @@ -122,12 +124,6 @@ namespace Emby.Server.Implementations /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value> public abstract bool CanSelfRestart { get; } - /// <summary> - /// Gets or sets a value indicating whether this instance can self update. - /// </summary> - /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value> - public virtual bool CanSelfUpdate => false; - public virtual bool CanLaunchWebBrowser { get @@ -202,7 +198,7 @@ namespace Emby.Server.Implementations /// Gets all concrete types. /// </summary> /// <value>All concrete types.</value> - public Tuple<Type, string>[] AllConcreteTypes { get; protected set; } + public Type[] AllConcreteTypes { get; protected set; } /// <summary> /// The disposable parts @@ -219,8 +215,6 @@ namespace Emby.Server.Implementations protected IEnvironmentInfo EnvironmentInfo { get; set; } - private IBlurayExaminer BlurayExaminer { get; set; } - public PackageVersionClass SystemUpdateLevel { get @@ -232,12 +226,7 @@ namespace Emby.Server.Implementations } } - public virtual string OperatingSystemDisplayName => EnvironmentInfo.OperatingSystemName; - - /// <summary> - /// The container - /// </summary> - protected readonly SimpleInjector.Container Container = new SimpleInjector.Container(); + protected IServiceProvider _serviceProvider; /// <summary> /// Gets the server configuration manager. @@ -309,7 +298,6 @@ namespace Emby.Server.Implementations /// <value>The user data repository.</value> private IUserDataManager UserDataManager { get; set; } private IUserRepository UserRepository { get; set; } - internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; } internal SqliteItemRepository ItemRepository { get; set; } private INotificationManager NotificationManager { get; set; } @@ -325,6 +313,8 @@ namespace Emby.Server.Implementations private IMediaSourceManager MediaSourceManager { get; set; } private IPlaylistManager PlaylistManager { get; set; } + private readonly IConfiguration _configuration; + /// <summary> /// Gets or sets the installation manager. /// </summary> @@ -363,8 +353,10 @@ namespace Emby.Server.Implementations IFileSystem fileSystem, IEnvironmentInfo environmentInfo, IImageEncoder imageEncoder, - INetworkManager networkManager) + INetworkManager networkManager, + IConfiguration configuration) { + _configuration = configuration; // hack alert, until common can target .net core BaseExtensions.CryptographyProvider = CryptographyProvider; @@ -440,7 +432,7 @@ namespace Emby.Server.Implementations { if (_deviceId == null) { - _deviceId = new DeviceId(ApplicationPaths, LoggerFactory, FileSystemManager); + _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); } return _deviceId.Value; @@ -453,138 +445,58 @@ namespace Emby.Server.Implementations /// <value>The name.</value> public string Name => ApplicationProductName; - private static Tuple<Assembly, string> GetAssembly(Type type) - { - var assembly = type.GetTypeInfo().Assembly; - - return new Tuple<Assembly, string>(assembly, null); - } - - public virtual IStreamHelper CreateStreamHelper() - { - return new StreamHelper(); - } - /// <summary> - /// Creates an instance of type and resolves all constructor dependancies + /// 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) - { - return Container.GetInstance(type); - } + => ActivatorUtilities.CreateInstance(_serviceProvider, type); + + /// <summary> + /// Creates an instance of type and resolves all constructor dependencies + /// </summary> + /// <param name="type">The type.</param> + /// <returns>System.Object.</returns> + public T CreateInstance<T>() + => ActivatorUtilities.CreateInstance<T>(_serviceProvider); /// <summary> /// Creates the instance safe. /// </summary> /// <param name="typeInfo">The type information.</param> /// <returns>System.Object.</returns> - protected object CreateInstanceSafe(Tuple<Type, string> typeInfo) + protected object CreateInstanceSafe(Type type) { - var type = typeInfo.Item1; - try { - return Container.GetInstance(type); + Logger.LogDebug("Creating instance of {Type}", type); + return ActivatorUtilities.CreateInstance(_serviceProvider, type); } catch (Exception ex) { - Logger.LogError(ex, "Error creating {type}", type.FullName); - // Don't blow up in release mode + Logger.LogError(ex, "Error creating {Type}", type); return null; } } /// <summary> - /// Registers the specified obj. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="obj">The obj.</param> - /// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param> - protected void RegisterSingleInstance<T>(T obj, bool manageLifetime = true) - where T : class - { - Container.RegisterInstance<T>(obj); - - if (manageLifetime) - { - var disposable = obj as IDisposable; - - if (disposable != null) - { - DisposableParts.Add(disposable); - } - } - } - - /// <summary> - /// Registers the single instance. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="func">The func.</param> - protected void RegisterSingleInstance<T>(Func<T> func) - where T : class - { - Container.RegisterSingleton(func); - } - - /// <summary> - /// Resolves this instance. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <returns>``0.</returns> - public T Resolve<T>() - { - return (T)Container.GetRegistration(typeof(T), true).GetInstance(); - } - - /// <summary> /// Resolves this instance. /// </summary> /// <typeparam name="T"></typeparam> /// <returns>``0.</returns> - public T TryResolve<T>() - { - var result = Container.GetRegistration(typeof(T), false); - - if (result == null) - { - return default(T); - } - return (T)result.GetInstance(); - } - - /// <summary> - /// Loads the assembly. - /// </summary> - /// <param name="file">The file.</param> - /// <returns>Assembly.</returns> - protected Tuple<Assembly, string> LoadAssembly(string file) - { - try - { - var assembly = Assembly.Load(File.ReadAllBytes(file)); - - return new Tuple<Assembly, string>(assembly, file); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading assembly {File}", file); - return null; - } - } + public T Resolve<T>() => _serviceProvider.GetService<T>(); /// <summary> /// Gets the export types. /// </summary> /// <typeparam name="T"></typeparam> /// <returns>IEnumerable{Type}.</returns> - public IEnumerable<Tuple<Type, string>> GetExportTypes<T>() + public IEnumerable<Type> GetExportTypes<T>() { var currentType = typeof(T); - return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1)); + return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i)); } /// <summary> @@ -596,9 +508,10 @@ namespace Emby.Server.Implementations public IEnumerable<T> GetExports<T>(bool manageLifetime = true) { var parts = GetExportTypes<T>() - .Select(CreateInstanceSafe) + .Select(x => CreateInstanceSafe(x)) .Where(i => i != null) - .Cast<T>(); + .Cast<T>() + .ToList(); // Convert to list so this isn't executed for each iteration if (manageLifetime) { @@ -611,33 +524,6 @@ namespace Emby.Server.Implementations return parts; } - public List<Tuple<T, string>> GetExportsWithInfo<T>(bool manageLifetime = true) - { - var parts = GetExportTypes<T>() - .Select(i => - { - var obj = CreateInstanceSafe(i); - - if (obj == null) - { - return null; - } - return new Tuple<T, string>((T)obj, i.Item2); - }) - .Where(i => i != null) - .ToList(); - - if (manageLifetime) - { - lock (DisposableParts) - { - DisposableParts.AddRange(parts.Select(i => i.Item1).OfType<IDisposable>()); - } - } - - return parts; - } - /// <summary> /// Runs the startup tasks. /// </summary> @@ -664,16 +550,18 @@ namespace Emby.Server.Implementations var entryPoints = GetExports<IServerEntryPoint>(); - var now = DateTime.UtcNow; + var stopWatch = new Stopwatch(); + stopWatch.Start(); await Task.WhenAll(StartEntryPoints(entryPoints, true)); - Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); + Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; - now = DateTime.UtcNow; + stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)); - Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); + Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); + stopWatch.Stop(); } private IEnumerable<Task> StartEntryPoints(IEnumerable<IServerEntryPoint> entryPoints, bool isBeforeStartup) @@ -691,7 +579,7 @@ namespace Emby.Server.Implementations } } - public async Task Init() + public async Task Init(IServiceCollection serviceCollection) { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -721,7 +609,7 @@ namespace Emby.Server.Implementations SetHttpLimit(); - await RegisterResources(); + await RegisterResources(serviceCollection); FindParts(); } @@ -736,104 +624,105 @@ namespace Emby.Server.Implementations /// <summary> /// Registers resources that classes will depend on /// </summary> - protected async Task RegisterResources() + protected async Task RegisterResources(IServiceCollection serviceCollection) { - RegisterSingleInstance(ConfigurationManager); - RegisterSingleInstance<IApplicationHost>(this); + serviceCollection.AddMemoryCache(); + + serviceCollection.AddSingleton(ConfigurationManager); + serviceCollection.AddSingleton<IApplicationHost>(this); - RegisterSingleInstance<IApplicationPaths>(ApplicationPaths); + serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); - RegisterSingleInstance(JsonSerializer); + serviceCollection.AddSingleton(JsonSerializer); - RegisterSingleInstance(LoggerFactory, false); - RegisterSingleInstance(Logger); + serviceCollection.AddSingleton(LoggerFactory); + serviceCollection.AddLogging(); + serviceCollection.AddSingleton(Logger); - RegisterSingleInstance(EnvironmentInfo); + serviceCollection.AddSingleton(EnvironmentInfo); - RegisterSingleInstance(FileSystemManager); + serviceCollection.AddSingleton(FileSystemManager); + serviceCollection.AddSingleton<TvDbClientManager>(); HttpClient = CreateHttpClient(); - RegisterSingleInstance(HttpClient); + serviceCollection.AddSingleton(HttpClient); - RegisterSingleInstance(NetworkManager); + serviceCollection.AddSingleton(NetworkManager); IsoManager = new IsoManager(); - RegisterSingleInstance(IsoManager); + serviceCollection.AddSingleton(IsoManager); TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager); - RegisterSingleInstance(TaskManager); + serviceCollection.AddSingleton(TaskManager); - RegisterSingleInstance(XmlSerializer); + serviceCollection.AddSingleton(XmlSerializer); ProcessFactory = new ProcessFactory(); - RegisterSingleInstance(ProcessFactory); + serviceCollection.AddSingleton(ProcessFactory); - var streamHelper = CreateStreamHelper(); - ApplicationHost.StreamHelper = streamHelper; - RegisterSingleInstance(streamHelper); + ApplicationHost.StreamHelper = new StreamHelper(); + serviceCollection.AddSingleton(StreamHelper); - RegisterSingleInstance(CryptographyProvider); + serviceCollection.AddSingleton(CryptographyProvider); SocketFactory = new SocketFactory(); - RegisterSingleInstance(SocketFactory); + serviceCollection.AddSingleton(SocketFactory); - InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime); - RegisterSingleInstance(InstallationManager); + InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime); + serviceCollection.AddSingleton(InstallationManager); - ZipClient = new ZipClient(FileSystemManager); - RegisterSingleInstance(ZipClient); + ZipClient = new ZipClient(); + serviceCollection.AddSingleton(ZipClient); HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); - RegisterSingleInstance(HttpResultFactory); + serviceCollection.AddSingleton(HttpResultFactory); - RegisterSingleInstance<IServerApplicationHost>(this); - RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths); + serviceCollection.AddSingleton<IServerApplicationHost>(this); + serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths); - RegisterSingleInstance(ServerConfigurationManager); + serviceCollection.AddSingleton(ServerConfigurationManager); - IAssemblyInfo assemblyInfo = new AssemblyInfo(); - RegisterSingleInstance(assemblyInfo); + var assemblyInfo = new AssemblyInfo(); + serviceCollection.AddSingleton<IAssemblyInfo>(assemblyInfo); LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); await LocalizationManager.LoadAll(); - RegisterSingleInstance<ILocalizationManager>(LocalizationManager); + serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager); - BlurayExaminer = new BdInfoExaminer(FileSystemManager); - RegisterSingleInstance(BlurayExaminer); + serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager)); - RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory()); + serviceCollection.AddSingleton<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory()); UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); - RegisterSingleInstance(UserDataManager); + serviceCollection.AddSingleton(UserDataManager); UserRepository = GetUserRepository(); // This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it - RegisterSingleInstance(UserRepository); + serviceCollection.AddSingleton(UserRepository); var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager); - DisplayPreferencesRepository = displayPreferencesRepo; - RegisterSingleInstance(DisplayPreferencesRepository); + serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo); ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo); - RegisterSingleInstance<IItemRepository>(ItemRepository); + serviceCollection.AddSingleton<IItemRepository>(ItemRepository); AuthenticationRepository = GetAuthenticationRepository(); - RegisterSingleInstance(AuthenticationRepository); + serviceCollection.AddSingleton(AuthenticationRepository); - UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider); - RegisterSingleInstance(UserManager); + UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager); + serviceCollection.AddSingleton(UserManager); LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); - RegisterSingleInstance(LibraryManager); + serviceCollection.AddSingleton(LibraryManager); // TODO wtaylor: investigate use of second music manager var musicManager = new MusicManager(LibraryManager); - RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager)); + serviceCollection.AddSingleton<IMusicManager>(new MusicManager(LibraryManager)); - LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo); - RegisterSingleInstance(LibraryMonitor); + LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo); + serviceCollection.AddSingleton(LibraryMonitor); - RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager)); + serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager)); CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); @@ -841,88 +730,88 @@ namespace Emby.Server.Implementations HttpServer = new HttpListenerHost(this, LoggerFactory, ServerConfigurationManager, - "web/index.html", + _configuration, NetworkManager, JsonSerializer, - XmlSerializer, - GetParseFn); + XmlSerializer); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); - RegisterSingleInstance(HttpServer); + serviceCollection.AddSingleton(HttpServer); ImageProcessor = GetImageProcessor(); - RegisterSingleInstance(ImageProcessor); + serviceCollection.AddSingleton(ImageProcessor); TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); - RegisterSingleInstance(TVSeriesManager); + serviceCollection.AddSingleton(TVSeriesManager); var encryptionManager = new EncryptionManager(); - RegisterSingleInstance<IEncryptionManager>(encryptionManager); + serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager); - DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager); - RegisterSingleInstance(DeviceManager); + DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager); + serviceCollection.AddSingleton(DeviceManager); MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); - RegisterSingleInstance(MediaSourceManager); + serviceCollection.AddSingleton(MediaSourceManager); SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); - RegisterSingleInstance(SubtitleManager); + serviceCollection.AddSingleton(SubtitleManager); ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); - RegisterSingleInstance(ProviderManager); + serviceCollection.AddSingleton(ProviderManager); - DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager); - RegisterSingleInstance(DtoService); + DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); + serviceCollection.AddSingleton(DtoService); ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); - RegisterSingleInstance(ChannelManager); + serviceCollection.AddSingleton(ChannelManager); SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); - RegisterSingleInstance(SessionManager); + serviceCollection.AddSingleton(SessionManager); - var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo); - RegisterSingleInstance<IDlnaManager>(dlnaManager); + serviceCollection.AddSingleton<IDlnaManager>( + new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo)); CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); - RegisterSingleInstance(CollectionManager); + serviceCollection.AddSingleton(CollectionManager); PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager); - RegisterSingleInstance(PlaylistManager); + serviceCollection.AddSingleton(PlaylistManager); LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); - RegisterSingleInstance(LiveTvManager); + serviceCollection.AddSingleton(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); - RegisterSingleInstance(UserViewManager); + serviceCollection.AddSingleton(UserViewManager); NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); - RegisterSingleInstance(NotificationManager); + serviceCollection.AddSingleton(NotificationManager); - RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); + serviceCollection.AddSingleton<IDeviceDiscovery>( + new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); - RegisterSingleInstance(ChapterManager); + serviceCollection.AddSingleton(ChapterManager); - RegisterMediaEncoder(assemblyInfo); + RegisterMediaEncoder(serviceCollection); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); - RegisterSingleInstance(EncodingManager); + serviceCollection.AddSingleton(EncodingManager); var activityLogRepo = GetActivityLogRepository(); - RegisterSingleInstance(activityLogRepo); - RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); + serviceCollection.AddSingleton(activityLogRepo); + serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); var authContext = new AuthorizationContext(AuthenticationRepository, UserManager); - RegisterSingleInstance<IAuthorizationContext>(authContext); - RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); + serviceCollection.AddSingleton<IAuthorizationContext>(authContext); + serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager); - RegisterSingleInstance(AuthService); + serviceCollection.AddSingleton(AuthService); SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); - RegisterSingleInstance(SubtitleEncoder); + serviceCollection.AddSingleton(SubtitleEncoder); - RegisterSingleInstance(CreateResourceFileManager()); + serviceCollection.AddSingleton(CreateResourceFileManager()); displayPreferencesRepo.Initialize(); @@ -935,6 +824,8 @@ namespace Emby.Server.Implementations ((UserDataManager)UserDataManager).Repository = userDataRepo; ItemRepository.Initialize(userDataRepo, UserManager); ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; + + _serviceProvider = serviceCollection.BuildServiceProvider(); } protected virtual IBrotliCompressor CreateBrotliCompressor() @@ -942,11 +833,6 @@ namespace Emby.Server.Implementations return null; } - private static Func<string, object> GetParseFn(Type propertyType) - { - return s => JsvReader.GetParseFn(propertyType)(s); - } - public virtual string PackageRuntime => "netcore"; public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo) @@ -1058,7 +944,7 @@ namespace Emby.Server.Implementations protected virtual FFMpegInfo GetFFMpegInfo() { - return new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, GetFfmpegInstallInfo()) + return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo()) .GetFFMpegInfo(StartupOptions); } @@ -1066,7 +952,7 @@ namespace Emby.Server.Implementations /// Registers the media encoder. /// </summary> /// <returns>Task.</returns> - private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) + private void RegisterMediaEncoder(IServiceCollection serviceCollection) { string encoderPath = null; string probePath = null; @@ -1098,7 +984,7 @@ namespace Emby.Server.Implementations 5000); MediaEncoder = mediaEncoder; - RegisterSingleInstance(MediaEncoder); + serviceCollection.AddSingleton(MediaEncoder); } /// <summary> @@ -1174,7 +1060,10 @@ namespace Emby.Server.Implementations } ConfigurationManager.AddParts(GetExports<IConfigurationFactory>()); - Plugins = GetExportsWithInfo<IPlugin>().Select(LoadPlugin).Where(i => i != null).ToArray(); + Plugins = GetExports<IPlugin>() + .Select(LoadPlugin) + .Where(i => i != null) + .ToArray(); HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>()); @@ -1208,19 +1097,15 @@ namespace Emby.Server.Implementations IsoManager.AddParts(GetExports<IIsoMounter>()); } - private IPlugin LoadPlugin(Tuple<IPlugin, string> info) + private IPlugin LoadPlugin(IPlugin plugin) { - var plugin = info.Item1; - var assemblyFilePath = info.Item2; - try { - var assemblyPlugin = plugin as IPluginAssembly; - - if (assemblyPlugin != null) + if (plugin is IPluginAssembly assemblyPlugin) { var assembly = plugin.GetType().Assembly; var assemblyName = assembly.GetName(); + var assemblyFilePath = assembly.Location; var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); @@ -1264,78 +1149,15 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Loading assemblies"); - var assemblyInfos = GetComposablePartAssemblies(); - - foreach (var assemblyInfo in assemblyInfos) - { - var assembly = assemblyInfo.Item1; - var path = assemblyInfo.Item2; - - if (path == null) + AllConcreteTypes = GetComposablePartAssemblies() + .SelectMany(x => x.ExportedTypes) + .Where(type => { - Logger.LogInformation("Loading {assemblyName}", assembly.FullName); - } - else - { - Logger.LogInformation("Loading {assemblyName} from {path}", assembly.FullName, path); - } - } - - AllConcreteTypes = assemblyInfos - .SelectMany(GetTypes) - .Where(info => - { - var t = info.Item1; - return t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType; + return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType; }) .ToArray(); } - /// <summary> - /// Gets a list of types within an assembly - /// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference - /// </summary> - protected List<Tuple<Type, string>> GetTypes(Tuple<Assembly, string> assemblyInfo) - { - if (assemblyInfo == null) - { - return new List<Tuple<Type, string>>(); - } - - var assembly = assemblyInfo.Item1; - - try - { - // This null checking really shouldn't be needed but adding it due to some - // unhandled exceptions in mono 5.0 that are a little hard to hunt down - var types = assembly.GetTypes() ?? new Type[] { }; - return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList(); - } - catch (ReflectionTypeLoadException ex) - { - if (ex.LoaderExceptions != null) - { - foreach (var loaderException in ex.LoaderExceptions) - { - if (loaderException != null) - { - Logger.LogError("LoaderException: " + loaderException.Message); - } - } - } - - // If it fails we can still get a list of the Types it was able to resolve - var types = ex.Types ?? new Type[] { }; - return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading types from assembly"); - - return new List<Tuple<Type, string>>(); - } - } - private CertificateInfo CertificateInfo { get; set; } protected X509Certificate Certificate { get; private set; } @@ -1546,150 +1368,63 @@ namespace Emby.Server.Implementations /// Gets the composable part assemblies. /// </summary> /// <returns>IEnumerable{Assembly}.</returns> - protected List<Tuple<Assembly, string>> GetComposablePartAssemblies() + protected IEnumerable<Assembly> GetComposablePartAssemblies() { - var list = GetPluginAssemblies(ApplicationPaths.PluginsPath); - - // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that - // This will prevent the .dll file from getting locked, and allow us to replace it when needed + if (Directory.Exists(ApplicationPaths.PluginsPath)) + { + foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)) + { + Logger.LogInformation("Loading assembly {Path}", file); + yield return Assembly.LoadFrom(file); + } + } // Include composable parts in the Api assembly - list.Add(GetAssembly(typeof(ApiEntryPoint))); + yield return typeof(ApiEntryPoint).Assembly; // Include composable parts in the Dashboard assembly - list.Add(GetAssembly(typeof(DashboardService))); + yield return typeof(DashboardService).Assembly; // Include composable parts in the Model assembly - list.Add(GetAssembly(typeof(SystemInfo))); + yield return typeof(SystemInfo).Assembly; // Include composable parts in the Common assembly - list.Add(GetAssembly(typeof(IApplicationHost))); + yield return typeof(IApplicationHost).Assembly; // Include composable parts in the Controller assembly - list.Add(GetAssembly(typeof(IServerApplicationHost))); + yield return typeof(IServerApplicationHost).Assembly; // Include composable parts in the Providers assembly - list.Add(GetAssembly(typeof(ProviderUtils))); + yield return typeof(ProviderUtils).Assembly; // Include composable parts in the Photos assembly - list.Add(GetAssembly(typeof(PhotoProvider))); + yield return typeof(PhotoProvider).Assembly; // Emby.Server implementations - list.Add(GetAssembly(typeof(InstallationManager))); + yield return typeof(InstallationManager).Assembly; // MediaEncoding - list.Add(GetAssembly(typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder))); + yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly; // Dlna - list.Add(GetAssembly(typeof(DlnaEntryPoint))); + yield return typeof(DlnaEntryPoint).Assembly; // Local metadata - list.Add(GetAssembly(typeof(BoxSetXmlSaver))); + yield return typeof(BoxSetXmlSaver).Assembly; // Notifications - list.Add(GetAssembly(typeof(NotificationManager))); + yield return typeof(NotificationManager).Assembly; // Xbmc - list.Add(GetAssembly(typeof(ArtistNfoProvider))); + yield return typeof(ArtistNfoProvider).Assembly; - list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple<Assembly, string>(i, null))); - - return list.ToList(); - } - - protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal(); - - private List<Tuple<Assembly, string>> GetPluginAssemblies(string path) - { - try - { - return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly)) - .Select(LoadAssembly) - .Where(a => a != null) - .ToList(); - } - catch (DirectoryNotFoundException) + foreach (var i in GetAssembliesWithPartsInternal()) { - return new List<Tuple<Assembly, string>>(); + yield return i; } } - private IEnumerable<string> FilterAssembliesToLoad(IEnumerable<string> paths) - { - - var exclude = new[] - { - "mbplus.dll", - "mbintros.dll", - "embytv.dll", - "Messenger.dll", - "Messages.dll", - "MediaBrowser.Plugins.TvMazeProvider.dll", - "MBBookshelf.dll", - "MediaBrowser.Channels.Adult.YouJizz.dll", - "MediaBrowser.Channels.Vine-co.dll", - "MediaBrowser.Plugins.Vimeo.dll", - "MediaBrowser.Channels.Vevo.dll", - "MediaBrowser.Plugins.Twitch.dll", - "MediaBrowser.Channels.SvtPlay.dll", - "MediaBrowser.Plugins.SoundCloud.dll", - "MediaBrowser.Plugins.SnesBox.dll", - "MediaBrowser.Plugins.RottenTomatoes.dll", - "MediaBrowser.Plugins.Revision3.dll", - "MediaBrowser.Plugins.NesBox.dll", - "MBChapters.dll", - "MediaBrowser.Channels.LeagueOfLegends.dll", - "MediaBrowser.Plugins.ADEProvider.dll", - "MediaBrowser.Channels.BallStreams.dll", - "MediaBrowser.Channels.Adult.Beeg.dll", - "ChannelDownloader.dll", - "Hamstercat.Emby.EmbyBands.dll", - "EmbyTV.dll", - "MediaBrowser.Channels.HitboxTV.dll", - "MediaBrowser.Channels.HockeyStreams.dll", - "MediaBrowser.Plugins.ITV.dll", - "MediaBrowser.Plugins.Lastfm.dll", - "ServerRestart.dll", - "MediaBrowser.Plugins.NotifyMyAndroidNotifications.dll", - "MetadataViewer.dll" - }; - - var minRequiredVersions = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase) - { - { "moviethemesongs.dll", new Version(1, 6) }, - { "themesongs.dll", new Version(1, 2) } - }; - - return paths.Where(path => - { - var filename = Path.GetFileName(path); - if (exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - if (minRequiredVersions.TryGetValue(filename, out Version minRequiredVersion)) - { - try - { - var version = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion); - - if (version < minRequiredVersion) - { - Logger.LogInformation("Not loading {filename} {version} because the minimum supported version is {minRequiredVersion}. Please update to the newer version", filename, version, minRequiredVersion); - return false; - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting version number from {path}", path); - - return false; - } - } - return true; - }); - } + protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal(); /// <summary> /// Gets the system status. @@ -1718,9 +1453,8 @@ namespace Emby.Server.Implementations SupportsHttps = SupportsHttps, HttpsPortNumber = HttpsPort, OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), - OperatingSystemDisplayName = OperatingSystemDisplayName, + OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName, CanSelfRestart = CanSelfRestart, - CanSelfUpdate = CanSelfUpdate, CanLaunchWebBrowser = CanLaunchWebBrowser, WanAddress = wanAddress, HasUpdateAvailable = HasUpdateAvailable, @@ -1788,7 +1522,7 @@ namespace Emby.Server.Implementations public async Task<string> GetWanApiUrl(CancellationToken cancellationToken) { - var url = "http://ipv4.icanhazip.com"; + const string url = "http://ipv4.icanhazip.com"; try { using (var response = await HttpClient.Get(new HttpRequestOptions @@ -2020,21 +1754,6 @@ namespace Emby.Server.Implementations } /// <summary> - /// Updates the application. - /// </summary> - /// <param name="package">The package that contains the update</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress) - { - await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false); - - HasUpdateAvailable = false; - - OnApplicationUpdated(package); - } - - /// <summary> /// This returns localhost in the case of no external dns, and the hostname if the /// dns is prefixed with a valid Uri prefix. /// </summary> diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs index 1135cf694..6b0fd2dc6 100644 --- a/Emby.Server.Implementations/Archiving/ZipClient.cs +++ b/Emby.Server.Implementations/Archiving/ZipClient.cs @@ -14,11 +14,9 @@ namespace Emby.Server.Implementations.Archiving /// </summary> public class ZipClient : IZipClient { - private readonly IFileSystem _fileSystem; - - public ZipClient(IFileSystem fileSystem) + public ZipClient() { - _fileSystem = fileSystem; + } /// <summary> diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 844f77a1a..303a8ac7b 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Channels { - class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask + public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask { private readonly IChannelManager _channelManager; private readonly IUserManager _userManager; diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index 6aeadda2f..cdfb5cadf 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -10,14 +10,18 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Collections { public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet> { - public CollectionImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + public CollectionImageProvider( + IFileSystem fileSystem, + IProviderManager providerManager, + IApplicationPaths applicationPaths, + IImageProcessor imageProcessor) + : base(fileSystem, providerManager, applicationPaths, imageProcessor) { } diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 812e48a1f..2b99e0ddf 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -342,14 +342,12 @@ namespace Emby.Server.Implementations.Collections { private readonly CollectionManager _collectionManager; private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; private ILogger _logger; - public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger) + public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger) { _collectionManager = (CollectionManager)collectionManager; _config = config; - _fileSystem = fileSystem; _logger = logger; } diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs new file mode 100644 index 000000000..30bfd8749 --- /dev/null +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Emby.Server.Implementations +{ + public static class ConfigurationOptions + { + public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string> + { + {"HttpListenerHost:DefaultRedirectPath", "web/index.html"} + }; + } +} diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 0f432c36c..fba81306b 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -224,7 +224,7 @@ namespace Emby.Server.Implementations.Data }); } - db.ExecuteAll(string.Join(";", queries.ToArray())); + db.ExecuteAll(string.Join(";", queries)); Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First()); } @@ -232,23 +232,6 @@ namespace Emby.Server.Implementations.Data protected virtual int? CacheSize => null; - internal static void CheckOk(int rc) - { - string msg = ""; - - if (raw.SQLITE_OK != rc) - { - throw CreateException((ErrorCode)rc, msg); - } - } - - internal static Exception CreateException(ErrorCode rc, string msg) - { - var exp = new Exception(msg); - - return exp; - } - private bool _disposed; protected void CheckDisposed() { @@ -375,13 +358,6 @@ namespace Emby.Server.Implementations.Data } } - public class DummyToken : IDisposable - { - public void Dispose() - { - } - } - public static IDisposable Read(this ReaderWriterLockSlim obj) { //if (BaseSqliteRepository.ThreadSafeMode > 0) @@ -390,6 +366,7 @@ namespace Emby.Server.Implementations.Data //} return new WriteLockToken(obj); } + public static IDisposable Write(this ReaderWriterLockSlim obj) { //if (BaseSqliteRepository.ThreadSafeMode > 0) diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index dcfe14943..f7743a3c2 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -1,11 +1,8 @@ using System; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Data @@ -13,18 +10,12 @@ namespace Emby.Server.Implementations.Data public class CleanDatabaseScheduledTask : ILibraryPostScanTask { private readonly ILibraryManager _libraryManager; - private readonly IItemRepository _itemRepo; private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IApplicationPaths _appPaths; - public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths) + public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger) { _libraryManager = libraryManager; - _itemRepo = itemRepo; _logger = logger; - _fileSystem = fileSystem; - _appPaths = appPaths; } public Task Run(IProgress<double> progress, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 3014e482d..70e5fa640 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -536,7 +536,7 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(item)); } - SaveItems(new List<BaseItem> { item }, cancellationToken); + SaveItems(new [] { item }, cancellationToken); } public void SaveImages(BaseItem item) @@ -576,7 +576,7 @@ namespace Emby.Server.Implementations.Data /// or /// cancellationToken /// </exception> - public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken) + public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken) { if (items == null) { @@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.Data CheckDisposed(); - var tuples = new List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>>(); + var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>(); foreach (var item in items) { var ancestorIds = item.SupportsAncestors ? @@ -599,7 +599,7 @@ namespace Emby.Server.Implementations.Data var userdataKey = item.GetUserDataKeys().FirstOrDefault(); var inheritedTags = item.GetInheritedTags(); - tuples.Add(new Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>(item, ancestorIds, topParent, userdataKey, inheritedTags)); + tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags)); } using (WriteLock.Write()) @@ -615,7 +615,7 @@ namespace Emby.Server.Implementations.Data } } - private void SaveItemsInTranscation(IDatabaseConnection db, List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>> tuples) + private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples) { var statements = PrepareAllSafe(db, new string[] { @@ -966,7 +966,7 @@ namespace Emby.Server.Implementations.Data if (item.ExtraIds.Length > 0) { - saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray())); + saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds)); } else { @@ -1183,9 +1183,9 @@ namespace Emby.Server.Implementations.Data /// <exception cref="ArgumentException"></exception> public BaseItem RetrieveItem(Guid id) { - if (id.Equals(Guid.Empty)) + if (id == Guid.Empty) { - throw new ArgumentNullException(nameof(id)); + throw new ArgumentException(nameof(id), "Guid can't be empty"); } CheckDisposed(); @@ -2079,14 +2079,14 @@ namespace Emby.Server.Implementations.Data return false; } - var sortingFields = query.OrderBy.Select(i => i.Item1); + var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase); - return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.SeriesDatePlayed, StringComparer.OrdinalIgnoreCase) + return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked) + || sortingFields.Contains(ItemSortBy.IsPlayed) + || sortingFields.Contains(ItemSortBy.IsUnplayed) + || sortingFields.Contains(ItemSortBy.PlayCount) + || sortingFields.Contains(ItemSortBy.DatePlayed) + || sortingFields.Contains(ItemSortBy.SeriesDatePlayed) || query.IsFavoriteOrLiked.HasValue || query.IsFavorite.HasValue || query.IsResumable.HasValue @@ -2094,9 +2094,9 @@ namespace Emby.Server.Implementations.Data || query.IsLiked.HasValue; } - private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields)) + private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields)) .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToList(); + .ToArray(); private string[] GetColumnNamesFromField(ItemFields field) { @@ -2151,18 +2151,26 @@ namespace Emby.Server.Implementations.Data } } - private bool HasProgramAttributes(InternalItemsQuery query) + private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; + "Series", + "Season", + "MusicAlbum", + "MusicArtist", + "PhotoAlbum" + }; + + private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + "Program", + "TvChannel", + "LiveTvProgram", + "LiveTvTvChannel" + }; - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + private bool HasProgramAttributes(InternalItemsQuery query) + { + if (_programExcludeParentTypes.Contains(query.ParentType)) { return false; } @@ -2172,29 +2180,18 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "Program", - "TvChannel", - "LiveTvProgram", - "LiveTvTvChannel" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _programTypes.Contains(x)); } - private bool HasServiceName(InternalItemsQuery query) + private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; + "TvChannel", + "LiveTvTvChannel" + }; - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + private bool HasServiceName(InternalItemsQuery query) + { + if (_programExcludeParentTypes.Contains(query.ParentType)) { return false; } @@ -2204,27 +2201,18 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "TvChannel", - "LiveTvTvChannel" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x)); } - private bool HasStartDate(InternalItemsQuery query) + private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; + "Program", + "LiveTvProgram" + }; - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + private bool HasStartDate(InternalItemsQuery query) + { + if (_programExcludeParentTypes.Contains(query.ParentType)) { return false; } @@ -2234,13 +2222,7 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "Program", - "LiveTvProgram" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x)); } private bool HasEpisodeAttributes(InternalItemsQuery query) @@ -2263,16 +2245,26 @@ namespace Emby.Server.Implementations.Data return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase); } - private bool HasArtistFields(InternalItemsQuery query) + + private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "PhotoAlbum" - }; + "Series", + "Season", + "PhotoAlbum" + }; + + private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + "Audio", + "MusicAlbum", + "MusicVideo", + "AudioBook", + "AudioPodcast" + }; - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + private bool HasArtistFields(InternalItemsQuery query) + { + if (_artistExcludeParentTypes.Contains(query.ParentType)) { return false; } @@ -2282,18 +2274,18 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "Audio", - "MusicAlbum", - "MusicVideo", - "AudioBook", - "AudioPodcast" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x)); } + private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + "Audio", + "MusicAlbum", + "MusicVideo", + "AudioBook", + "AudioPodcast" + }; + private bool HasSeriesFields(InternalItemsQuery query) { if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase)) @@ -2306,26 +2298,18 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "Book", - "AudioBook", - "Episode", - "Season" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x)); } - private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns) + private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns) { var list = startColumns.ToList(); - foreach (var field in allFields) + foreach (var field in _allFields) { if (!HasField(query, field)) { - foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList()) + foreach (var fieldToRemove in GetColumnNamesFromField(field)) { list.Remove(fieldToRemove); } @@ -2419,11 +2403,14 @@ namespace Emby.Server.Implementations.Data list.Add(builder.ToString()); - var excludeIds = query.ExcludeItemIds.ToList(); - excludeIds.Add(item.Id); - excludeIds.AddRange(item.ExtraIds); + var oldLen = query.ExcludeItemIds.Length; + var newLen = oldLen + item.ExtraIds.Length + 1; + var excludeIds = new Guid[newLen]; + query.ExcludeItemIds.CopyTo(excludeIds, 0); + excludeIds[oldLen] = item.Id; + item.ExtraIds.CopyTo(excludeIds, oldLen + 1); - query.ExcludeItemIds = excludeIds.ToArray(); + query.ExcludeItemIds = excludeIds; query.ExcludeProviderIds = item.ProviderIds; } @@ -2444,7 +2431,7 @@ namespace Emby.Server.Implementations.Data list.Add(builder.ToString()); } - return list.ToArray(); + return list; } private void BindSearchParams(InternalItemsQuery query, IStatement statement) @@ -2723,18 +2710,17 @@ namespace Emby.Server.Implementations.Data private void AddItem(List<BaseItem> items, BaseItem newItem) { - var providerIds = newItem.ProviderIds.ToList(); - for (var i = 0; i < items.Count; i++) { var item = items[i]; - foreach (var providerId in providerIds) + foreach (var providerId in newItem.ProviderIds) { if (providerId.Key == MetadataProviders.TmdbCollection.ToString()) { continue; } + if (item.GetProviderId(providerId.Key) == providerId.Value) { if (newItem.SourceType == SourceType.Library) @@ -2753,15 +2739,15 @@ namespace Emby.Server.Implementations.Data { var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds; - int slowThreshold = 1000; + int slowThreshold = 100; #if DEBUG - slowThreshold = 250; + slowThreshold = 10; #endif if (elapsed >= slowThreshold) { - Logger.LogWarning("{0} query time (slow): {1}ms. Query: {2}", + Logger.LogWarning("{0} query time (slow): {1:g}. Query: {2}", methodName, elapsed, commandText); @@ -2806,7 +2792,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses); commandText += whereText + GetGroupBy(query) @@ -2930,25 +2916,31 @@ namespace Emby.Server.Implementations.Data private string GetOrderByText(InternalItemsQuery query) { - var orderBy = query.OrderBy.ToList(); - var enableOrderInversion = false; - - if (query.SimilarTo != null && orderBy.Count == 0) + if (string.IsNullOrEmpty(query.SearchTerm)) { - orderBy.Add(new ValueTuple<string, SortOrder>("SimilarityScore", SortOrder.Descending)); - orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)); - } + int oldLen = query.OrderBy.Length; - if (!string.IsNullOrEmpty(query.SearchTerm)) + if (query.SimilarTo != null && oldLen == 0) + { + var arr = new (string, SortOrder)[oldLen + 2]; + query.OrderBy.CopyTo(arr, 0); + arr[oldLen] = ("SimilarityScore", SortOrder.Descending); + arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending); + query.OrderBy = arr; + } + } + else { - orderBy = new List<(string, SortOrder)>(); - orderBy.Add(new ValueTuple<string, SortOrder>("SearchScore", SortOrder.Descending)); - orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending)); + query.OrderBy = new [] + { + ("SearchScore", SortOrder.Descending), + (ItemSortBy.SortName, SortOrder.Ascending) + }; } - query.OrderBy = orderBy.ToArray(); + var orderBy = query.OrderBy; - if (orderBy.Count == 0) + if (orderBy.Length == 0) { return string.Empty; } @@ -2957,6 +2949,7 @@ namespace Emby.Server.Implementations.Data { var columnMap = MapOrderByField(i.Item1, query); var columnAscending = i.Item2 == SortOrder.Ascending; + const bool enableOrderInversion = false; if (columnMap.Item2 && enableOrderInversion) { columnAscending = !columnAscending; @@ -2968,7 +2961,7 @@ namespace Emby.Server.Implementations.Data })); } - private ValueTuple<string, bool> MapOrderByField(string name, InternalItemsQuery query) + private (string, bool) MapOrderByField(string name, InternalItemsQuery query) { if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase)) { @@ -3218,7 +3211,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses); commandText += whereText + GetGroupBy(query) @@ -4378,7 +4371,7 @@ namespace Emby.Server.Implementations.Data } else if (query.Years.Length > 1) { - var val = string.Join(",", query.Years.ToArray()); + var val = string.Join(",", query.Years); whereClauses.Add("ProductionYear in (" + val + ")"); } @@ -4952,7 +4945,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return result; } - return new[] { value }.Where(IsValidType); + if (IsValidType(value)) + { + return new[] { value }; + } + + return Array.Empty<string>(); } public void DeleteItem(Guid id, CancellationToken cancellationToken) @@ -5215,32 +5213,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) { return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); } @@ -5317,7 +5315,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) + private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) { if (query == null) { @@ -5335,7 +5333,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var typeClause = itemValueTypes.Length == 1 ? ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : - ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")"); + ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")"); InternalItemsQuery typeSubQuery = null; @@ -5363,11 +5361,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")"); - var typeWhereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses); - - itemCountColumnQuery += typeWhereText; + itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses); itemCountColumns = new Dictionary<string, string>() { @@ -5400,7 +5394,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type IsSeries = query.IsSeries }; - columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList(); + columns = GetFinalColumnsToSelect(query, columns); var commandText = "select " + string.Join(",", columns) @@ -5492,8 +5486,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { return connection.RunInTransaction(db => { - var list = new List<Tuple<BaseItem, ItemCounts>>(); - var result = new QueryResult<Tuple<BaseItem, ItemCounts>>(); + var list = new List<(BaseItem, ItemCounts)>(); + var result = new QueryResult<(BaseItem, ItemCounts)>(); var statements = PrepareAllSafe(db, statementTexts); @@ -5531,7 +5525,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var countStartColumn = columns.Count - 1; - list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount))); + list.Add((item, GetItemCounts(row, countStartColumn, typesToCount))); } } @@ -6198,6 +6192,5 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return item; } - } } diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index 866bd137f..495c3436a 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -11,7 +11,6 @@ namespace Emby.Server.Implementations.Devices { private readonly IApplicationPaths _appPaths; private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; private readonly object _syncLock = new object(); @@ -86,19 +85,10 @@ namespace Emby.Server.Implementations.Devices private string _id; - public DeviceId( - IApplicationPaths appPaths, - ILoggerFactory loggerFactory, - IFileSystem fileSystem) + public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory) { - if (fileSystem == null) - { - throw new ArgumentNullException(nameof(fileSystem)); - } - _appPaths = appPaths; _logger = loggerFactory.CreateLogger("SystemId"); - _fileSystem = fileSystem; } public string Value => _id ?? (_id = GetDeviceId()); diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index ec3649bca..7d6529a67 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -34,8 +34,6 @@ namespace Emby.Server.Implementations.Devices private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _libraryMonitor; private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; - private readonly INetworkManager _network; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localizationManager; @@ -55,17 +53,13 @@ namespace Emby.Server.Implementations.Devices IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, - IServerConfigurationManager config, - ILoggerFactory loggerFactory, - INetworkManager network) + IServerConfigurationManager config) { _json = json; _userManager = userManager; _fileSystem = fileSystem; _libraryMonitor = libraryMonitor; _config = config; - _logger = loggerFactory.CreateLogger(nameof(DeviceManager)); - _network = network; _libraryManager = libraryManager; _localizationManager = localizationManager; _authRepo = authRepo; @@ -414,14 +408,12 @@ namespace Emby.Server.Implementations.Devices { private readonly DeviceManager _deviceManager; private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; private ILogger _logger; - public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger) + public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger) { _deviceManager = (DeviceManager)deviceManager; _config = config; - _fileSystem = fileSystem; _logger = logger; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 983eb51e6..2233d3d40 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -36,13 +36,9 @@ namespace Emby.Server.Implementations.Dto private readonly IItemRepository _itemRepo; private readonly IImageProcessor _imageProcessor; - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; private readonly IProviderManager _providerManager; - private readonly Func<IChannelManager> _channelManagerFactory; private readonly IApplicationHost _appHost; - private readonly Func<IDeviceManager> _deviceManager; private readonly Func<IMediaSourceManager> _mediaSourceManager; private readonly Func<ILiveTvManager> _livetvManager; @@ -52,12 +48,8 @@ namespace Emby.Server.Implementations.Dto IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, - IServerConfigurationManager config, - IFileSystem fileSystem, IProviderManager providerManager, - Func<IChannelManager> channelManagerFactory, IApplicationHost appHost, - Func<IDeviceManager> deviceManager, Func<IMediaSourceManager> mediaSourceManager, Func<ILiveTvManager> livetvManager) { @@ -66,12 +58,8 @@ namespace Emby.Server.Implementations.Dto _userDataRepository = userDataRepository; _itemRepo = itemRepo; _imageProcessor = imageProcessor; - _config = config; - _fileSystem = fileSystem; _providerManager = providerManager; - _channelManagerFactory = channelManagerFactory; _appHost = appHost; - _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; _livetvManager = livetvManager; } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 8356a9501..bbf165d62 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -22,9 +22,11 @@ </ItemGroup> <ItemGroup> + <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.4.0" /> <PackageReference Include="sharpcompress" Version="0.22.0" /> - <PackageReference Include="SimpleInjector" Version="4.4.2" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" /> <PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" /> </ItemGroup> diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 774ed09da..a5badacee 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints { - class UserDataChangeNotifier : IServerEntryPoint + public class UserDataChangeNotifier : IServerEntryPoint { private readonly ISessionManager _sessionManager; private readonly ILogger _logger; diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs index 6167d1eaa..bbf51dd24 100644 --- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs +++ b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs @@ -3,27 +3,19 @@ using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.FFMpeg { public class FFMpegLoader { - private readonly IHttpClient _httpClient; private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; - private readonly IZipClient _zipClient; private readonly IFileSystem _fileSystem; private readonly FFMpegInstallInfo _ffmpegInstallInfo; - public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo) + public FFMpegLoader(IApplicationPaths appPaths, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo) { - _logger = logger; _appPaths = appPaths; - _httpClient = httpClient; - _zipClient = zipClient; _fileSystem = fileSystem; _ffmpegInstallInfo = ffmpegInstallInfo; } @@ -115,8 +107,7 @@ namespace Emby.Server.Implementations.FFMpeg var encoderFilename = Path.GetFileName(info.EncoderPath); var probeFilename = Path.GetFileName(info.ProbePath); - foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath) - .ToList()) + foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath)) { var allFiles = _fileSystem.GetFilePaths(directory, true).ToList(); diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 6ea1bd08e..2e0728136 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -66,11 +66,6 @@ namespace Emby.Server.Implementations.HttpClientManager // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c ServicePointManager.Expect100Continue = false; - -#if NET46 -// Trakt requests sometimes fail without this - ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls; -#endif } /// <summary> @@ -106,23 +101,6 @@ namespace Emby.Server.Implementations.HttpClientManager return client; } - private static WebRequest CreateWebRequest(string url) - { - try - { - return WebRequest.Create(url); - } - catch (NotSupportedException) - { - //Webrequest creation does fail on MONO randomly when using WebRequest.Create - //the issue occurs in the GetCreator method here: http://www.oschina.net/code/explore/mono-2.8.1/mcs/class/System/System.Net/WebRequest.cs - - var type = Type.GetType("System.Net.HttpRequestCreator, System, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"); - var creator = Activator.CreateInstance(type, nonPublic: true) as IWebRequestCreate; - return creator.Create(new Uri(url)) as HttpWebRequest; - } - } - private WebRequest GetRequest(HttpRequestOptions options, string method) { string url = options.Url; @@ -135,7 +113,7 @@ namespace Emby.Server.Implementations.HttpClientManager url = url.Replace(userInfo + "@", string.Empty); } - var request = CreateWebRequest(url); + var request = WebRequest.Create(url); if (request is HttpWebRequest httpWebRequest) { @@ -561,21 +539,10 @@ namespace Emby.Server.Implementations.HttpClientManager var contentLength = GetContentLength(httpResponse); - if (contentLength.HasValue) - { - using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) - { - await httpResponse.GetResponseStream().CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); - } - } - else + using (var stream = httpResponse.GetResponseStream()) + using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) { - // We're not able to track progress - using (var stream = httpResponse.GetResponseStream()) - using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); - } + await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } options.Progress.Report(100); @@ -627,14 +594,16 @@ namespace Emby.Server.Implementations.HttpClientManager var exception = new HttpException(webException.Message, webException); - var response = webException.Response as HttpWebResponse; - if (response != null) + using (var response = webException.Response as HttpWebResponse) { - exception.StatusCode = response.StatusCode; - - if ((int)response.StatusCode == 429) + if (response != null) { - client.LastTimeout = DateTime.UtcNow; + exception.StatusCode = response.StatusCode; + + if ((int)response.StatusCode == 429) + { + client.LastTimeout = DateTime.UtcNow; + } } } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 834ffb130..ee746c669 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -19,7 +20,9 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using ServiceStack.Text.Jsv; namespace Emby.Server.Implementations.HttpServer { @@ -53,20 +56,20 @@ namespace Emby.Server.Implementations.HttpServer IServerApplicationHost applicationHost, ILoggerFactory loggerFactory, IServerConfigurationManager config, - string defaultRedirectPath, + IConfiguration configuration, INetworkManager networkManager, IJsonSerializer jsonSerializer, - IXmlSerializer xmlSerializer, - Func<Type, Func<string, object>> funcParseFn) + IXmlSerializer xmlSerializer) { _appHost = applicationHost; _logger = loggerFactory.CreateLogger("HttpServer"); _config = config; - DefaultRedirectPath = defaultRedirectPath; + DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; - _funcParseFn = funcParseFn; + + _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; ResponseFilters = Array.Empty<Action<IRequest, IResponse, object>>(); @@ -284,31 +287,6 @@ namespace Emby.Server.Implementations.HttpServer } } - private static readonly string[] _skipLogExtensions = - { - ".js", - ".css", - ".woff", - ".woff2", - ".ttf", - ".html" - }; - - private bool EnableLogging(string url, string localPath) - { - var extension = GetExtension(url); - - return ((string.IsNullOrEmpty(extension) || !_skipLogExtensions.Contains(extension)) - && (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1)); - } - - private static string GetExtension(string url) - { - var parts = url.Split(new[] { '?' }, 2); - - return Path.GetExtension(parts[0]); - } - public static string RemoveQueryStringByKey(string url, string key) { var uri = new Uri(url); @@ -446,10 +424,9 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { - var date = DateTime.Now; + var stopWatch = new Stopwatch(); + stopWatch.Start(); var httpRes = httpReq.Response; - bool enableLog = false; - bool logHeaders = false; string urlToLog = null; string remoteIp = httpReq.RemoteIp; @@ -496,18 +473,8 @@ namespace Emby.Server.Implementations.HttpServer return; } - var operationName = httpReq.OperationName; - - enableLog = EnableLogging(urlString, localPath); - urlToLog = urlString; - logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1; - - if (enableLog) - { - urlToLog = GetUrlToLog(urlString); - - LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null); - } + urlToLog = GetUrlToLog(urlString); + Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers); if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) @@ -515,6 +482,7 @@ namespace Emby.Server.Implementations.HttpServer RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) { @@ -560,16 +528,19 @@ namespace Emby.Server.Implementations.HttpServer RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) { RedirectToUrl(httpRes, "../" + DefaultRedirectPath); return; } + if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.IsNullOrEmpty(localPath)) { RedirectToUrl(httpRes, "/" + DefaultRedirectPath); @@ -605,33 +576,21 @@ namespace Emby.Server.Implementations.HttpServer if (handler != null) { - await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false); + await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, httpReq.OperationName, cancellationToken).ConfigureAwait(false); } else { await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false); } } - catch (OperationCanceledException ex) - { - await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); - } - - catch (IOException ex) - { - await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); - } - - catch (SocketException ex) + catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException) { await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); } - catch (SecurityException ex) { await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false); } - catch (Exception ex) { var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase); @@ -642,13 +601,15 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.Close(); - if (enableLog) + stopWatch.Stop(); + var elapsed = stopWatch.Elapsed; + if (elapsed.TotalMilliseconds > 500) { - var statusCode = httpRes.StatusCode; - - var duration = DateTime.Now - date; - - LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null); + _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); + } + else + { + _logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } } } @@ -661,12 +622,11 @@ namespace Emby.Server.Implementations.HttpServer var pathParts = pathInfo.TrimStart('/').Split('/'); if (pathParts.Length == 0) { - _logger.LogError("Path parts empty for PathInfo: {pathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); + _logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); return null; } var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out string contentType); - if (restPath != null) { return new ServiceHandler diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 7445fd3c2..070717d48 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null) { - var result = new StreamWriter(content, contentType, _logger); + var result = new StreamWriter(content, contentType); if (responseHeaders == null) { @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer content = Array.Empty<byte>(); } - result = new StreamWriter(content, contentType, contentLength, _logger); + result = new StreamWriter(content, contentType, contentLength); } else { @@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary<string, string>(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) + if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) { responseHeaders["Expires"] = "-1"; } @@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer bytes = Array.Empty<byte>(); } - result = new StreamWriter(bytes, contentType, contentLength, _logger); + result = new StreamWriter(bytes, contentType, contentLength); } else { @@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary<string, string>(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) + if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) { responseHeaders["Expires"] = "-1"; } @@ -277,9 +277,10 @@ namespace Emby.Server.Implementations.HttpServer private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null) { - var contentType = request.ResponseContentType; + // TODO: @bond use Span and .Equals + var contentType = request.ResponseContentType?.Split(';')[0].Trim().ToLowerInvariant(); - switch (GetRealContentType(contentType)) + switch (contentType) { case "application/xml": case "text/xml": @@ -333,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger); + var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength); AddResponseHeaders(result, responseHeaders); return result; } else { - var result = new StreamWriter(content, contentType, contentLength, _logger); + var result = new StreamWriter(content, contentType, contentLength); AddResponseHeaders(result, responseHeaders); return result; } @@ -348,13 +349,19 @@ namespace Emby.Server.Implementations.HttpServer private byte[] Compress(byte[] bytes, string compressionType) { if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase)) + { return CompressBrotli(bytes); + } if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase)) + { return Deflate(bytes); + } if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase)) + { return GZip(bytes); + } throw new NotSupportedException(compressionType); } @@ -390,13 +397,6 @@ namespace Emby.Server.Implementations.HttpServer } } - public static string GetRealContentType(string contentType) - { - return contentType == null - ? null - : contentType.Split(';')[0].ToLowerInvariant().Trim(); - } - private static string SerializeToXmlString(object from) { using (var ms = new MemoryStream()) @@ -422,18 +422,20 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Pres the process optimized result. /// </summary> - private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType) + private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options) { bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; + AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified); if (!noCache) { - if (IsNotModified(requestContext, cacheKey)) + DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader); + + if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified)) { - AddAgeHeader(responseHeaders, lastDateModified); - AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration); + AddAgeHeader(responseHeaders, options.DateLastModified); - var result = new HttpResult(Array.Empty<byte>(), contentType ?? "text/html", HttpStatusCode.NotModified); + var result = new HttpResult(Array.Empty<byte>(), options.ContentType ?? "text/html", HttpStatusCode.NotModified); AddResponseHeaders(result, responseHeaders); @@ -441,8 +443,6 @@ namespace Emby.Server.Implementations.HttpServer } } - AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration); - return null; } @@ -487,9 +487,6 @@ namespace Emby.Server.Implementations.HttpServer options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path); } - var cacheKey = path + options.DateLastModified.Value.Ticks; - - options.CacheKey = cacheKey.GetMD5(); options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare)); options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); @@ -520,7 +517,6 @@ namespace Emby.Server.Implementations.HttpServer return GetStaticResult(requestContext, new StaticResultOptions { CacheDuration = cacheDuration, - CacheKey = cacheKey, ContentFactory = factoryFn, ContentType = contentType, DateLastModified = lastDateModified, @@ -534,14 +530,10 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var contentType = options.ContentType; - var etag = requestContext.Headers.Get("If-None-Match"); - var cacheKey = etag != null ? new Guid(etag.Trim('\"')) : Guid.Empty; - if (!cacheKey.Equals(Guid.Empty)) + if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since"))) { - var key = cacheKey.ToString("N"); - // See if the result is already cached in the browser - var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType); + var result = GetCachedResult(requestContext, options.ResponseHeaders, options); if (result != null) { @@ -553,6 +545,8 @@ namespace Emby.Server.Implementations.HttpServer var isHeadRequest = options.IsHeadRequest || string.Equals(requestContext.Verb, "HEAD", StringComparison.OrdinalIgnoreCase); var factoryFn = options.ContentFactory; var responseHeaders = options.ResponseHeaders; + AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified); + AddAgeHeader(responseHeaders, options.DateLastModified); var rangeHeader = requestContext.Headers.Get("Range"); @@ -566,21 +560,10 @@ namespace Emby.Server.Implementations.HttpServer }; AddResponseHeaders(hasHeaders, options.ResponseHeaders); - // Generate an ETag based on identifying information - TODO read contents from filesystem instead? - var responseId = $"{hasHeaders.ContentType}{options.Path}{hasHeaders.TotalContentLength}"; - var hashedId = MD5.Create().ComputeHash(Encoding.Default.GetBytes(responseId)); - hasHeaders.Headers["ETag"] = new Guid(hashedId).ToString("N"); - return hasHeaders; } var stream = await factoryFn().ConfigureAwait(false); - // Generate an etag based on stream content - var streamHash = MD5.Create().ComputeHash(stream); - var newEtag = new Guid(streamHash).ToString("N"); - - // reset position so the response can re-use it -- TODO is this ok? - stream.Position = 0; var totalContentLength = options.ContentLength; if (!totalContentLength.HasValue) @@ -603,7 +586,6 @@ namespace Emby.Server.Implementations.HttpServer }; AddResponseHeaders(hasHeaders, options.ResponseHeaders); - hasHeaders.Headers["ETag"] = newEtag; return hasHeaders; } else @@ -621,14 +603,13 @@ namespace Emby.Server.Implementations.HttpServer } } - var hasHeaders = new StreamWriter(stream, contentType, _logger) + var hasHeaders = new StreamWriter(stream, contentType) { OnComplete = options.OnComplete, OnError = options.OnError }; AddResponseHeaders(hasHeaders, options.ResponseHeaders); - hasHeaders.Headers["ETag"] = newEtag; return hasHeaders; } } @@ -641,37 +622,28 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Adds the caching responseHeaders. /// </summary> - private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration) + private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration, + bool noCache, DateTime? lastModifiedDate) { - if (cacheDuration.HasValue) - { - responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds); - } - else if (!string.IsNullOrEmpty(cacheKey)) - { - responseHeaders["Cache-Control"] = "public"; - } - else + if (noCache) { responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate"; responseHeaders["pragma"] = "no-cache, no-store, must-revalidate"; + return; } - AddExpiresHeader(responseHeaders, cacheKey, cacheDuration); - } - - /// <summary> - /// Adds the expires header. - /// </summary> - private static void AddExpiresHeader(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration) - { if (cacheDuration.HasValue) { - responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r"); + responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds; } - else if (string.IsNullOrEmpty(cacheKey)) + else { - responseHeaders["Expires"] = "-1"; + responseHeaders["Cache-Control"] = "public"; + } + + if (lastModifiedDate.HasValue) + { + responseHeaders["Last-Modified"] = lastModifiedDate.ToString(); } } @@ -687,32 +659,6 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); } } - /// <summary> - /// Determines whether [is not modified] [the specified cache key]. - /// </summary> - /// <param name="requestContext">The request context.</param> - /// <param name="cacheKey">The cache key.</param> - /// <param name="lastDateModified">The last date modified.</param> - /// <param name="cacheDuration">Duration of the cache.</param> - /// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns> - private bool IsNotModified(IRequest requestContext, Guid cacheKey) - { - var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match"); - - bool hasCacheKey = !cacheKey.Equals(Guid.Empty); - - // Validate If-None-Match - if (hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader)) - { - if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch) - && cacheKey.Equals(ifNoneMatch)) - { - return true; - } - } - - return false; - } /// <summary> /// Determines whether [is not modified] [the specified if modified since]. diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs deleted file mode 100644 index d22d9db26..000000000 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Globalization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.HttpServer -{ - public static class LoggerUtils - { - public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers) - { - if (headers == null) - { - logger.LogInformation("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty); - } - else - { - var headerText = string.Empty; - var index = 0; - - foreach (var i in headers) - { - if (index > 0) - { - headerText += ", "; - } - - headerText += i.Name + "=" + i.Value; - - index++; - } - - logger.LogInformation("HTTP {0} {1}. {2}", method, url, headerText); - } - } - - /// <summary> - /// Logs the response. - /// </summary> - /// <param name="logger">The logger.</param> - /// <param name="statusCode">The status code.</param> - /// <param name="url">The URL.</param> - /// <param name="endPoint">The end point.</param> - /// <param name="duration">The duration.</param> - public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration, QueryParamCollection headers) - { - var durationMs = duration.TotalMilliseconds; - var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; - - //var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); - var headerText = string.Empty; - logger.LogInformation("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText); - } - } -} diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 3269d44cf..cb2e3580b 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -14,8 +14,6 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> public class StreamWriter : IAsyncStreamWriter, IHasHeaders { - private ILogger Logger { get; set; } - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// <summary> @@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.HttpServer /// <param name="source">The source.</param> /// <param name="contentType">Type of the content.</param> /// <param name="logger">The logger.</param> - public StreamWriter(Stream source, string contentType, ILogger logger) + public StreamWriter(Stream source, string contentType) { if (string.IsNullOrEmpty(contentType)) { @@ -53,7 +51,6 @@ namespace Emby.Server.Implementations.HttpServer } SourceStream = source; - Logger = logger; Headers["Content-Type"] = contentType; @@ -69,7 +66,7 @@ namespace Emby.Server.Implementations.HttpServer /// <param name="source">The source.</param> /// <param name="contentType">Type of the content.</param> /// <param name="logger">The logger.</param> - public StreamWriter(byte[] source, string contentType, int contentLength, ILogger logger) + public StreamWriter(byte[] source, string contentType, int contentLength) { if (string.IsNullOrEmpty(contentType)) { @@ -77,7 +74,6 @@ namespace Emby.Server.Implementations.HttpServer } SourceBytes = source; - Logger = logger; Headers["Content-Type"] = contentType; diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 3668f6a7a..73242d0ad 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -17,31 +17,23 @@ namespace Emby.Server.Implementations.IO public class FileRefresher : IDisposable { private ILogger Logger { get; set; } - private ITaskManager TaskManager { get; set; } private ILibraryManager LibraryManager { get; set; } private IServerConfigurationManager ConfigurationManager { get; set; } - private readonly IFileSystem _fileSystem; private readonly List<string> _affectedPaths = new List<string>(); private Timer _timer; private readonly object _timerLock = new object(); public string Path { get; private set; } public event EventHandler<EventArgs> Completed; - private readonly IEnvironmentInfo _environmentInfo; - private readonly ILibraryManager _libraryManager; - public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1) + public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger) { logger.LogDebug("New file refresher created for {0}", path); Path = path; - _fileSystem = fileSystem; ConfigurationManager = configurationManager; LibraryManager = libraryManager; - TaskManager = taskManager; Logger = logger; - _environmentInfo = environmentInfo; - _libraryManager = libraryManager1; AddPath(path); } diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 607a4d333..d47342511 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.IO /// <summary> /// Any file name ending in any of these will be ignored by the watchers /// </summary> - private readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "small.jpg", "albumart.jpg", @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.IO "TempSBE" }; - private readonly string[] _alwaysIgnoreSubstrings = new string[] + private static readonly string[] _alwaysIgnoreSubstrings = new string[] { // Synology "eaDir", @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.IO ".actors" }; - private readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { // thumbs.db ".db", @@ -123,12 +123,6 @@ namespace Emby.Server.Implementations.IO /// <value>The logger.</value> private ILogger Logger { get; set; } - /// <summary> - /// Gets or sets the task manager. - /// </summary> - /// <value>The task manager.</value> - private ITaskManager TaskManager { get; set; } - private ILibraryManager LibraryManager { get; set; } private IServerConfigurationManager ConfigurationManager { get; set; } @@ -140,19 +134,12 @@ namespace Emby.Server.Implementations.IO /// </summary> public LibraryMonitor( ILoggerFactory loggerFactory, - ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) { - if (taskManager == null) - { - throw new ArgumentNullException(nameof(taskManager)); - } - LibraryManager = libraryManager; - TaskManager = taskManager; Logger = loggerFactory.CreateLogger(GetType().Name); ConfigurationManager = configurationManager; _fileSystem = fileSystem; @@ -541,7 +528,7 @@ namespace Emby.Server.Implementations.IO } } - var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _environmentInfo, LibraryManager); + var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger); newRefresher.Completed += NewRefresher_Completed; _activeRefreshers.Add(newRefresher); } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 7c44878ec..a64dfb607 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -4,8 +4,10 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Text; +using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO @@ -20,61 +22,27 @@ namespace Emby.Server.Implementations.IO private readonly bool _supportsAsyncFileStreams; private char[] _invalidFileNameChars; private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>(); - private bool EnableSeparateFileAndDirectoryQueries; - private string _tempPath; + private readonly string _tempPath; - private IEnvironmentInfo _environmentInfo; - private bool _isEnvironmentCaseInsensitive; - - private string _defaultDirectory; + private readonly IEnvironmentInfo _environmentInfo; + private readonly bool _isEnvironmentCaseInsensitive; public ManagedFileSystem( ILoggerFactory loggerFactory, IEnvironmentInfo environmentInfo, - string defaultDirectory, - string tempPath, - bool enableSeparateFileAndDirectoryQueries) + IApplicationPaths applicationPaths) { Logger = loggerFactory.CreateLogger("FileSystem"); _supportsAsyncFileStreams = true; - _tempPath = tempPath; + _tempPath = applicationPaths.TempDirectory; _environmentInfo = environmentInfo; - _defaultDirectory = defaultDirectory; - - // On Linux with mono, this needs to be true or symbolic links are ignored - EnableSeparateFileAndDirectoryQueries = enableSeparateFileAndDirectoryQueries; SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows); _isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; } - public virtual string DefaultDirectory - { - get - { - var value = _defaultDirectory; - - if (!string.IsNullOrEmpty(value)) - { - try - { - if (Directory.Exists(value)) - { - return value; - } - } - catch - { - - } - } - - return null; - } - } - public virtual void AddShortcutHandler(IShortcutHandler handler) { _shortcutHandlers.Add(handler); @@ -718,7 +686,7 @@ namespace Emby.Server.Implementations.IO SetAttributes(path, false, false); File.Delete(path); } - + public virtual List<FileSystemMetadata> GetDrives() { // Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout @@ -777,20 +745,15 @@ namespace Emby.Server.Implementations.IO var directoryInfo = new DirectoryInfo(path); var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - if (EnableSeparateFileAndDirectoryQueries) - { - return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption)) - .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption))); - } - - return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", searchOption)); + return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption)) + .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption))); } private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos) { return infos.Select(GetFileSystemMetadata); } - + public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false) { var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 80f746c7a..c644d13ea 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Controller.Entities; @@ -7,7 +6,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library { @@ -16,16 +14,14 @@ namespace Emby.Server.Implementations.Library /// </summary> public class CoreResolutionIgnoreRule : IResolverIgnoreRule { - private readonly IFileSystem _fileSystem; private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; private bool _ignoreDotPrefix; /// <summary> /// Any folder named in this list will be ignored - can be added to at runtime for extensibility /// </summary> - public static readonly Dictionary<string, string> IgnoreFolders = new List<string> + public static readonly string[] IgnoreFolders = { "metadata", "ps3_update", @@ -50,13 +46,11 @@ namespace Emby.Server.Implementations.Library // macos ".AppleDouble" - }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + }; - public CoreResolutionIgnoreRule(IFileSystem fileSystem, ILibraryManager libraryManager, ILogger logger) + public CoreResolutionIgnoreRule(ILibraryManager libraryManager) { - _fileSystem = fileSystem; _libraryManager = libraryManager; - _logger = logger; _ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT; } @@ -117,7 +111,7 @@ namespace Emby.Server.Implementations.Library if (fileInfo.IsDirectory) { // Ignore any folders in our list - if (IgnoreFolders.ContainsKey(filename)) + if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase)) { return true; } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 064006ebd..3c2272b56 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -986,7 +986,7 @@ namespace Emby.Server.Implementations.Library // Ensure the location is available. Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath); - return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress); + return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress); } /// <summary> @@ -1225,9 +1225,9 @@ namespace Emby.Server.Implementations.Library /// <exception cref="ArgumentNullException">id</exception> public BaseItem GetItemById(Guid id) { - if (id.Equals(Guid.Empty)) + if (id == Guid.Empty) { - throw new ArgumentNullException(nameof(id)); + throw new ArgumentException(nameof(id), "Guid can't be empty"); } if (LibraryItemsCache.TryGetValue(id, out BaseItem item)) @@ -1237,8 +1237,6 @@ namespace Emby.Server.Implementations.Library item = RetrieveItem(id); - //_logger.LogDebug("GetitemById {0}", id); - if (item != null) { RegisterItem(item); @@ -1333,7 +1331,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetItemIdsList(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) { if (query.User != null) { @@ -1344,7 +1342,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetStudios(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) { if (query.User != null) { @@ -1355,7 +1353,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetGenres(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query) { if (query.User != null) { @@ -1366,7 +1364,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetMusicGenres(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1377,7 +1375,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetAllArtists(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1421,7 +1419,7 @@ namespace Emby.Server.Implementations.Library } } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1808,18 +1806,16 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken) { - var list = items.ToList(); - - ItemRepository.SaveItems(list, cancellationToken); + ItemRepository.SaveItems(items, cancellationToken); - foreach (var item in list) + foreach (var item in items) { RegisterItem(item); } if (ItemAdded != null) { - foreach (var item in list) + foreach (var item in items) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -1853,7 +1849,7 @@ namespace Emby.Server.Implementations.Library /// <summary> /// Updates the item. /// </summary> - public void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { foreach (var item in items) { @@ -1908,7 +1904,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - UpdateItems(new List<BaseItem> { item }, parent, updateReason, cancellationToken); + UpdateItems(new [] { item }, parent, updateReason, cancellationToken); } /// <summary> @@ -2005,9 +2001,7 @@ namespace Emby.Server.Implementations.Library .FirstOrDefault(); } - var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); - - return options; + return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); } public string GetContentType(BaseItem item) @@ -2017,11 +2011,13 @@ namespace Emby.Server.Implementations.Library { return configuredContentType; } + configuredContentType = GetConfiguredContentType(item, true); if (!string.IsNullOrEmpty(configuredContentType)) { return configuredContentType; } + return GetInheritedContentType(item); } @@ -2056,6 +2052,7 @@ namespace Emby.Server.Implementations.Library { return collectionFolder.CollectionType; } + return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath); } @@ -2066,6 +2063,7 @@ namespace Emby.Server.Implementations.Library { return nameValuePair.Value; } + return null; } @@ -2108,9 +2106,9 @@ namespace Emby.Server.Implementations.Library string viewType, string sortName) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views"); - - path = Path.Combine(path, _fileSystem.GetValidFilename(viewType)); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, + "views", + _fileSystem.GetValidFilename(viewType)); var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView)); @@ -2543,7 +2541,7 @@ namespace Emby.Server.Implementations.Library var resolvers = new IItemResolver[] { - new GenericVideoResolver<Trailer>(this, _fileSystem) + new GenericVideoResolver<Trailer>(this) }; return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers) diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index d992f8d03..541b13cbe 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers { @@ -18,11 +17,9 @@ namespace Emby.Server.Implementations.Library.Resolvers where T : Video, new() { protected readonly ILibraryManager LibraryManager; - protected readonly IFileSystem FileSystem; - protected BaseVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) + protected BaseVideoResolver(ILibraryManager libraryManager) { - FileSystem = fileSystem; LibraryManager = libraryManager; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 472a3f105..848563679 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -548,7 +548,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private IImageProcessor _imageProcessor; - public MovieResolver(ILibraryManager libraryManager, IFileSystem fileSystem, IImageProcessor imageProcessor) : base(libraryManager, fileSystem) + public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor) + : base(libraryManager) { _imageProcessor = imageProcessor; } diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index a3298c580..db270c398 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers { @@ -15,13 +14,11 @@ namespace Emby.Server.Implementations.Library.Resolvers { private readonly IImageProcessor _imageProcessor; private readonly ILibraryManager _libraryManager; - private readonly IFileSystem _fileSystem; - public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager, IFileSystem fileSystem) + public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager) { _imageProcessor = imageProcessor; _libraryManager = libraryManager; - _fileSystem = fileSystem; } /// <summary> @@ -113,8 +110,7 @@ namespace Emby.Server.Implementations.Library.Resolvers return false; } - return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.')); + return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal); } - } } diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index fa8c89e88..7e4b38b4c 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -9,7 +9,7 @@ using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers { - class SpecialFolderResolver : FolderResolver<Folder> + public class SpecialFolderResolver : FolderResolver<Folder> { private readonly IFileSystem _fileSystem; private readonly IServerApplicationPaths _appPaths; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index fed0904d1..a6d18c9b5 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -3,7 +3,6 @@ using System.Linq; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers.TV { @@ -74,7 +73,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } - public EpisodeResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem) + public EpisodeResolver(ILibraryManager libraryManager) + : base(libraryManager) { } } diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs index 60752a85d..68d5d8b2d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs @@ -1,13 +1,13 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers { public class GenericVideoResolver<T> : BaseVideoResolver<T> where T : Video, new() { - public GenericVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem) + public GenericVideoResolver(ILibraryManager libraryManager) + : base(libraryManager) { } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 3ff84382f..dfef8e997 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -74,7 +74,6 @@ namespace Emby.Server.Implementations.Library private readonly Func<IDtoService> _dtoServiceFactory; private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; - private readonly ICryptoProvider _cryptographyProvider; private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; @@ -89,8 +88,7 @@ namespace Emby.Server.Implementations.Library Func<IDtoService> dtoServiceFactory, IServerApplicationHost appHost, IJsonSerializer jsonSerializer, - IFileSystem fileSystem, - ICryptoProvider cryptographyProvider) + IFileSystem fileSystem) { _logger = loggerFactory.CreateLogger(nameof(UserManager)); UserRepository = userRepository; @@ -101,7 +99,6 @@ namespace Emby.Server.Implementations.Library _appHost = appHost; _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; - _cryptographyProvider = cryptographyProvider; ConfigurationManager = configurationManager; _users = Array.Empty<User>(); @@ -171,9 +168,9 @@ namespace Emby.Server.Implementations.Library /// <exception cref="ArgumentNullException"></exception> public User GetUserById(Guid id) { - if (id.Equals(Guid.Empty)) + if (id == Guid.Empty) { - throw new ArgumentNullException(nameof(id)); + throw new ArgumentException(nameof(id), "Guid can't be empty"); } return Users.FirstOrDefault(u => u.Id == id); diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index 0ea543ba0..7899cf01b 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -24,7 +24,6 @@ namespace Emby.Server.Implementations.Library.Validators /// </summary> private readonly ILogger _logger; - private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; /// <summary> @@ -32,11 +31,10 @@ namespace Emby.Server.Implementations.Library.Validators /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> - public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem) + public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem) { _libraryManager = libraryManager; _logger = logger; - _config = config; _fileSystem = fileSystem; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 84ca130b7..fceb82ba1 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -105,8 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _mediaSourceManager = mediaSourceManager; _streamHelper = streamHelper; - _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); - _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger); + _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); + _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger); _timerProvider.TimerFired += _timerProvider_TimerFired; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; @@ -1708,7 +1708,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http)) { - return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _httpClient, _processFactory, _config, _assemblyInfo); + return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config); } return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index eed239514..9a9bae215 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -7,7 +7,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; @@ -17,7 +16,6 @@ using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -27,7 +25,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { private readonly ILogger _logger; private readonly IFileSystem _fileSystem; - private readonly IHttpClient _httpClient; private readonly IMediaEncoder _mediaEncoder; private readonly IServerApplicationPaths _appPaths; private bool _hasExited; @@ -38,19 +35,23 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly IJsonSerializer _json; private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); private readonly IServerConfigurationManager _config; - private readonly IAssemblyInfo _assemblyInfo; - public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, IHttpClient httpClient, IProcessFactory processFactory, IServerConfigurationManager config, IAssemblyInfo assemblyInfo) + public EncodedRecorder( + ILogger logger, + IFileSystem fileSystem, + IMediaEncoder mediaEncoder, + IServerApplicationPaths appPaths, + IJsonSerializer json, + IProcessFactory processFactory, + IServerConfigurationManager config) { _logger = logger; _fileSystem = fileSystem; _mediaEncoder = mediaEncoder; _appPaths = appPaths; _json = json; - _httpClient = httpClient; _processFactory = processFactory; _config = config; - _assemblyInfo = assemblyInfo; } private static bool CopySubtitles => false; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 6b02eaea8..a2ac60b31 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -17,15 +17,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV protected readonly ILogger Logger; private readonly string _dataPath; protected readonly Func<T, T, bool> EqualityComparer; - private readonly IFileSystem _fileSystem; - public ItemDataProvider(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer) + public ItemDataProvider(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer) { Logger = logger; _dataPath = dataPath; EqualityComparer = equalityComparer; _jsonSerializer = jsonSerializer; - _fileSystem = fileSystem; } public IReadOnlyList<T> GetAll() @@ -45,12 +43,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var jsonFile = path + ".json"; - try + if (!File.Exists(jsonFile)) { - return _jsonSerializer.DeserializeFromFile<List<T>>(jsonFile) ?? new List<T>(); + return new List<T>(); } - catch (FileNotFoundException) + + try { + return _jsonSerializer.DeserializeFromFile<List<T>>(jsonFile) ?? new List<T>(); } catch (IOException) { @@ -59,6 +59,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { Logger.LogError(ex, "Error deserializing {jsonFile}", jsonFile); } + return new List<T>(); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs index d2ad65a1e..520b44404 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -8,8 +7,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo> { - public SeriesTimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath) - : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) + public SeriesTimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath) + : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) { } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 1dcb02f43..3c807a8ea 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -19,8 +18,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired; - public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1) - : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) + public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1) + : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) { _logger = logger1; } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index a36302876..f7ef16fb0 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.LiveTv public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { - var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId); + var user = query.UserId == Guid.Empty ? null : _userManager.GetUserById(query.UserId); var topFolder = GetInternalLiveTvFolder(cancellationToken); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 6d1eff187..715f600a1 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -9,7 +9,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; @@ -23,18 +22,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected readonly IServerConfigurationManager Config; protected readonly ILogger Logger; protected IJsonSerializer JsonSerializer; - protected readonly IMediaEncoder MediaEncoder; protected readonly IFileSystem FileSystem; private readonly ConcurrentDictionary<string, ChannelCache> _channelCache = new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase); - protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem) + protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem) { Config = config; Logger = logger; JsonSerializer = jsonSerializer; - MediaEncoder = mediaEncoder; FileSystem = fileSystem; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 77b09a83d..24b100edd 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -31,15 +31,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IServerApplicationHost _appHost; private readonly ISocketFactory _socketFactory; private readonly INetworkManager _networkManager; - private readonly IEnvironmentInfo _environment; - public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem) + public HdHomerunHost( + IServerConfigurationManager config, + ILogger logger, + IJsonSerializer jsonSerializer, + IFileSystem fileSystem, + IHttpClient httpClient, + IServerApplicationHost appHost, + ISocketFactory socketFactory, + INetworkManager networkManager) + : base(config, logger, jsonSerializer, fileSystem) { _httpClient = httpClient; _appHost = appHost; _socketFactory = socketFactory; _networkManager = networkManager; - _environment = environment; } public string Name => "HD Homerun"; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 638796e2e..fdaaf0bae 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -26,15 +26,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { private readonly IHttpClient _httpClient; private readonly IServerApplicationHost _appHost; - private readonly IEnvironmentInfo _environment; private readonly INetworkManager _networkManager; private readonly IMediaSourceManager _mediaSourceManager; - public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem) + public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager) + : base(config, logger, jsonSerializer, fileSystem) { _httpClient = httpClient; _appHost = appHost; - _environment = environment; _networkManager = networkManager; _mediaSourceManager = mediaSourceManager; } @@ -52,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var channelIdPrefix = GetFullChannelIdPrefix(info); - var result = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false); + var result = await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false); return result.Cast<ChannelInfo>().ToList(); } @@ -115,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task Validate(TunerHostInfo info) { - using (var stream = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false)) + using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false)) { } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 9a01c42d3..ad124bb0f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -19,14 +19,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public class M3uParser { private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; private readonly IServerApplicationHost _appHost; - public M3uParser(ILogger logger, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost) + public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost) { _logger = logger; - _fileSystem = fileSystem; _httpClient = httpClient; _appHost = appHost; } @@ -157,56 +155,56 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null; string numberString = null; + string attributeValue; + double doubleValue; - // Check for channel number with the format from SatIp - // #EXTINF:0,84. VOX Schweiz - // #EXTINF:0,84.0 - VOX Schweiz - if (!string.IsNullOrWhiteSpace(nameInExtInf)) + if (attributes.TryGetValue("tvg-chno", out attributeValue)) { - var numberIndex = nameInExtInf.IndexOf(' '); - if (numberIndex > 0) + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) { - var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); - - if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) - { - numberString = numberPart; - } + numberString = attributeValue; } } - if (!string.IsNullOrWhiteSpace(numberString)) - { - numberString = numberString.Trim(); - } - if (!IsValidChannelNumber(numberString)) { - if (attributes.TryGetValue("tvg-id", out string value)) + if (attributes.TryGetValue("tvg-id", out attributeValue)) { - if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleValue)) + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) { - numberString = value; + numberString = attributeValue; + } + else if (attributes.TryGetValue("channel-id", out attributeValue)) + { + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) + { + numberString = attributeValue; + } } } - } - if (!string.IsNullOrWhiteSpace(numberString)) - { - numberString = numberString.Trim(); - } - - if (!IsValidChannelNumber(numberString)) - { - if (attributes.TryGetValue("channel-id", out string value)) + if (String.IsNullOrWhiteSpace(numberString)) { - numberString = value; + // Using this as a fallback now as this leads to Problems with channels like "5 USA" + // where 5 isnt ment to be the channel number + // Check for channel number with the format from SatIp + // #EXTINF:0,84. VOX Schweiz + // #EXTINF:0,84.0 - VOX Schweiz + if (!string.IsNullOrWhiteSpace(nameInExtInf)) + { + var numberIndex = nameInExtInf.IndexOf(' '); + if (numberIndex > 0) + { + var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); + + if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) + { + numberString = numberPart; + } + } + } } - } - if (!string.IsNullOrWhiteSpace(numberString)) - { - numberString = numberString.Trim(); } if (!IsValidChannelNumber(numberString)) @@ -214,7 +212,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts numberString = null; } - if (string.IsNullOrWhiteSpace(numberString)) + if (!string.IsNullOrWhiteSpace(numberString)) + { + numberString = numberString.Trim(); + } + else { if (string.IsNullOrWhiteSpace(mediaUrl)) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 4eff9252e..d74cf3be2 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var now = DateTime.UtcNow; - var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); + _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Path = tempFile; diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 7004d44db..9d4d74099 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -2,10 +2,10 @@ "Albums": "Album", "AppDeviceValues": "App: {0}, Enhed: {1}", "Application": "Applikation", - "Artists": "Kunstner", + "Artists": "Kunstnere", "AuthenticationSucceededWithUserName": "{0} bekræftet med succes", "Books": "Bøger", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}", "Channels": "Kanaler", "ChapterNameValue": "Kapitel {0}", "Collections": "Samlinger", @@ -14,41 +14,41 @@ "FailedLoginAttemptWithUserName": "Fejlet loginforsøg fra {0}", "Favorites": "Favoritter", "Folders": "Mapper", - "Genres": "Genre", + "Genres": "Genrer", "HeaderAlbumArtists": "Albumkunstnere", - "HeaderCameraUploads": "Camera Uploads", + "HeaderCameraUploads": "Kamera Uploads", "HeaderContinueWatching": "Fortsæt Afspilning", "HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteArtists": "Favoritkunstnere", - "HeaderFavoriteEpisodes": "Favoritepisoder", - "HeaderFavoriteShows": "Favorit serier", - "HeaderFavoriteSongs": "Favoritsange", + "HeaderFavoriteEpisodes": "Favorit-afsnit", + "HeaderFavoriteShows": "Favorit-serier", + "HeaderFavoriteSongs": "Favorit-sange", "HeaderLiveTV": "Live TV", "HeaderNextUp": "Næste", - "HeaderRecordingGroups": "Optagegrupper", + "HeaderRecordingGroups": "Optagelsesgrupper", "HomeVideos": "Hjemmevideoer", - "Inherit": "Arv", + "Inherit": "Nedarv", "ItemAddedWithName": "{0} blev tilføjet til biblioteket", "ItemRemovedWithName": "{0} blev fjernet fra biblioteket", "LabelIpAddressValue": "IP-adresse: {0}", "LabelRunningTimeValue": "Spilletid: {0}", "Latest": "Seneste", "MessageApplicationUpdated": "Jellyfin Server er blevet opdateret", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server konfigurationssektion {0} er blevet opdateret", - "MessageServerConfigurationUpdated": "Serverkonfiguration er blevet opdateret", + "MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret", + "MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret", "MixedContent": "Blandet indhold", "Movies": "Film", "Music": "Musik", "MusicVideos": "Musikvideoer", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "{0} installationen mislykkedes", "NameSeasonNumber": "Sæson {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NameSeasonUnknown": "Ukendt Sæson", + "NewVersionIsAvailable": "En ny version af Jellyfin Server er tilgængelig til download.", "NotificationOptionApplicationUpdateAvailable": "Opdatering til applikation tilgængelig", "NotificationOptionApplicationUpdateInstalled": "Opdatering til applikation installeret", - "NotificationOptionAudioPlayback": "Audioafspilning påbegyndt", - "NotificationOptionAudioPlaybackStopped": "Audioafspilning stoppet", + "NotificationOptionAudioPlayback": "Lydafspilning påbegyndt", + "NotificationOptionAudioPlaybackStopped": "Lydafspilning stoppet", "NotificationOptionCameraImageUploaded": "Kamerabillede uploadet", "NotificationOptionInstallationFailed": "Installationsfejl", "NotificationOptionNewLibraryContent": "Nyt indhold tilføjet", @@ -70,16 +70,16 @@ "ProviderValue": "Udbyder: {0}", "ScheduledTaskFailedWithName": "{0} fejlet", "ScheduledTaskStartedWithName": "{0} påbegyndt", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Shows", + "ServerNameNeedsToBeRestarted": "{0} skal genstartes", + "Shows": "Serier", "Songs": "Sange", - "StartupEmbyServerIsLoading": "Jellyfin Server indlæser. Prøv venligst igen om kort tid.", + "StartupEmbyServerIsLoading": "Jellyfin Server er i gang med at starte op. Prøv venligst igen om lidt.", "SubtitleDownloadFailureForItem": "Fejlet i download af undertekster for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke downloades fra {0} til {1}", "SubtitlesDownloadedForItem": "Undertekster downloadet for {0}", "Sync": "Synk", "System": "System", - "TvShows": "TV Shows", + "TvShows": "TV serier", "User": "Bruger", "UserCreatedWithName": "Bruger {0} er blevet oprettet", "UserDeletedWithName": "Brugeren {0} er blevet slettet", @@ -88,10 +88,10 @@ "UserOfflineFromDevice": "{0} har afbrudt fra {1}", "UserOnlineFromDevice": "{0} er online fra {1}", "UserPasswordChangedWithName": "Adgangskode er ændret for bruger {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserPolicyUpdatedWithName": "Brugerpolitik er blevet opdateret for {0}", "UserStartedPlayingItemWithValues": "{0} har påbegyndt afspilning af {1}", - "UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}", + "ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek", "ValueSpecialEpisodeName": "Special - {0}", "VersionNumber": "Version {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 7bd2e90fe..6fd63a514 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -3,61 +3,61 @@ "AppDeviceValues": "App: {0}, Gerät: {1}", "Application": "Anwendung", "Artists": "Interpreten", - "AuthenticationSucceededWithUserName": "{0} erfolgreich authentifiziert", + "AuthenticationSucceededWithUserName": "{0} hat sich angemeldet", "Books": "Bücher", - "CameraImageUploadedFrom": "Ein neues Bild wurde hochgeladen von {0}", + "CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}", "Channels": "Kanäle", "ChapterNameValue": "Kapitel {0}", "Collections": "Sammlungen", "DeviceOfflineWithName": "{0} wurde getrennt", - "DeviceOnlineWithName": "{0} ist verbunden", + "DeviceOnlineWithName": "{0} hat sich verbunden", "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}", "Favorites": "Favoriten", "Folders": "Verzeichnisse", "Genres": "Genres", - "HeaderAlbumArtists": "Album-Künstler", - "HeaderCameraUploads": "Kamera Uploads", + "HeaderAlbumArtists": "Album-Interpreten", + "HeaderCameraUploads": "Kamera-Uploads", "HeaderContinueWatching": "Weiterschauen", "HeaderFavoriteAlbums": "Lieblingsalben", - "HeaderFavoriteArtists": "Interpreten Favoriten", + "HeaderFavoriteArtists": "Lieblings-Interpreten", "HeaderFavoriteEpisodes": "Lieblingsepisoden", "HeaderFavoriteShows": "Lieblingsserien", - "HeaderFavoriteSongs": "Lieder Favoriten", - "HeaderLiveTV": "Live TV", + "HeaderFavoriteSongs": "Lieblingslieder", + "HeaderLiveTV": "Live-TV", "HeaderNextUp": "Als Nächstes", "HeaderRecordingGroups": "Aufnahme-Gruppen", "HomeVideos": "Heimvideos", "Inherit": "Übernehmen", "ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt", "ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt", - "LabelIpAddressValue": "IP Adresse: {0}", + "LabelIpAddressValue": "IP-Adresse: {0}", "LabelRunningTimeValue": "Laufzeit: {0}", "Latest": "Neueste", - "MessageApplicationUpdated": "Jellyfin Server wurde auf den neusten Stand gebracht.", - "MessageApplicationUpdatedTo": "Jellyfin Server wurde auf Version {0} aktualisiert", + "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert", + "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert", "MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert", - "MessageServerConfigurationUpdated": "Server Einstellungen wurden aktualisiert", + "MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert", "MixedContent": "Gemischte Inhalte", "Movies": "Filme", "Music": "Musik", "MusicVideos": "Musikvideos", - "NameInstallFailed": "{0} Installation fehlgeschlagen", + "NameInstallFailed": "Installation von {0} fehlgeschlagen", "NameSeasonNumber": "Staffel {0}", "NameSeasonUnknown": "Staffel unbekannt", - "NewVersionIsAvailable": "Eine neue Version von Jellyfin Server steht zum Download bereit.", + "NewVersionIsAvailable": "Eine neue Version von Jellyfin-Server steht zum Download bereit.", "NotificationOptionApplicationUpdateAvailable": "Anwendungsaktualisierung verfügbar", "NotificationOptionApplicationUpdateInstalled": "Anwendungsaktualisierung installiert", "NotificationOptionAudioPlayback": "Audiowiedergabe gestartet", "NotificationOptionAudioPlaybackStopped": "Audiowiedergabe gestoppt", - "NotificationOptionCameraImageUploaded": "Kamera Bild hochgeladen", + "NotificationOptionCameraImageUploaded": "Foto hochgeladen", "NotificationOptionInstallationFailed": "Installationsfehler", "NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugefügt", - "NotificationOptionPluginError": "Plugin Fehler", + "NotificationOptionPluginError": "Plugin-Fehler", "NotificationOptionPluginInstalled": "Plugin installiert", "NotificationOptionPluginUninstalled": "Plugin deinstalliert", "NotificationOptionPluginUpdateInstalled": "Pluginaktualisierung installiert", "NotificationOptionServerRestartRequired": "Serverneustart notwendig", - "NotificationOptionTaskFailed": "Geplante Aufgaben fehlgeschlagen", + "NotificationOptionTaskFailed": "Geplante Aufgabe fehlgeschlagen", "NotificationOptionUserLockedOut": "Benutzer ausgeschlossen", "NotificationOptionVideoPlayback": "Videowiedergabe gestartet", "NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt", @@ -68,18 +68,18 @@ "PluginUninstalledWithName": "{0} wurde deinstalliert", "PluginUpdatedWithName": "{0} wurde aktualisiert", "ProviderValue": "Anbieter: {0}", - "ScheduledTaskFailedWithName": "{0} fehlgeschlagen", - "ScheduledTaskStartedWithName": "{0} gestartet", + "ScheduledTaskFailedWithName": "{0} ist fehlgeschlagen", + "ScheduledTaskStartedWithName": "{0} wurde gestartet", "ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden", "Shows": "Serien", "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server startet, bitte versuche es gleich noch einmal.", + "StartupEmbyServerIsLoading": "Jellyfin-Server startet, bitte versuche es gleich noch einmal.", "SubtitleDownloadFailureForItem": "Download der Untertitel fehlgeschlagen für {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden", "SubtitlesDownloadedForItem": "Untertitel heruntergeladen für {0}", "Sync": "Synchronisation", "System": "System", - "TvShows": "TV Sendungen", + "TvShows": "TV-Sendungen", "User": "Benutzer", "UserCreatedWithName": "Benutzer {0} wurde erstellt", "UserDeletedWithName": "Benutzer {0} wurde gelöscht", @@ -88,10 +88,10 @@ "UserOfflineFromDevice": "{0} wurde getrennt von {1}", "UserOnlineFromDevice": "{0} ist online von {1}", "UserPasswordChangedWithName": "Das Passwort für Benutzer {0} wurde geändert", - "UserPolicyUpdatedWithName": "Benutzerrichtlinie wurde für {0} aktualisiert", - "UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} gestartet", - "UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} beendet", - "ValueHasBeenAddedToLibrary": "{0} wurde ihrer Bibliothek hinzugefügt", - "ValueSpecialEpisodeName": "Special - {0}", + "UserPolicyUpdatedWithName": "Benutzerrichtlinie von {0} wurde aktualisiert", + "UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet", + "UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet", + "ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt", + "ValueSpecialEpisodeName": "Extra - {0}", "VersionNumber": "Version {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 332902292..67d4068cf 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -90,7 +90,7 @@ "UserPasswordChangedWithName": "Password has been changed for user {0}", "UserPolicyUpdatedWithName": "User policy has been updated for {0}", "UserStartedPlayingItemWithValues": "{0} has started playing {1}", - "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", + "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueSpecialEpisodeName": "Special - {0}", "VersionNumber": "Version {0}" diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index f19cd532b..aa855ed21 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -30,7 +30,7 @@ "Inherit": "Inherit", "ItemAddedWithName": "{0} was added to the library", "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", + "LabelIpAddressValue": "IP address: {0}", "LabelRunningTimeValue": "Running time: {0}", "Latest": "Latest", "MessageApplicationUpdated": "Jellyfin Server has been updated", diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 5d118d21f..1850b8f25 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -5,46 +5,46 @@ "Artists": "Artistas", "AuthenticationSucceededWithUserName": "{0} autenticado correctamente", "Books": "Libros", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}", "Channels": "Canales", "ChapterNameValue": "Capítulo {0}", "Collections": "Colecciones", "DeviceOfflineWithName": "{0} se ha desconectado", "DeviceOnlineWithName": "{0} está conectado", - "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión a partir de {0}", + "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión desde {0}", "Favorites": "Favoritos", "Folders": "Carpetas", "Genres": "Géneros", - "HeaderAlbumArtists": "Artistas del Álbum", - "HeaderCameraUploads": "Camera Uploads", + "HeaderAlbumArtists": "Artistas del álbum", + "HeaderCameraUploads": "Subidas desde cámara", "HeaderContinueWatching": "Continuar viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteSongs": "Canciones favoritas", - "HeaderLiveTV": "TV en vivo", + "HeaderLiveTV": "TV en directo", "HeaderNextUp": "Siguiendo", "HeaderRecordingGroups": "Grupos de grabación", - "HomeVideos": "Vídeos de inicio", + "HomeVideos": "Vídeos caseros", "Inherit": "Heredar", "ItemAddedWithName": "{0} se ha añadido a la biblioteca", - "ItemRemovedWithName": "{0} se elimina de la biblioteca", + "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", "LabelIpAddressValue": "Dirección IP: {0}", "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", "Latest": "Últimos", "MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "La sección de configuración del servidor {0} ha sido actualizado", + "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada", "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", "MixedContent": "Contenido mixto", - "Movies": "Peliculas", + "Movies": "Películas", "Music": "Música", - "MusicVideos": "Videos musicales", - "NameInstallFailed": "{0} installation failed", + "MusicVideos": "Vídeos musicales", + "NameInstallFailed": "{0} error de instalación", "NameSeasonNumber": "Temporada {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NameSeasonUnknown": "Temporada desconocida", + "NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.", "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", "NotificationOptionAudioPlayback": "Se inició la reproducción de audio", @@ -56,13 +56,13 @@ "NotificationOptionPluginInstalled": "Plugin instalado", "NotificationOptionPluginUninstalled": "Plugin desinstalado", "NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada", - "NotificationOptionServerRestartRequired": "Requiere reinicio del servidor", + "NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor", "NotificationOptionTaskFailed": "Error de tarea programada", "NotificationOptionUserLockedOut": "Usuario bloqueado", "NotificationOptionVideoPlayback": "Se inició la reproducción de vídeo", "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detenida", "Photos": "Fotos", - "Playlists": "Listas reproducción", + "Playlists": "Listas de reproducción", "Plugin": "Plugin", "PluginInstalledWithName": "{0} se ha instalado", "PluginUninstalledWithName": "{0} se ha desinstalado", @@ -70,16 +70,16 @@ "ProviderValue": "Proveedor: {0}", "ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskStartedWithName": "{0} iniciada", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", "Shows": "Series", "Songs": "Canciones", "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", "SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}", "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}", "Sync": "Sincronizar", "System": "Sistema", - "TvShows": "Series TV", + "TvShows": "Series de TV", "User": "Usuario", "UserCreatedWithName": "El usuario {0} ha sido creado", "UserDeletedWithName": "El usuario {0} ha sido borrado", @@ -88,10 +88,10 @@ "UserOfflineFromDevice": "{0} se ha desconectado de {1}", "UserOnlineFromDevice": "{0} está en línea desde {1}", "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} ha comenzado reproducir {1}", - "UserStoppedPlayingItemWithValues": "{0} ha parado de reproducir {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia", "ValueSpecialEpisodeName": "Especial - {0}", "VersionNumber": "Versión {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index aa9a1add3..52afb4e49 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -36,7 +36,7 @@ "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour", "MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}", "MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour", - "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour.", + "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour", "MixedContent": "Contenu mixte", "Movies": "Films", "Music": "Musique", diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index d2d16b18f..c0f988abe 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -5,48 +5,48 @@ "Artists": "Előadók", "AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva", "Books": "Könyvek", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Új kamerakép került feltöltésre {0}", "Channels": "Csatornák", "ChapterNameValue": "Jelenet {0}", "Collections": "Gyűjtemények", "DeviceOfflineWithName": "{0} kijelentkezett", "DeviceOnlineWithName": "{0} belépett", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", + "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet {0}", "Favorites": "Kedvencek", "Folders": "Könyvtárak", "Genres": "Műfajok", "HeaderAlbumArtists": "Album Előadók", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Vetítés(ek) folytatása", + "HeaderCameraUploads": "Kamera feltöltések", + "HeaderContinueWatching": "Folyamatban lévő filmek", "HeaderFavoriteAlbums": "Kedvenc Albumok", "HeaderFavoriteArtists": "Kedvenc Művészek", "HeaderFavoriteEpisodes": "Kedvenc Epizódok", "HeaderFavoriteShows": "Kedvenc Műsorok", "HeaderFavoriteSongs": "Kedvenc Dalok", - "HeaderLiveTV": "Live TV", + "HeaderLiveTV": "Élő TV", "HeaderNextUp": "Következik", - "HeaderRecordingGroups": "Recording Groups", + "HeaderRecordingGroups": "Felvételi csoportok", "HomeVideos": "Házi videók", "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip cím: {0}", - "LabelRunningTimeValue": "Running time: {0}", + "ItemAddedWithName": "{0} hozzáadva a könyvtárhoz", + "ItemRemovedWithName": "{0} eltávolítva a könyvtárból", + "LabelIpAddressValue": "IP cím: {0}", + "LabelRunningTimeValue": "Futási idő: {0}", "Latest": "Legújabb", "MessageApplicationUpdated": "Jellyfin Szerver frissítve", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve", "MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve", "MixedContent": "Vegyes tartalom", "Movies": "Filmek", "Music": "Zene", "MusicVideos": "Zenei Videók", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Program frissítés elérhető", - "NotificationOptionApplicationUpdateInstalled": "Program frissítés telepítve", + "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", "NotificationOptionAudioPlayback": "Audió lejátszás elkezdve", "NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve", "NotificationOptionCameraImageUploaded": "Kamera kép feltöltve", @@ -57,7 +57,7 @@ "NotificationOptionPluginUninstalled": "Bővítmény eltávolítva", "NotificationOptionPluginUpdateInstalled": "Bővítmény frissítés telepítve", "NotificationOptionServerRestartRequired": "Szerver újraindítás szükséges", - "NotificationOptionTaskFailed": "Scheduled task failure", + "NotificationOptionTaskFailed": "Ütemezett feladat hiba", "NotificationOptionUserLockedOut": "Felhasználó tiltva", "NotificationOptionVideoPlayback": "Videó lejátszás elkezdve", "NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve", @@ -68,30 +68,30 @@ "PluginUninstalledWithName": "{0} eltávolítva", "PluginUpdatedWithName": "{0} frissítve", "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ScheduledTaskFailedWithName": "{0} hiba", + "ScheduledTaskStartedWithName": "{0} elkezdve", + "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani", "Shows": "Műsorok", "Songs": "Dalok", - "StartupEmbyServerIsLoading": "Jellyfin Szerver betöltődik. Kérjük, próbáld meg újra később.", + "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded 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}", "Sync": "Szinkronizál", "System": "Rendszer", "TvShows": "TV Műsorok", "User": "Felhasználó", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", + "UserCreatedWithName": "{0} felhasználó létrehozva", + "UserDeletedWithName": "{0} felhasználó törölve", "UserDownloadingItemWithValues": "{0} letölti {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} kijelentkezett innen {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} elkezdte játszani a következőt {1}", - "UserStoppedPlayingItemWithValues": "{0} befejezte a következőt {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "UserLockedOutWithName": "{0} felhasználó zárolva van", + "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}", + "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", "ValueSpecialEpisodeName": "Special - {0}", - "VersionNumber": "Verzió {0}" + "VersionNumber": "Verzió: {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index b3d9c16cf..a5f1e8f94 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -5,13 +5,13 @@ "Artists": "Artisti", "AuthenticationSucceededWithUserName": "{0} autenticato con successo", "Books": "Libri", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera {0}", "Channels": "Canali", "ChapterNameValue": "Capitolo {0}", "Collections": "Collezioni", "DeviceOfflineWithName": "{0} è stato disconnesso", "DeviceOnlineWithName": "{0} è connesso", - "FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}", + "FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}", "Favorites": "Preferiti", "Folders": "Cartelle", "Genres": "Generi", @@ -19,9 +19,9 @@ "HeaderCameraUploads": "Caricamenti Fotocamera", "HeaderContinueWatching": "Continua a guardare", "HeaderFavoriteAlbums": "Album preferiti", - "HeaderFavoriteArtists": "Artisti preferiti", + "HeaderFavoriteArtists": "Artisti Preferiti", "HeaderFavoriteEpisodes": "Episodi Preferiti", - "HeaderFavoriteShows": "Show preferiti", + "HeaderFavoriteShows": "Serie TV Preferite", "HeaderFavoriteSongs": "Brani Preferiti", "HeaderLiveTV": "Diretta TV", "HeaderNextUp": "Prossimo", diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index ae256f79d..658d168e9 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -1,97 +1,97 @@ { - "Albums": "Альбомдар", - "AppDeviceValues": "Қолданба: {0}, Құрылғы: {1}", - "Application": "Қолданба", - "Artists": "Орындаушылар", - "AuthenticationSucceededWithUserName": "{0} түпнұсқалығын расталуы сәтті", - "Books": "Кітаптар", - "CameraImageUploadedFrom": "Жаңа сурет {0} камерасынан жүктеп алынды", - "Channels": "Арналар", - "ChapterNameValue": "{0}-сахна", - "Collections": "Жиынтықтар", - "DeviceOfflineWithName": "{0} ажыратылған", - "DeviceOnlineWithName": "{0} қосылған", - "FailedLoginAttemptWithUserName": "{0} тарапынан кіру әрекеті сәтсіз", - "Favorites": "Таңдаулылар", - "Folders": "Қалталар", - "Genres": "Жанрлар", - "HeaderAlbumArtists": "Альбом орындаушылары", - "HeaderCameraUploads": "Камерадан жүктелгендер", - "HeaderContinueWatching": "Қарауды жалғастыру", - "HeaderFavoriteAlbums": "Таңдаулы альбомдар", - "HeaderFavoriteArtists": "Таңдаулы орындаушылар", - "HeaderFavoriteEpisodes": "Таңдаулы бөлімдер", - "HeaderFavoriteShows": "Таңдаулы көрсетімдер", - "HeaderFavoriteSongs": "Таңдаулы әуендер", - "HeaderLiveTV": "Эфир", - "HeaderNextUp": "Кезекті", - "HeaderRecordingGroups": "Жазба топтары", - "HomeVideos": "Үйлік бейнелер", - "Inherit": "Мұраға иелену", - "ItemAddedWithName": "{0} тасығышханаға үстелінді", - "ItemRemovedWithName": "{0} тасығышханадан аласталды", - "LabelIpAddressValue": "IP-мекенжайы: {0}", - "LabelRunningTimeValue": "Іске қосылу уақыты: {0}", - "Latest": "Ең кейінгі", - "MessageApplicationUpdated": "Jellyfin Server жаңартылды.", - "MessageApplicationUpdatedTo": "Jellyfin Server {0} үшін жаңартылды", - "MessageNamedServerConfigurationUpdatedWithValue": "Сервер теңшелімі ({0} бөлімі) жаңартылды", - "MessageServerConfigurationUpdated": "Сервер теңшелімі жаңартылды", - "MixedContent": "Аралас мазмұн", - "Movies": "Фильмдер", - "Music": "Музыка", - "MusicVideos": "Музыкалық бейнелер", - "NameInstallFailed": "{0} орнатылуы сәтсіз", - "NameSeasonNumber": "{0}-маусым", - "NameSeasonUnknown": "Белгісіз маусым", - "NewVersionIsAvailable": "Жаңа Jellyfin Server нұсқасы жүктеп алуға қолжетімді.", - "NotificationOptionApplicationUpdateAvailable": "Қолданба жаңартуы қолжетімді", - "NotificationOptionApplicationUpdateInstalled": "Қолданба жаңартуы орнатылды", - "NotificationOptionAudioPlayback": "Дыбыс ойнатуы басталды", - "NotificationOptionAudioPlaybackStopped": "Дыбыс ойнатуы тоқтатылды", - "NotificationOptionCameraImageUploaded": "Камерадан фотосурет кері қотарылған", - "NotificationOptionInstallationFailed": "Орнату сәтсіздігі", - "NotificationOptionNewLibraryContent": "Жаңа мазмұн үстелген", - "NotificationOptionPluginError": "Плагин сәтсіздігі", - "NotificationOptionPluginInstalled": "Плагин орнатылды", - "NotificationOptionPluginUninstalled": "Плагин орнатуы болдырылмады", - "NotificationOptionPluginUpdateInstalled": "Плагин жаңартуы орнатылды", - "NotificationOptionServerRestartRequired": "Серверді қайта іске қосу қажет", - "NotificationOptionTaskFailed": "Жоспарлаған тапсырма сәтсіздігі", - "NotificationOptionUserLockedOut": "Пайдаланушы құрсаулы", - "NotificationOptionVideoPlayback": "Бейне ойнатуы басталды", - "NotificationOptionVideoPlaybackStopped": "Бейне ойнатуы тоқтатылды", - "Photos": "Фотосуреттер", - "Playlists": "Ойнату тізімдері", - "Plugin": "Плагин", - "PluginInstalledWithName": "{0} орнатылды", - "PluginUninstalledWithName": "{0} жойылды", - "PluginUpdatedWithName": "{0} жаңартылды", - "ProviderValue": "Жеткізуші: {0}", - "ScheduledTaskFailedWithName": "{0} сәтсіз", - "ScheduledTaskStartedWithName": "{0} іске қосылды", - "ServerNameNeedsToBeRestarted": "{0} қайта іске қосу қажет", - "Shows": "Көрсетімдер", - "Songs": "Әуендер", - "StartupEmbyServerIsLoading": "Jellyfin Server жүктелуде. Әрекетті көп ұзамай қайталаңыз.", + "Albums": "Álbomdar", + "AppDeviceValues": "Qoldanba: {0}, Qurylǵy: {1}", + "Application": "Qoldanba", + "Artists": "Oryndaýshylar", + "AuthenticationSucceededWithUserName": "{0} túpnusqalyǵyn rastalýy sátti", + "Books": "Kitaptar", + "CameraImageUploadedFrom": "Jańa sýret {0} kamerasynan júktep alyndy", + "Channels": "Arnalar", + "ChapterNameValue": "{0}-sahna", + "Collections": "Jıyntyqtar", + "DeviceOfflineWithName": "{0} ajyratylǵan", + "DeviceOnlineWithName": "{0} qosylǵan", + "FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz", + "Favorites": "Tańdaýlylar", + "Folders": "Qaltalar", + "Genres": "Janrlar", + "HeaderAlbumArtists": "Álbom oryndaýshylary", + "HeaderCameraUploads": "Kameradan júktelgender", + "HeaderContinueWatching": "Qaraýdy jalǵastyrý", + "HeaderFavoriteAlbums": "Tańdaýly álbomdar", + "HeaderFavoriteArtists": "Tańdaýly oryndaýshylar", + "HeaderFavoriteEpisodes": "Tańdaýly bólimder", + "HeaderFavoriteShows": "Tańdaýly kórsetimder", + "HeaderFavoriteSongs": "Tańdaýly áýender", + "HeaderLiveTV": "Efır", + "HeaderNextUp": "Kezekti", + "HeaderRecordingGroups": "Jazba toptary", + "HomeVideos": "Úılik beıneler", + "Inherit": "Muraǵa ıelený", + "ItemAddedWithName": "{0} tasyǵyshhanaǵa ústelindi", + "ItemRemovedWithName": "{0} tasyǵyshhanadan alastaldy", + "LabelIpAddressValue": "IP-mekenjaıy: {0}", + "LabelRunningTimeValue": "Oınatý ýaqyty: {0}", + "Latest": "Eń keıingi", + "MessageApplicationUpdated": "Jellyfin Serveri jańartyldy", + "MessageApplicationUpdatedTo": "Jellyfin Serveri {0} deńgeıge jańartyldy", + "MessageNamedServerConfigurationUpdatedWithValue": "Server teńsheliminiń {0} bólimi jańartyldy", + "MessageServerConfigurationUpdated": "Server teńshelimi jańartyldy", + "MixedContent": "Aralas mazmun", + "Movies": "Fılmder", + "Music": "Mýzyka", + "MusicVideos": "Mýzykalyq beıneler", + "NameInstallFailed": "{0} ornatylýy sátsiz", + "NameSeasonNumber": "{0}-maýsym", + "NameSeasonUnknown": "Belgisiz maýsym", + "NewVersionIsAvailable": "Jańa Jellyfin Server nusqasy júktep alýǵa qoljetimdi.", + "NotificationOptionApplicationUpdateAvailable": "Qoldanba jańartýy qoljetimdi", + "NotificationOptionApplicationUpdateInstalled": "Qoldanba jańartýy ornatyldy", + "NotificationOptionAudioPlayback": "Dybys oınatýy bastaldy", + "NotificationOptionAudioPlaybackStopped": "Dybys oınatýy toqtatyldy", + "NotificationOptionCameraImageUploaded": "Kameradan fotosýret keri qotarylǵan", + "NotificationOptionInstallationFailed": "Ornatý sátsizdigi", + "NotificationOptionNewLibraryContent": "Jańa mazmun ústelgen", + "NotificationOptionPluginError": "Plagın sátsizdigi", + "NotificationOptionPluginInstalled": "Plagın ornatyldy", + "NotificationOptionPluginUninstalled": "Plagın ornatýy boldyrylmady", + "NotificationOptionPluginUpdateInstalled": "Plagın jańartýy ornatyldy", + "NotificationOptionServerRestartRequired": "Serverdi qaıta iske qosý qajet", + "NotificationOptionTaskFailed": "Josparlaǵan tapsyrma sátsizdigi", + "NotificationOptionUserLockedOut": "Paıdalanýshy qursaýly", + "NotificationOptionVideoPlayback": "Beıne oınatýy bastaldy", + "NotificationOptionVideoPlaybackStopped": "Beıne oınatýy toqtatyldy", + "Photos": "Fotosýretter", + "Playlists": "Oınatý tizimderi", + "Plugin": "Plagın", + "PluginInstalledWithName": "{0} ornatyldy", + "PluginUninstalledWithName": "{0} joıyldy", + "PluginUpdatedWithName": "{0} jańartyldy", + "ProviderValue": "Jetkizýshi: {0}", + "ScheduledTaskFailedWithName": "{0} sátsiz", + "ScheduledTaskStartedWithName": "{0} iske qosyldy", + "ServerNameNeedsToBeRestarted": "{0} qaıta iske qosý qajet", + "Shows": "Kórsetimder", + "Songs": "Áýender", + "StartupEmbyServerIsLoading": "Jellyfin Server júktelýde. Áreketti kóp uzamaı qaıtalańyz.", "SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "{0} үшін субтитрлер жүктеліп алынды", - "Sync": "Үндестіру", - "System": "Жүйе", - "TvShows": "ТД-көрсетімдер", - "User": "Пайдаланушы", - "UserCreatedWithName": "Пайдаланушы {0} жасалған", - "UserDeletedWithName": "Пайдаланушы {0} жойылған", - "UserDownloadingItemWithValues": "{0} мынаны жүктеп алуда: {1}", - "UserLockedOutWithName": "Пайдаланушы {0} құрсаулы", - "UserOfflineFromDevice": "{0} - {1} тарапынан ажыратылған", - "UserOnlineFromDevice": "{0} - {1} арқылы қосылған", - "UserPasswordChangedWithName": "Пайдаланушы {0} үшін құпия сөз өзгертілді", - "UserPolicyUpdatedWithName": "Пайдаланушы {0} үшін саясаттары жаңартылды", - "UserStartedPlayingItemWithValues": "{0} - {1} ойнатуын {2} бастады", - "UserStoppedPlayingItemWithValues": "{0} - {1} ойнатуын {2} тоқтатты", - "ValueHasBeenAddedToLibrary": "{0} (тасығышханаға үстелінді)", - "ValueSpecialEpisodeName": "Арнайы - {0}", - "VersionNumber": "Нұсқасы: {0}" + "SubtitleDownloadFailureFromForItem": "{1} úshin sýbtıtrlerdi {0} kózinen júktep alý sátsiz", + "SubtitlesDownloadedForItem": "{0} úshin sýbtıtrler júktelip alyndy", + "Sync": "Úndestirý", + "System": "Júıe", + "TvShows": "TD-kórsetimder", + "User": "Paıdalanýshy", + "UserCreatedWithName": "Paıdalanýshy {0} jasalǵan", + "UserDeletedWithName": "Paıdalanýshy {0} joıylǵan", + "UserDownloadingItemWithValues": "{0} mynany júktep alýda: {1}", + "UserLockedOutWithName": "Paıdalanýshy {0} qursaýly", + "UserOfflineFromDevice": "{0} - {1} tarapynan ajyratylǵan", + "UserOnlineFromDevice": "{0} - {1} arqyly qosylǵan", + "UserPasswordChangedWithName": "Paıdalanýshy {0} úshin paról ózgertildi", + "UserPolicyUpdatedWithName": "Paıdalanýshy {0} úshin saıasattary jańartyldy", + "UserStartedPlayingItemWithValues": "{0} - {1} oınatýyn {2} bastady", + "UserStoppedPlayingItemWithValues": "{0} - {1} oınatýyn {2} toqtatty", + "ValueHasBeenAddedToLibrary": "{0} (tasyǵyshhanaǵa ústelindi)", + "ValueSpecialEpisodeName": "Arnaıy - {0}", + "VersionNumber": "Nusqasy {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index c01bb0c50..c10fbe58f 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -1,10 +1,10 @@ { - "Albums": "Albums", + "Albums": "Album-album", "AppDeviceValues": "App: {0}, Device: {1}", "Application": "Application", - "Artists": "Artists", + "Artists": "Artis-artis", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", + "Books": "Buku-buku", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", "Channels": "Channels", "ChapterNameValue": "Chapter {0}", diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 589753c44..24af1839f 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -5,28 +5,28 @@ "Artists": "Artiesten", "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", "Books": "Boeken", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd via {0}", "Channels": "Kanalen", "ChapterNameValue": "Hoofdstuk {0}", "Collections": "Collecties", - "DeviceOfflineWithName": "{0} is losgekoppeld", + "DeviceOfflineWithName": "{0} heeft de verbinding verbroken", "DeviceOnlineWithName": "{0} is verbonden", "FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}", "Favorites": "Favorieten", "Folders": "Mappen", "Genres": "Genres", - "HeaderAlbumArtists": "Album artiesten", - "HeaderCameraUploads": "Camera uploads", + "HeaderAlbumArtists": "Albumartiesten", + "HeaderCameraUploads": "Camera-uploads", "HeaderContinueWatching": "Kijken hervatten", "HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteArtists": "Favoriete artiesten", "HeaderFavoriteEpisodes": "Favoriete afleveringen", "HeaderFavoriteShows": "Favoriete shows", - "HeaderFavoriteSongs": "Favoriete titels", + "HeaderFavoriteSongs": "Favoriete nummers", "HeaderLiveTV": "Live TV", "HeaderNextUp": "Volgende", "HeaderRecordingGroups": "Opnamegroepen", - "HomeVideos": "Thuis video's", + "HomeVideos": "Start video's", "Inherit": "Overerven", "ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek", "ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek", @@ -34,22 +34,22 @@ "LabelRunningTimeValue": "Looptijd: {0}", "Latest": "Nieuwste", "MessageApplicationUpdated": "Jellyfin Server is bijgewerkt", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server is bijgewerkt naar {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Sectie {0} van de server configuratie is bijgewerkt", "MessageServerConfigurationUpdated": "Server configuratie is bijgewerkt", "MixedContent": "Gemengde inhoud", "Movies": "Films", "Music": "Muziek", "MusicVideos": "Muziekvideo's", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "{0} installatie mislukt", "NameSeasonNumber": "Seizoen {0}", "NameSeasonUnknown": "Seizoen onbekend", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "Een nieuwe versie van Jellyfin Server is beschikbaar om te downloaden.", "NotificationOptionApplicationUpdateAvailable": "Programma-update beschikbaar", "NotificationOptionApplicationUpdateInstalled": "Programma-update geïnstalleerd", - "NotificationOptionAudioPlayback": "Geluid gestart", - "NotificationOptionAudioPlaybackStopped": "Geluid gestopt", - "NotificationOptionCameraImageUploaded": "Camera afbeelding geüpload", + "NotificationOptionAudioPlayback": "Muziek gestart", + "NotificationOptionAudioPlaybackStopped": "Muziek gestopt", + "NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload", "NotificationOptionInstallationFailed": "Installatie mislukt", "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd", "NotificationOptionPluginError": "Plug-in fout", @@ -70,12 +70,12 @@ "ProviderValue": "Aanbieder: {0}", "ScheduledTaskFailedWithName": "{0} is mislukt", "ScheduledTaskStartedWithName": "{0} is gestart", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} moet herstart worden", "Shows": "Series", - "Songs": "Titels", + "Songs": "Nummers", "StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden, probeer het later opnieuw.", "SubtitleDownloadFailureForItem": "Downloaden van ondertiteling voor {0} is mislukt", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Ondertitels konden niet gedownload worden van {0} voor {1}", "SubtitlesDownloadedForItem": "Ondertiteling voor {0} is gedownload", "Sync": "Synchronisatie", "System": "Systeem", @@ -89,9 +89,9 @@ "UserOnlineFromDevice": "{0} heeft verbinding met {1}", "UserPasswordChangedWithName": "Wachtwoord voor {0} is gewijzigd", "UserPolicyUpdatedWithName": "Gebruikersbeleid gewijzigd voor {0}", - "UserStartedPlayingItemWithValues": "{0} heeft afspelen van {1} gestart", - "UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "UserStartedPlayingItemWithValues": "{0} heeft afspelen van {1} gestart op {2}", + "UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}", + "ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek", "ValueSpecialEpisodeName": "Speciaal - {0}", "VersionNumber": "Versie {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index d799fa50b..c0465def8 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -5,7 +5,7 @@ "Artists": "Исполнители", "AuthenticationSucceededWithUserName": "{0} - авторизация успешна", "Books": "Литература", - "CameraImageUploadedFrom": "Новое фото было выложено с {0}", + "CameraImageUploadedFrom": "Новое фото было выложено с камеры {0}", "Channels": "Каналы", "ChapterNameValue": "Сцена {0}", "Collections": "Коллекции", @@ -31,20 +31,20 @@ "ItemAddedWithName": "{0} - добавлено в медиатеку", "ItemRemovedWithName": "{0} - изъято из медиатеки", "LabelIpAddressValue": "IP-адрес: {0}", - "LabelRunningTimeValue": "Время выполнения: {0}", + "LabelRunningTimeValue": "Длительность: {0}", "Latest": "Новейшее", "MessageApplicationUpdated": "Jellyfin Server был обновлён", "MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена", "MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена", - "MixedContent": "Смешанное содержание", + "MixedContent": "Смешанное содержимое", "Movies": "Кино", "Music": "Музыка", "MusicVideos": "Муз. видео", "NameInstallFailed": "Установка {0} неудачна", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Сезон неопознан", - "NewVersionIsAvailable": "Имеется новая версия Jellyfin Server", + "NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.", "NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения", "NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено", "NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но", @@ -75,7 +75,7 @@ "Songs": "Композиции", "StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.", "SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}", "SubtitlesDownloadedForItem": "Субтитры к {0} загружены", "Sync": "Синхро", "System": "Система", diff --git a/Emby.Server.Implementations/Localization/Ratings/kz.csv b/Emby.Server.Implementations/Localization/Ratings/kz.csv index 4441c5650..d546bff53 100644 --- a/Emby.Server.Implementations/Localization/Ratings/kz.csv +++ b/Emby.Server.Implementations/Localization/Ratings/kz.csv @@ -1,6 +1,7 @@ -KZ-К,1 -KZ-БА,6 -KZ-Б14,7 -KZ-Е16,8 -KZ-Е18,10 -KZ-НА,15 +KZ-6-,0 +KZ-6+,6 +KZ-12+,12 +KZ-14+,14 +KZ-16+,16 +KZ-18+,18 +KZ-21+,21 diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 81fdb96d2..2f07ff15a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <summary> /// Class ChapterImagesTask /// </summary> - class ChapterImagesTask : IScheduledTask + public class ChapterImagesTask : IScheduledTask { /// <summary> /// The _logger diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 98685cebe..ec9466c4a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var dueTime = triggerDate - now; - logger.LogInformation("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:g}, which is {DueTime:g} from now.", taskName, triggerDate, dueTime); Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index 44898d498..8ae7fd90c 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -42,6 +42,27 @@ namespace Emby.Server.Implementations.Serialization } /// <summary> + /// Serializes to stream. + /// </summary> + /// <param name="obj">The obj.</param> + /// <param name="stream">The stream.</param> + /// <exception cref="ArgumentNullException">obj</exception> + public void SerializeToStream<T>(T obj, Stream stream) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + ServiceStack.Text.JsonSerializer.SerializeToStream<T>(obj, stream); + } + + /// <summary> /// Serializes to file. /// </summary> /// <param name="obj">The obj.</param> diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 271188314..16507466f 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { - class AiredEpisodeOrderComparer : IBaseItemComparer + public class AiredEpisodeOrderComparer : IBaseItemComparer { /// <summary> /// Compares the specified x. diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 942e84704..46e0dd918 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { - class SeriesSortNameComparer : IBaseItemComparer + public class SeriesSortNameComparer : IBaseItemComparer { /// <summary> /// Compares the specified x. diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index dc7f57f27..301802b8a 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -116,6 +116,7 @@ namespace Emby.Server.Implementations.Updates private readonly IApplicationHost _applicationHost; private readonly ICryptoProvider _cryptographyProvider; + private readonly IZipClient _zipClient; // netframework or netcore private readonly string _packageRuntime; @@ -129,6 +130,7 @@ namespace Emby.Server.Implementations.Updates IServerConfigurationManager config, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, + IZipClient zipClient, string packageRuntime) { if (loggerFactory == null) @@ -146,6 +148,7 @@ namespace Emby.Server.Implementations.Updates _config = config; _fileSystem = fileSystem; _cryptographyProvider = cryptographyProvider; + _zipClient = zipClient; _packageRuntime = packageRuntime; _logger = loggerFactory.CreateLogger(nameof(InstallationManager)); } @@ -526,14 +529,18 @@ namespace Emby.Server.Implementations.Updates private async Task PerformPackageInstallation(IProgress<double> progress, string target, PackageVersionInfo package, CancellationToken cancellationToken) { - // Target based on if it is an archive or single assembly - // zip archives are assumed to contain directory structures relative to our ProgramDataPath var extension = Path.GetExtension(package.targetFilename); - var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".rar", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".7z", StringComparison.OrdinalIgnoreCase); + var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase); + + if (!isArchive) + { + _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename); + return; + } if (target == null) { - target = Path.Combine(isArchive ? _appPaths.TempUpdatePath : _appPaths.PluginsPath, package.targetFilename); + target = Path.Combine(_appPaths.PluginsPath, Path.GetFileNameWithoutExtension(package.targetFilename)); } // Download to temporary file so that, if interrupted, it won't destroy the existing installation @@ -547,36 +554,19 @@ namespace Emby.Server.Implementations.Updates cancellationToken.ThrowIfCancellationRequested(); - // Validate with a checksum - var packageChecksum = string.IsNullOrWhiteSpace(package.checksum) ? Guid.Empty : new Guid(package.checksum); - if (!packageChecksum.Equals(Guid.Empty)) // support for legacy uploads for now - { - using (var stream = File.OpenRead(tempFile)) - { - var check = Guid.Parse(BitConverter.ToString(_cryptographyProvider.ComputeMD5(stream)).Replace("-", string.Empty)); - if (check != packageChecksum) - { - throw new Exception(string.Format("Download validation failed for {0}. Probably corrupted during transfer.", package.name)); - } - } - } - - cancellationToken.ThrowIfCancellationRequested(); + // TODO: Validate with a checksum, *properly* // Success - move it to the real target try { - Directory.CreateDirectory(Path.GetDirectoryName(target)); - File.Copy(tempFile, target, true); - //If it is an archive - write out a version file so we know what it is - if (isArchive) + using (var stream = File.OpenRead(tempFile)) { - File.WriteAllText(target + ".ver", package.versionStr); + _zipClient.ExtractAllFromZip(stream, target, true); } } catch (IOException ex) { - _logger.LogError(ex, "Error attempting to move file from {TempFile} to {TargetFile}", tempFile, target); + _logger.LogError(ex, "Error attempting to extract {TempFile} to {TargetFile}", tempFile, target); throw; } |
