Browse Source

added app theme classes and service

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
83619ef25a

+ 102 - 0
MediaBrowser.Api/AppThemeService.cs

@@ -0,0 +1,102 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Themes;
+using MediaBrowser.Model.Themes;
+using ServiceStack;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Api
+{
+    [Route("/Themes", "GET")]
+    [Api(Description = "Gets a list of available themes for an app")]
+    public class GetAppThemes : IReturn<List<AppThemeInfo>>
+    {
+        [ApiMember(Name = "ApplicationName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ApplicationName { get; set; }
+    }
+
+    [Route("/Themes/Info", "GET")]
+    [Api(Description = "Gets an app theme")]
+    public class GetAppTheme : IReturn<AppTheme>
+    {
+        [ApiMember(Name = "ApplicationName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ApplicationName { get; set; }
+
+        [ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Name { get; set; }
+    }
+
+    [Route("/Themes/Images", "GET")]
+    [Api(Description = "Gets an app theme")]
+    public class GetAppThemeImage
+    {
+        [ApiMember(Name = "ApplicationName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ApplicationName { get; set; }
+
+        [ApiMember(Name = "ThemeName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ThemeName { get; set; }
+
+        [ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Name { get; set; }
+
+        [ApiMember(Name = "Tag", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Tag { get; set; }
+    }
+
+    [Route("/Themes", "POST")]
+    [Api(Description = "Saves a theme")]
+    public class SaveTheme : AppTheme, IReturnVoid
+    {
+    }
+    
+    public class AppThemeService : BaseApiService
+    {
+        private readonly IAppThemeManager _themeManager;
+        private readonly IFileSystem _fileSystem;
+
+        public AppThemeService(IAppThemeManager themeManager, IFileSystem fileSystem)
+        {
+            _themeManager = themeManager;
+            _fileSystem = fileSystem;
+        }
+
+        public object Get(GetAppThemes request)
+        {
+            var result = _themeManager.GetThemes(request.ApplicationName).ToList();
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Get(GetAppTheme request)
+        {
+            var result = _themeManager.GetTheme(request.ApplicationName, request.Name);
+
+            return ToOptimizedResult(result);
+        }
+
+        public void Post(SaveTheme request)
+        {
+            _themeManager.SaveTheme(request);
+        }
+
+        public object Get(GetAppThemeImage request)
+        {
+            var info = _themeManager.GetImageImageInfo(request.ApplicationName, request.ThemeName, request.Name);
+
+            var cacheGuid = new Guid(info.CacheTag);
+
+            TimeSpan? cacheDuration = null;
+
+            if (!string.IsNullOrEmpty(request.Tag) && cacheGuid == new Guid(request.Tag))
+            {
+                cacheDuration = TimeSpan.FromDays(365);
+            }
+
+            var contentType = MimeTypes.GetMimeType(info.Path);
+
+            return ToCachedResult(cacheGuid, info.DateModified, cacheDuration, () => _fileSystem.GetFileStream(info.Path, FileMode.Open, FileAccess.Read, FileShare.Read), contentType);
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -67,6 +67,7 @@
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
     <Compile Include="AlbumsService.cs" />
+    <Compile Include="AppThemeService.cs" />
     <Compile Include="BaseApiService.cs" />
     <Compile Include="ConfigurationService.cs" />
     <Compile Include="DefaultTheme\DefaultThemeService.cs" />

+ 17 - 5
MediaBrowser.Common.Implementations/Security/MBRegistration.cs

@@ -14,11 +14,12 @@ namespace MediaBrowser.Common.Implementations.Security
     {
 
         private static MBLicenseFile _licenseFile;
-        private const string MBValidateUrl = Constants.Constants.MbAdminUrl+"service/registration/validate";
+        private const string MBValidateUrl = Constants.Constants.MbAdminUrl + "service/registration/validate";
 
         private static IApplicationPaths _appPaths;
         private static INetworkManager _networkManager;
         private static ILogger _logger;
+        private static IApplicationHost _applicationHost;
 
         private static MBLicenseFile LicenseFile
         {
@@ -37,24 +38,35 @@ namespace MediaBrowser.Common.Implementations.Security
             set { LicenseFile.LegacyKey = value; LicenseFile.Save(); }
         }
 
-        public static void Init(IApplicationPaths appPaths, INetworkManager networkManager, ILogManager logManager)
+        public static void Init(IApplicationPaths appPaths, INetworkManager networkManager, ILogManager logManager, IApplicationHost appHost)
         {
             // Ugly alert (static init)
 
             _appPaths = appPaths;
             _networkManager = networkManager;
             _logger = logManager.GetLogger("SecurityManager");
+            _applicationHost = appHost;
         }
 
         public static async Task<MBRegistrationRecord> GetRegistrationStatus(IHttpClient httpClient, IJsonSerializer jsonSerializer, string feature, string mb2Equivalent = null, string version = null)
         {
             //check the reg file first to alleviate strain on the MB admin server - must actually check in every 30 days tho
-            var reg = new RegRecord {registered = LicenseFile.LastChecked(feature) > DateTime.UtcNow.AddDays(-30)};
+            var reg = new RegRecord { registered = LicenseFile.LastChecked(feature) > DateTime.UtcNow.AddDays(-30) };
 
             if (!reg.registered)
             {
                 var mac = _networkManager.GetMacAddress();
-                var data = new Dictionary<string, string> { { "feature", feature }, { "key", SupporterKey }, { "mac", mac }, { "mb2equiv", mb2Equivalent }, { "legacykey", LegacyKey }, { "ver", version }, { "platform", Environment.OSVersion.VersionString } };
+                var data = new Dictionary<string, string>
+                {
+                    { "feature", feature }, 
+                    { "key", SupporterKey }, 
+                    { "mac", mac }, 
+                    { "mb2equiv", mb2Equivalent }, 
+                    { "legacykey", LegacyKey }, 
+                    { "ver", version }, 
+                    { "platform", Environment.OSVersion.VersionString }, 
+                    { "isservice", _applicationHost.IsRunningAsService.ToString().ToLower() }
+                };
 
                 try
                 {
@@ -79,7 +91,7 @@ namespace MediaBrowser.Common.Implementations.Security
                 }
             }
 
-            return new MBRegistrationRecord {IsRegistered = reg.registered, ExpirationDate = reg.expDate, RegChecked = true};
+            return new MBRegistrationRecord { IsRegistered = reg.registered, ExpirationDate = reg.expDate, RegChecked = true };
         }
     }
 

+ 1 - 1
MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs

@@ -74,7 +74,7 @@ namespace MediaBrowser.Common.Implementations.Security
             _appHost = appHost;
             _httpClient = httpClient;
             _jsonSerializer = jsonSerializer;
-            MBRegistration.Init(_applciationPaths, _networkManager, logManager);
+            MBRegistration.Init(_applciationPaths, _networkManager, logManager, _appHost);
         }
 
         /// <summary>

+ 1 - 0
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -256,6 +256,7 @@ namespace MediaBrowser.Controller.Entities.TV
             if (series != null)
             {
                 id.SeriesProviderIds = series.ProviderIds;
+                id.AnimeSeriesIndex = series.AnimeSeriesIndex;
             }
 
             id.IndexNumberEnd = IndexNumberEnd;

+ 1 - 0
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -257,6 +257,7 @@ namespace MediaBrowser.Controller.Entities.TV
             if (series != null)
             {
                 id.SeriesProviderIds = series.ProviderIds;
+                id.AnimeSeriesIndex = series.AnimeSeriesIndex;
             }
 
             return id;

+ 7 - 1
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities.TV
 
         public int SeasonCount { get; set; }
 
+        public int? AnimeSeriesIndex { get; set; }
+
         /// <summary>
         /// Gets or sets the preferred metadata country code.
         /// </summary>
@@ -224,7 +226,11 @@ namespace MediaBrowser.Controller.Entities.TV
 
         public SeriesInfo GetLookupInfo()
         {
-            return GetItemLookupInfo<SeriesInfo>();
+            var info = GetItemLookupInfo<SeriesInfo>();
+
+            info.AnimeSeriesIndex = AnimeSeriesIndex;
+
+            return info;
         }
 
         public override bool BeforeMetadataRefresh()

+ 2 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -239,6 +239,8 @@
     <Compile Include="Sorting\IUserBaseItemComparer.cs" />
     <Compile Include="Providers\BaseItemXmlParser.cs" />
     <Compile Include="Sorting\SortExtensions.cs" />
+    <Compile Include="Themes\IAppThemeManager.cs" />
+    <Compile Include="Themes\InternalThemeImage.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">

+ 3 - 1
MediaBrowser.Controller/Providers/ItemLookupInfo.cs

@@ -101,6 +101,7 @@ namespace MediaBrowser.Controller.Providers
         public Dictionary<string, string> SeriesProviderIds { get; set; }
 
         public int? IndexNumberEnd { get; set; }
+        public int? AnimeSeriesIndex { get; set; }
 
         public EpisodeInfo()
         {
@@ -117,7 +118,7 @@ namespace MediaBrowser.Controller.Providers
 
     public class SeriesInfo : ItemLookupInfo
     {
-
+        public int? AnimeSeriesIndex { get; set; }
     }
 
     public class PersonLookupInfo : ItemLookupInfo
@@ -153,6 +154,7 @@ namespace MediaBrowser.Controller.Providers
     public class SeasonInfo : ItemLookupInfo
     {
         public Dictionary<string, string> SeriesProviderIds { get; set; }
+        public int? AnimeSeriesIndex { get; set; }
 
         public SeasonInfo()
         {

+ 38 - 0
MediaBrowser.Controller/Themes/IAppThemeManager.cs

@@ -0,0 +1,38 @@
+using MediaBrowser.Model.Themes;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Themes
+{
+    public interface IAppThemeManager
+    {
+        /// <summary>
+        /// Gets the themes.
+        /// </summary>
+        /// <param name="applicationName">Name of the application.</param>
+        /// <returns>IEnumerable{AppThemeInfo}.</returns>
+        IEnumerable<AppThemeInfo> GetThemes(string applicationName);
+
+        /// <summary>
+        /// Gets the theme.
+        /// </summary>
+        /// <param name="applicationName">Name of the application.</param>
+        /// <param name="name">The name.</param>
+        /// <returns>AppTheme.</returns>
+        AppTheme GetTheme(string applicationName, string name);
+
+        /// <summary>
+        /// Saves the theme.
+        /// </summary>
+        /// <param name="theme">The theme.</param>
+        void SaveTheme(AppTheme theme);
+
+        /// <summary>
+        /// Gets the image image information.
+        /// </summary>
+        /// <param name="applicationName">Name of the application.</param>
+        /// <param name="themeName">Name of the theme.</param>
+        /// <param name="imageName">Name of the image.</param>
+        /// <returns>InternalThemeImage.</returns>
+        InternalThemeImage GetImageImageInfo(string applicationName, string themeName, string imageName);
+    }
+}

+ 31 - 0
MediaBrowser.Controller/Themes/InternalThemeImage.cs

@@ -0,0 +1,31 @@
+using System;
+
+namespace MediaBrowser.Controller.Themes
+{
+    public class InternalThemeImage
+    {
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the cache tag.
+        /// </summary>
+        /// <value>The cache tag.</value>
+        public string CacheTag { get; set; }
+
+        /// <summary>
+        /// Gets or sets the path.
+        /// </summary>
+        /// <value>The path.</value>
+        public string Path { get; set; }
+
+        /// <summary>
+        /// Gets or sets the date modified.
+        /// </summary>
+        /// <value>The date modified.</value>
+        public DateTime DateModified { get; set; }
+    }
+}

+ 6 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -452,6 +452,12 @@
     <Compile Include="..\MediaBrowser.Model\Tasks\TaskTriggerInfo.cs">
       <Link>Tasks\TaskTriggerInfo.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Themes\AppTheme.cs">
+      <Link>Themes\AppTheme.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Themes\ThemeImage.cs">
+      <Link>Themes\ThemeImage.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Updates\CheckForUpdateResult.cs">
       <Link>Updates\CheckForUpdateResult.cs</Link>
     </Compile>

+ 6 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -439,6 +439,12 @@
     <Compile Include="..\MediaBrowser.Model\Tasks\TaskTriggerInfo.cs">
       <Link>Tasks\TaskTriggerInfo.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Themes\AppTheme.cs">
+      <Link>Themes\AppTheme.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Themes\ThemeImage.cs">
+      <Link>Themes\ThemeImage.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Updates\CheckForUpdateResult.cs">
       <Link>Updates\CheckForUpdateResult.cs</Link>
     </Compile>

+ 2 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -165,6 +165,8 @@
     <Compile Include="Session\SessionInfoDto.cs" />
     <Compile Include="Session\SystemCommand.cs" />
     <Compile Include="Session\UserDataChangeInfo.cs" />
+    <Compile Include="Themes\AppTheme.cs" />
+    <Compile Include="Themes\ThemeImage.cs" />
     <Compile Include="Updates\CheckForUpdateResult.cs" />
     <Compile Include="Updates\PackageTargetSystem.cs" />
     <Compile Include="Updates\InstallationInfo.cs" />

+ 30 - 0
MediaBrowser.Model/Themes/AppTheme.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Themes
+{
+    public class AppTheme
+    {
+        public string ApplicationName { get; set; }
+
+        public string Name { get; set; }
+
+        public Dictionary<string, string> Options { get; set; }
+
+        public List<ThemeImage> Images { get; set; }
+
+        public AppTheme()
+        {
+            Options = new Dictionary<string, string>(StringComparer.Ordinal);
+
+            Images = new List<ThemeImage>();
+        }
+    }
+
+    public class AppThemeInfo
+    {
+        public string ApplicationName { get; set; }
+        
+        public string Name { get; set; }
+    }
+}

+ 18 - 0
MediaBrowser.Model/Themes/ThemeImage.cs

@@ -0,0 +1,18 @@
+
+namespace MediaBrowser.Model.Themes
+{
+    public class ThemeImage
+    {
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the cache tag.
+        /// </summary>
+        /// <value>The cache tag.</value>
+        public string CacheTag { get; set; }
+    }
+}

+ 1 - 0
MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs

@@ -25,6 +25,7 @@ namespace MediaBrowser.Server.Implementations.Configuration
             : base(applicationPaths, logManager, xmlSerializer)
         {
             UpdateItemsByNamePath();
+            UpdateTranscodingTempPath();
         }
 
         /// <summary>

+ 1 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -248,6 +248,7 @@
     <Compile Include="Persistence\SqliteUserRepository.cs" />
     <Compile Include="Sorting\TrailerCountComparer.cs" />
     <Compile Include="Sorting\VideoBitRateComparer.cs" />
+    <Compile Include="Themes\AppThemeManager.cs" />
     <Compile Include="Udp\UdpMessageReceivedEventArgs.cs" />
     <Compile Include="Udp\UdpServer.cs" />
     <Compile Include="WebSocket\AlchemyServer.cs" />

+ 163 - 0
MediaBrowser.Server.Implementations/Themes/AppThemeManager.cs

@@ -0,0 +1,163 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Themes;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Themes;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.Themes
+{
+    public class AppThemeManager : IAppThemeManager
+    {
+        private readonly IServerApplicationPaths _appPaths;
+        private readonly IFileSystem _fileSystem;
+        private readonly IJsonSerializer _json;
+        private readonly ILogger _logger;
+
+        private readonly string[] _supportedImageExtensions = { ".png", ".jpg", ".jpeg" };
+
+        public AppThemeManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer json, ILogger logger)
+        {
+            _appPaths = appPaths;
+            _fileSystem = fileSystem;
+            _json = json;
+            _logger = logger;
+        }
+
+        private string ThemePath
+        {
+            get
+            {
+                return Path.Combine(_appPaths.ItemsByNamePath, "appthemes");
+            }
+        }
+
+        private string GetThemesPath(string applicationName)
+        {
+            if (string.IsNullOrWhiteSpace(applicationName))
+            {
+                throw new ArgumentNullException("applicationName");
+            }
+
+            // Force everything lowercase for consistency and maximum compatibility with case-sensitive file systems
+            var name = _fileSystem.GetValidFilename(applicationName.ToLower());
+
+            return Path.Combine(ThemePath, name);
+        }
+
+        private string GetThemePath(string applicationName, string name)
+        {
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentNullException("name");
+            }
+            
+            // Force everything lowercase for consistency and maximum compatibility with case-sensitive file systems
+            name = _fileSystem.GetValidFilename(name.ToLower());
+
+            return Path.Combine(GetThemesPath(applicationName), name);
+        }
+
+        public IEnumerable<AppThemeInfo> GetThemes(string applicationName)
+        {
+            var path = GetThemesPath(applicationName);
+
+            try
+            {
+                return Directory
+                    .EnumerateFiles(path, "*", SearchOption.AllDirectories)
+                    .Where(i => string.Equals(Path.GetExtension(i), ".json", StringComparison.OrdinalIgnoreCase))
+                    .Select(i =>
+                    {
+                        try
+                        {
+                            return _json.DeserializeFromFile<AppThemeInfo>(i);
+                        }
+                        catch (Exception ex)
+                        {
+                            _logger.ErrorException("Error deserializing {0}", ex, i);
+                            return null;
+                        }
+
+                    }).Where(i => i != null);
+            }
+            catch (DirectoryNotFoundException)
+            {
+                return new List<AppThemeInfo>();
+            }
+        }
+
+        public AppTheme GetTheme(string applicationName, string name)
+        {
+            var themePath = GetThemePath(applicationName, name);
+            var file = Path.Combine(themePath, "theme.json");
+
+            var theme = _json.DeserializeFromFile<AppTheme>(file);
+
+            theme.Images = new DirectoryInfo(themePath)
+                .EnumerateFiles("*", SearchOption.TopDirectoryOnly)
+                .Where(i => _supportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase))
+                .Select(GetThemeImage)
+                .ToList();
+
+            return theme;
+        }
+
+        private ThemeImage GetThemeImage(FileInfo file)
+        {
+            var dateModified = _fileSystem.GetLastWriteTimeUtc(file);
+
+            var cacheTag = (file.FullName + dateModified.Ticks).GetMD5().ToString("N");
+
+            return new ThemeImage
+            {
+                CacheTag = cacheTag,
+                Name = file.Name
+            };
+        }
+
+        public void SaveTheme(AppTheme theme)
+        {
+            var themePath = GetThemePath(theme.ApplicationName, theme.Name);
+            var file = Path.Combine(themePath, "theme.json");
+
+            Directory.CreateDirectory(themePath);
+
+            // Clone it so that we don't serialize all the images - they're always dynamic
+            var clone = new AppTheme
+            {
+                ApplicationName = theme.ApplicationName,
+                Name = theme.Name,
+                Options = theme.Options,
+                Images = null
+            };
+
+            _json.SerializeToFile(clone, file);
+        }
+
+        public InternalThemeImage GetImageImageInfo(string applicationName, string themeName, string imageName)
+        {
+            var themePath = GetThemePath(applicationName, themeName);
+
+            var fullPath = Path.Combine(themePath, imageName);
+
+            var file = new DirectoryInfo(themePath).EnumerateFiles("*", SearchOption.TopDirectoryOnly)
+                .First(i => string.Equals(i.FullName, fullPath, StringComparison.OrdinalIgnoreCase));
+
+            var themeImage = GetThemeImage(file);
+
+            return new InternalThemeImage
+            {
+                CacheTag = themeImage.CacheTag,
+                Name = themeImage.Name,
+                Path = file.FullName,
+                DateModified = _fileSystem.GetLastWriteTimeUtc(file)
+            };
+        }
+    }
+}

+ 7 - 2
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -27,6 +27,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Controller.Themes;
 using MediaBrowser.Dlna.PlayTo;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
@@ -49,6 +50,7 @@ using MediaBrowser.Server.Implementations.MediaEncoder;
 using MediaBrowser.Server.Implementations.Persistence;
 using MediaBrowser.Server.Implementations.ServerManager;
 using MediaBrowser.Server.Implementations.Session;
+using MediaBrowser.Server.Implementations.Themes;
 using MediaBrowser.Server.Implementations.WebSocket;
 using MediaBrowser.ServerApplication.EntryPoints;
 using MediaBrowser.ServerApplication.FFMpeg;
@@ -163,7 +165,7 @@ namespace MediaBrowser.ServerApplication
         private ILocalizationManager LocalizationManager { get; set; }
 
         private IEncodingManager EncodingManager { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the user data repository.
         /// </summary>
@@ -438,6 +440,9 @@ namespace MediaBrowser.ServerApplication
                 MediaEncoder);
             RegisterSingleInstance(EncodingManager);
 
+            var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
+            RegisterSingleInstance<IAppThemeManager>(appThemeManager);
+
             LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager);
             RegisterSingleInstance(LiveTvManager);
 
@@ -747,7 +752,7 @@ namespace MediaBrowser.ServerApplication
 
             // Dlna implementations
             list.Add(typeof(PlayToServerEntryPoint).Assembly);
-            
+
             list.AddRange(Assemblies.GetAssembliesWithParts());
 
             // Include composable parts in the running assembly