Procházet zdrojové kódy

fixes #762 - Marking unwatched doesn't update display

Luke Pulverenti před 11 roky
rodič
revize
7fa9b14f56
55 změnil soubory, kde provedl 676 přidání a 408 odebrání
  1. 28 0
      MediaBrowser.Api/BrandingService.cs
  2. 7 0
      MediaBrowser.Api/ConfigurationService.cs
  3. 7 0
      MediaBrowser.Api/Images/ImageService.cs
  4. 14 8
      MediaBrowser.Api/ItemRefreshService.cs
  5. 2 0
      MediaBrowser.Api/Library/LibraryService.cs
  6. 2 2
      MediaBrowser.Api/MediaBrowser.Api.csproj
  7. 2 0
      MediaBrowser.Api/NotificationsService.cs
  8. 11 1
      MediaBrowser.Api/SessionsService.cs
  9. 44 39
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  10. 67 3
      MediaBrowser.Api/SystemService.cs
  11. 3 7
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  12. 4 0
      MediaBrowser.Api/UserService.cs
  13. 0 149
      MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs
  14. 4 0
      MediaBrowser.Common/Net/MimeTypes.cs
  15. 0 7
      MediaBrowser.Controller/Dto/IDtoService.cs
  16. 15 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  17. 75 5
      MediaBrowser.Controller/Entities/Folder.cs
  18. 17 1
      MediaBrowser.Controller/Entities/IHasUserData.cs
  19. 1 1
      MediaBrowser.Controller/Entities/TV/Episode.cs
  20. 6 0
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  21. 1 1
      MediaBrowser.Controller/Entities/UserView.cs
  22. 9 0
      MediaBrowser.Controller/Library/IUserDataManager.cs
  23. 1 3
      MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
  24. 25 14
      MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
  25. 7 1
      MediaBrowser.Controller/Session/ISessionManager.cs
  26. 6 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  27. 6 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  28. 12 0
      MediaBrowser.Model/Branding/BrandingOptions.cs
  29. 0 3
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  30. 3 0
      MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
  31. 14 2
      MediaBrowser.Model/Dto/UserItemDataDto.cs
  32. 2 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  33. 1 2
      MediaBrowser.Model/Notifications/NotificationRequest.cs
  34. 1 31
      MediaBrowser.Model/Session/SessionInfoDto.cs
  35. 31 0
      MediaBrowser.Model/System/LogFile.cs
  36. 22 6
      MediaBrowser.Providers/Manager/ImageSaver.cs
  37. 2 2
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  38. 21 0
      MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs
  39. 31 21
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  40. 31 14
      MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
  41. 16 10
      MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
  42. 8 1
      MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
  43. 37 0
      MediaBrowser.Server.Implementations/Library/UserDataManager.cs
  44. 4 7
      MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
  45. 2 4
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  46. 13 4
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  47. 1 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  48. 3 2
      MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs
  49. 21 6
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  50. 28 5
      MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs
  51. 1 1
      MediaBrowser.ServerApplication/ApplicationHost.cs
  52. 0 12
      MediaBrowser.ServerApplication/Native/BrowserLauncher.cs
  53. 0 15
      MediaBrowser.ServerApplication/ServerNotifyIcon.cs
  54. 1 2
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  55. 6 16
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 28 - 0
MediaBrowser.Api/BrandingService.cs

@@ -0,0 +1,28 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Branding;
+using ServiceStack;
+
+namespace MediaBrowser.Api
+{
+    [Route("/Branding/Configuration", "GET", Summary = "Gets branding configuration")]
+    public class GetBrandingOptions : IReturn<BrandingOptions>
+    {
+    }
+    
+    public class BrandingService : BaseApiService
+    {
+        private readonly IConfigurationManager _config;
+
+        public BrandingService(IConfigurationManager config)
+        {
+            _config = config;
+        }
+
+        public object Get(GetBrandingOptions request)
+        {
+            var result = _config.GetConfiguration<BrandingOptions>("branding");
+
+            return ToOptimizedResult(result);
+        }
+    }
+}

+ 7 - 0
MediaBrowser.Api/ConfigurationService.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Serialization;
@@ -27,6 +28,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")]
+    [Authenticated]
     public class GetNamedConfiguration
     {
         [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -37,11 +39,13 @@ namespace MediaBrowser.Api
     /// Class UpdateConfiguration
     /// </summary>
     [Route("/System/Configuration", "POST", Summary = "Updates application configuration")]
+    [Authenticated]
     public class UpdateConfiguration : ServerConfiguration, IReturnVoid
     {
     }
 
     [Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")]
+    [Authenticated]
     public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream
     {
         [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -51,18 +55,21 @@ namespace MediaBrowser.Api
     }
     
     [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
+    [Authenticated]
     public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
     {
 
     }
 
     [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")]
+    [Authenticated]
     public class GetMetadataPlugins : IReturn<List<MetadataPluginSummary>>
     {
 
     }
 
     [Route("/System/Configuration/VideoImageExtraction", "POST", Summary = "Updates image extraction for all types")]
+    [Authenticated]
     public class UpdateVideoImageExtraction : IReturnVoid
     {
         public bool Enabled { get; set; }

+ 7 - 0
MediaBrowser.Api/Images/ImageService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Drawing;
@@ -26,6 +27,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     [Route("/Items/{Id}/Images", "GET")]
     [Api(Description = "Gets information about an item's images")]
+    [Authenticated]
     public class GetItemImageInfos : IReturn<List<ImageInfo>>
     {
         /// <summary>
@@ -56,6 +58,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     [Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST")]
     [Api(Description = "Updates the index for an item image")]
+    [Authenticated]
     public class UpdateItemImageIndex : IReturnVoid
     {
         /// <summary>
@@ -137,6 +140,7 @@ namespace MediaBrowser.Api.Images
     [Route("/Items/{Id}/Images/{Type}", "DELETE")]
     [Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")]
     [Api(Description = "Deletes an item image")]
+    [Authenticated]
     public class DeleteItemImage : DeleteImageRequest, IReturnVoid
     {
         /// <summary>
@@ -153,6 +157,7 @@ namespace MediaBrowser.Api.Images
     [Route("/Users/{Id}/Images/{Type}", "DELETE")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")]
     [Api(Description = "Deletes a user image")]
+    [Authenticated]
     public class DeleteUserImage : DeleteImageRequest, IReturnVoid
     {
         /// <summary>
@@ -169,6 +174,7 @@ namespace MediaBrowser.Api.Images
     [Route("/Users/{Id}/Images/{Type}", "POST")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")]
     [Api(Description = "Posts a user image")]
+    [Authenticated]
     public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
     {
         /// <summary>
@@ -191,6 +197,7 @@ namespace MediaBrowser.Api.Images
     [Route("/Items/{Id}/Images/{Type}", "POST")]
     [Route("/Items/{Id}/Images/{Type}/{Index}", "POST")]
     [Api(Description = "Posts an item image")]
+    [Authenticated]
     public class PostItemImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
     {
         /// <summary>

+ 14 - 8
MediaBrowser.Api/ItemRefreshService.cs

@@ -13,10 +13,16 @@ namespace MediaBrowser.Api
 {
     public class BaseRefreshRequest : IReturnVoid
     {
-        [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
-        public bool Forced { get; set; }
+        [ApiMember(Name = "MetadataRefreshMode", Description = "Specifies the metadata refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
+        public MetadataRefreshMode MetadataRefreshMode { get; set; }
 
-        [ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced during the refresh.", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
+        [ApiMember(Name = "ImageRefreshMode", Description = "Specifies the image refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
+        public ImageRefreshMode ImageRefreshMode { get; set; }
+
+        [ApiMember(Name = "ReplaceAllMetadata", Description = "Determines if metadata should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
+        public bool ReplaceAllMetadata { get; set; }
+
+        [ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
         public bool ReplaceAllImages { get; set; }
     }
 
@@ -93,7 +99,7 @@ namespace MediaBrowser.Api
         private async Task RefreshItem(RefreshItem request, BaseItem item)
         {
             var options = GetRefreshOptions(request);
-            
+
             try
             {
                 await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
@@ -148,10 +154,10 @@ namespace MediaBrowser.Api
         {
             return new MetadataRefreshOptions
             {
-                MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
-                ImageRefreshMode = ImageRefreshMode.FullRefresh,
-                ReplaceAllMetadata = request.Forced,
-                ReplaceAllImages = request.ReplaceAllImages
+                MetadataRefreshMode = request.MetadataRefreshMode,
+                ImageRefreshMode = request.ImageRefreshMode,
+                ReplaceAllImages = request.ReplaceAllImages,
+                ReplaceAllMetadata = request.ReplaceAllMetadata
             };
         }
     }

+ 2 - 0
MediaBrowser.Api/Library/LibraryService.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Dto;
@@ -226,6 +227,7 @@ namespace MediaBrowser.Api.Library
     /// <summary>
     /// Class LibraryService
     /// </summary>
+    [Authenticated]
     public class LibraryService : BaseApiService
     {
         /// <summary>

+ 2 - 2
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -65,11 +65,12 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
+    <Compile Include="BrandingService.cs" />
     <Compile Include="ChannelService.cs" />
     <Compile Include="Dlna\DlnaServerService.cs" />
     <Compile Include="Dlna\DlnaService.cs" />
     <Compile Include="Library\ChapterService.cs" />
-    <Compile Include="Library\SubtitleService.cs" />
+    <Compile Include="Subtitles\SubtitleService.cs" />
     <Compile Include="Movies\CollectionService.cs" />
     <Compile Include="Music\AlbumsService.cs" />
     <Compile Include="AppThemeService.cs" />
@@ -139,7 +140,6 @@
     <Compile Include="UserService.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="VideosService.cs" />
-    <Compile Include="WebSocket\LogFileWebSocketListener.cs" />
     <Compile Include="WebSocket\SessionInfoWebSocketListener.cs" />
     <Compile Include="WebSocket\SystemInfoWebSocketListener.cs" />
   </ItemGroup>

+ 2 - 0
MediaBrowser.Api/NotificationsService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Notifications;
 using MediaBrowser.Model.Notifications;
 using ServiceStack;
@@ -82,6 +83,7 @@ namespace MediaBrowser.Api
         public string Ids { get; set; }
     }
 
+    [Authenticated]
     public class NotificationsService : BaseApiService
     {
         private readonly INotificationsRepository _notificationsRepo;

+ 11 - 1
MediaBrowser.Api/SessionsService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Session;
 using ServiceStack;
@@ -14,6 +15,7 @@ namespace MediaBrowser.Api
     /// Class GetSessions
     /// </summary>
     [Route("/Sessions", "GET", Summary = "Gets a list of sessions")]
+    [Authenticated]
     public class GetSessions : IReturn<List<SessionInfoDto>>
     {
         [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -27,6 +29,7 @@ namespace MediaBrowser.Api
     /// Class DisplayContent
     /// </summary>
     [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")]
+    [Authenticated]
     public class DisplayContent : IReturnVoid
     {
         /// <summary>
@@ -59,6 +62,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")]
+    [Authenticated]
     public class Play : IReturnVoid
     {
         /// <summary>
@@ -91,6 +95,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")]
+    [Authenticated]
     public class SendPlaystateCommand : IReturnVoid
     {
         /// <summary>
@@ -115,6 +120,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")]
+    [Authenticated]
     public class SendSystemCommand : IReturnVoid
     {
         /// <summary>
@@ -133,6 +139,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")]
+    [Authenticated]
     public class SendGeneralCommand : IReturnVoid
     {
         /// <summary>
@@ -151,6 +158,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")]
+    [Authenticated]
     public class SendFullGeneralCommand : GeneralCommand, IReturnVoid
     {
         /// <summary>
@@ -162,6 +170,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")]
+    [Authenticated]
     public class SendMessageCommand : IReturnVoid
     {
         /// <summary>
@@ -182,6 +191,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")]
+    [Authenticated]
     public class AddUserToSession : IReturnVoid
     {
         [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
@@ -192,6 +202,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")]
+    [Authenticated]
     public class RemoveUserFromSession : IReturnVoid
     {
         [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
@@ -202,7 +213,6 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")]
-    [Route("/Sessions/{Id}/Capabilities", "POST", Summary = "Updates capabilities for a device")]
     public class PostCapabilities : IReturnVoid
     {
         /// <summary>

+ 44 - 39
MediaBrowser.Api/Library/SubtitleService.cs → MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Entities;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Net;
@@ -9,37 +11,13 @@ using MediaBrowser.Model.Providers;
 using ServiceStack;
 using System;
 using System.Collections.Generic;
-using System.IO;
-using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace MediaBrowser.Api.Library
+namespace MediaBrowser.Api.Subtitles
 {
-    [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")]
-    public class GetSubtitle
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string Id { get; set; }
-
-        [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string MediaSourceId { get; set; }
-
-        [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
-        public int Index { get; set; }
-
-        [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string Format { get; set; }
-
-        [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public long StartPositionTicks { get; set; }
-    }
-
     [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
+    [Authenticated]
     public class DeleteSubtitle
     {
         /// <summary>
@@ -54,6 +32,7 @@ namespace MediaBrowser.Api.Library
     }
 
     [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
+    [Authenticated]
     public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -64,6 +43,7 @@ namespace MediaBrowser.Api.Library
     }
 
     [Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")]
+    [Authenticated]
     public class GetSubtitleProviders : IReturn<List<SubtitleProviderInfo>>
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -71,6 +51,7 @@ namespace MediaBrowser.Api.Library
     }
 
     [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")]
+    [Authenticated]
     public class DownloadRemoteSubtitles : IReturnVoid
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
@@ -81,13 +62,36 @@ namespace MediaBrowser.Api.Library
     }
 
     [Route("/Providers/Subtitles/Subtitles/{Id}", "GET")]
+    [Authenticated]
     public class GetRemoteSubtitles : IReturnVoid
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Id { get; set; }
     }
 
-    [Authenticated]
+    [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")]
+    public class GetSubtitle
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+
+        [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string MediaSourceId { get; set; }
+
+        [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
+        public int Index { get; set; }
+
+        [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Format { get; set; }
+
+        [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public long StartPositionTicks { get; set; }
+    }
+
     public class SubtitleService : BaseApiService
     {
         private readonly ILibraryManager _libraryManager;
@@ -101,14 +105,6 @@ namespace MediaBrowser.Api.Library
             _subtitleEncoder = subtitleEncoder;
         }
 
-        public object Get(SearchRemoteSubtitles request)
-        {
-            var video = (Video)_libraryManager.GetItemById(request.Id);
-
-            var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
-
-            return ToOptimizedResult(response);
-        }
         public object Get(GetSubtitle request)
         {
             if (string.IsNullOrEmpty(request.Format))
@@ -131,14 +127,23 @@ namespace MediaBrowser.Api.Library
 
         private async Task<Stream> GetSubtitles(GetSubtitle request)
         {
-            return await _subtitleEncoder.GetSubtitles(request.Id, 
-                request.MediaSourceId, 
-                request.Index, 
+            return await _subtitleEncoder.GetSubtitles(request.Id,
+                request.MediaSourceId,
+                request.Index,
                 request.Format,
                 request.StartPositionTicks,
                 CancellationToken.None).ConfigureAwait(false);
         }
 
+        public object Get(SearchRemoteSubtitles request)
+        {
+            var video = (Video)_libraryManager.GetItemById(request.Id);
+
+            var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
+
+            return ToOptimizedResult(response);
+        }
+
         public void Delete(DeleteSubtitle request)
         {
             var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index);

+ 67 - 3
MediaBrowser.Api/SystemService.cs

@@ -1,6 +1,12 @@
-using MediaBrowser.Controller;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.System;
 using ServiceStack;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Api
@@ -18,15 +24,30 @@ namespace MediaBrowser.Api
     /// Class RestartApplication
     /// </summary>
     [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")]
+    [Authenticated]
     public class RestartApplication
     {
     }
 
     [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")]
+    [Authenticated]
     public class ShutdownApplication
     {
     }
 
+    [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")]
+    [Authenticated]
+    public class GetServerLogs : IReturn<List<LogFile>>
+    {
+    }
+
+    [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")]
+    public class GetLogFile
+    {
+        [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Name { get; set; }
+    }
+
     /// <summary>
     /// Class SystemInfoService
     /// </summary>
@@ -36,16 +57,59 @@ namespace MediaBrowser.Api
         /// The _app host
         /// </summary>
         private readonly IServerApplicationHost _appHost;
-
+        private readonly IApplicationPaths _appPaths;
+        private readonly IFileSystem _fileSystem;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="SystemService" /> class.
         /// </summary>
         /// <param name="appHost">The app host.</param>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
-        public SystemService(IServerApplicationHost appHost)
+        public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem)
         {
             _appHost = appHost;
+            _appPaths = appPaths;
+            _fileSystem = fileSystem;
+        }
+
+        public object Get(GetServerLogs request)
+        {
+            List<FileInfo> files;
+
+            try
+            {
+                files = new DirectoryInfo(_appPaths.LogDirectoryPath)
+                    .EnumerateFiles("*", SearchOption.AllDirectories)
+                    .Where(i => string.Equals(i.Extension, ".txt", System.StringComparison.OrdinalIgnoreCase))
+                    .ToList();
+            }
+            catch (DirectoryNotFoundException)
+            {
+                files = new List<FileInfo>();
+            }
+
+            var result = files.Select(i => new LogFile
+            {
+                DateCreated = _fileSystem.GetCreationTimeUtc(i),
+                DateModified = _fileSystem.GetLastWriteTimeUtc(i),
+                Name = i.Name,
+                Size = i.Length
+
+            }).OrderByDescending(i => i.DateModified)
+                .ThenByDescending(i => i.DateCreated)
+                .ThenBy(i => i.Name)
+                .ToList();
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Get(GetLogFile request)
+        {
+            var file = new DirectoryInfo(_appPaths.LogDirectoryPath)
+                .EnumerateFiles("*", SearchOption.AllDirectories)
+                .First(i => string.Equals(i.Name, request.Name, System.StringComparison.OrdinalIgnoreCase));
+
+            return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);
         }
 
         /// <summary>

+ 3 - 7
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -717,9 +717,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
 
-            data = _userDataRepository.GetUserData(user.Id, key);
-
-            return _dtoService.GetUserItemDataDto(data);
+            return _userDataRepository.GetUserDataDto(item, user);
         }
 
         /// <summary>
@@ -766,9 +764,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
 
-            data = _userDataRepository.GetUserData(user.Id, key);
-
-            return _dtoService.GetUserItemDataDto(data);
+            return _userDataRepository.GetUserDataDto(item, user);
         }
 
         /// <summary>
@@ -936,7 +932,7 @@ namespace MediaBrowser.Api.UserLibrary
                 await item.MarkUnplayed(user, _userDataRepository).ConfigureAwait(false);
             }
 
-            return _dtoService.GetUserItemDataDto(_userDataRepository.GetUserData(user.Id, item.GetUserDataKey()));
+            return _userDataRepository.GetUserDataDto(item, user);
         }
     }
 }

+ 4 - 0
MediaBrowser.Api/UserService.cs

@@ -51,6 +51,7 @@ namespace MediaBrowser.Api
     /// Class DeleteUser
     /// </summary>
     [Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")]
+    [Authenticated]
     public class DeleteUser : IReturnVoid
     {
         /// <summary>
@@ -107,6 +108,7 @@ namespace MediaBrowser.Api
     /// Class UpdateUserPassword
     /// </summary>
     [Route("/Users/{Id}/Password", "POST", Summary = "Updates a user's password")]
+    [Authenticated]
     public class UpdateUserPassword : IReturnVoid
     {
         /// <summary>
@@ -138,6 +140,7 @@ namespace MediaBrowser.Api
     /// Class UpdateUser
     /// </summary>
     [Route("/Users/{Id}", "POST", Summary = "Updates a user")]
+    [Authenticated]
     public class UpdateUser : UserDto, IReturnVoid
     {
     }
@@ -146,6 +149,7 @@ namespace MediaBrowser.Api
     /// Class CreateUser
     /// </summary>
     [Route("/Users", "POST", Summary = "Creates a user")]
+    [Authenticated]
     public class CreateUser : UserDto, IReturn<UserDto>
     {
     }

+ 0 - 149
MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs

@@ -1,149 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.IO;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Api.WebSocket
-{
-    /// <summary>
-    /// Class ScheduledTasksWebSocketListener
-    /// </summary>
-    public class LogFileWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<string>, LogFileWebSocketState>
-    {
-        /// <summary>
-        /// Gets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        protected override string Name
-        {
-            get { return "LogFile"; }
-        }
-
-        /// <summary>
-        /// The _kernel
-        /// </summary>
-        private readonly ILogManager _logManager;
-        private readonly IFileSystem _fileSystem;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="LogFileWebSocketListener" /> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="logManager">The log manager.</param>
-        public LogFileWebSocketListener(ILogger logger, ILogManager logManager, IFileSystem fileSystem)
-            : base(logger)
-        {
-            _logManager = logManager;
-            _fileSystem = fileSystem;
-            _logManager.LoggerLoaded += kernel_LoggerLoaded;
-        }
-
-        /// <summary>
-        /// Gets the data to send.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>IEnumerable{System.String}.</returns>
-        protected override async Task<IEnumerable<string>> GetDataToSend(LogFileWebSocketState state)
-        {
-            if (!string.Equals(_logManager.LogFilePath, state.LastLogFilePath))
-            {
-                state.LastLogFilePath = _logManager.LogFilePath;
-                state.StartLine = 0;
-            }
-
-            var lines = await GetLogLines(state.LastLogFilePath, state.StartLine, _fileSystem).ConfigureAwait(false);
-
-            state.StartLine += lines.Count;
-
-            return lines;
-        }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected override void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-                _logManager.LoggerLoaded -= kernel_LoggerLoaded;
-            }
-            base.Dispose(dispose);
-        }
-
-        /// <summary>
-        /// Handles the LoggerLoaded event of the kernel control.
-        /// </summary>
-        /// <param name="sender">The source of the event.</param>
-        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
-        void kernel_LoggerLoaded(object sender, EventArgs e)
-        {
-            // Reset the startline for each connection whenever the logger reloads
-            lock (ActiveConnections)
-            {
-                foreach (var connection in ActiveConnections)
-                {
-                    connection.Item4.StartLine = 0;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the log lines.
-        /// </summary>
-        /// <param name="logFilePath">The log file path.</param>
-        /// <param name="startLine">The start line.</param>
-        /// <returns>Task{IEnumerable{System.String}}.</returns>
-        internal static async Task<List<string>> GetLogLines(string logFilePath, int startLine, IFileSystem fileSystem)
-        {
-            var lines = new List<string>();
-
-            using (var fs = fileSystem.GetFileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
-            {
-                using (var reader = new StreamReader(fs))
-                {
-                    while (!reader.EndOfStream)
-                    {
-                        var line = await reader.ReadLineAsync().ConfigureAwait(false);
-
-                        if (line.IndexOf("Info -", StringComparison.OrdinalIgnoreCase) != -1 ||
-                            line.IndexOf("Warn -", StringComparison.OrdinalIgnoreCase) != -1 ||
-                            line.IndexOf("Error -", StringComparison.OrdinalIgnoreCase) != -1)
-                        {
-                            lines.Add(line);
-                        }
-                    }
-                }
-            }
-
-            if (startLine > 0)
-            {
-                lines = lines.Skip(startLine).ToList();
-            }
-
-            return lines;
-        }
-    }
-
-    /// <summary>
-    /// Class LogFileWebSocketState
-    /// </summary>
-    public class LogFileWebSocketState : WebSocketListenerState
-    {
-        /// <summary>
-        /// Gets or sets the last log file path.
-        /// </summary>
-        /// <value>The last log file path.</value>
-        public string LastLogFilePath { get; set; }
-        /// <summary>
-        /// Gets or sets the start line.
-        /// </summary>
-        /// <value>The start line.</value>
-        public int StartLine { get; set; }
-    }
-}

+ 4 - 0
MediaBrowser.Common/Net/MimeTypes.cs

@@ -199,6 +199,10 @@ namespace MediaBrowser.Common.Net
             {
                 return "application/x-javascript";
             }
+            if (ext.Equals(".map", StringComparison.OrdinalIgnoreCase))
+            {
+                return "application/x-javascript";
+            }
 
             if (ext.Equals(".woff", StringComparison.OrdinalIgnoreCase))
             {

+ 0 - 7
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -25,13 +25,6 @@ namespace MediaBrowser.Controller.Dto
         /// <returns>System.String.</returns>
         string GetDtoId(BaseItem item);
 
-        /// <summary>
-        /// Gets the user item data dto.
-        /// </summary>
-        /// <param name="data">The data.</param>
-        /// <returns>UserItemDataDto.</returns>
-        UserItemDataDto GetUserItemDataDto(UserItemData data);
-
         /// <summary>
         /// Attaches the primary image aspect ratio.
         /// </summary>

+ 15 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Logging;
@@ -1571,5 +1572,19 @@ namespace MediaBrowser.Controller.Entities
 
             return path;
         }
+
+        public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
+        {
+            if (RunTimeTicks.HasValue)
+            {
+                double pct = RunTimeTicks.Value;
+
+                if (pct > 0)
+                {
+                    pct = userData.PlaybackPositionTicks / pct;
+                    dto.PlayedPercentage = 100 * pct;
+                }
+            }
+        }
     }
 }

+ 75 - 5
MediaBrowser.Controller/Entities/Folder.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MoreLinq;
 using System;
@@ -769,6 +770,11 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>IEnumerable{BaseItem}.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
         public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
+        {
+            return GetChildren(user, includeLinkedChildren, false);
+        }
+
+        internal IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren, bool includeHidden)
         {
             if (user == null)
             {
@@ -780,7 +786,7 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false);
+            var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, includeHidden, false);
 
             return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list;
         }
@@ -796,9 +802,10 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="user">The user.</param>
         /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
         /// <param name="list">The list.</param>
+        /// <param name="includeHidden">if set to <c>true</c> [include hidden].</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private bool AddChildrenToList(User user, bool includeLinkedChildren, List<BaseItem> list, bool recursive)
+        private bool AddChildrenToList(User user, bool includeLinkedChildren, List<BaseItem> list, bool includeHidden, bool recursive)
         {
             var hasLinkedChildren = false;
 
@@ -806,7 +813,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 if (child.IsVisible(user))
                 {
-                    if (!child.IsHiddenFromUser(user))
+                    if (includeHidden || !child.IsHiddenFromUser(user))
                     {
                         list.Add(child);
                     }
@@ -815,7 +822,7 @@ namespace MediaBrowser.Controller.Entities
                     {
                         var folder = (Folder)child;
 
-                        if (folder.AddChildrenToList(user, includeLinkedChildren, list, true))
+                        if (folder.AddChildrenToList(user, includeLinkedChildren, list, includeHidden, true))
                         {
                             hasLinkedChildren = true;
                         }
@@ -855,7 +862,7 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true);
+            var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false, true);
 
             return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list;
         }
@@ -1069,5 +1076,68 @@ namespace MediaBrowser.Controller.Entities
             return GetRecursiveChildren(user).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual)
                 .All(i => i.IsUnplayed(user));
         }
+
+        public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
+        {
+            var recursiveItemCount = 0;
+            var unplayed = 0;
+
+            double totalPercentPlayed = 0;
+
+            IEnumerable<BaseItem> children;
+            var folder = this;
+
+            var season = folder as Season;
+
+            if (season != null)
+            {
+                children = season.GetEpisodes(user).Where(i => i.LocationType != LocationType.Virtual);
+            }
+            else
+            {
+                children = folder.GetRecursiveChildren(user)
+                    .Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual);
+            }
+
+            // Loop through each recursive child
+            foreach (var child in children)
+            {
+                recursiveItemCount++;
+
+                var isUnplayed = true;
+
+                var itemUserData = UserDataManager.GetUserData(user.Id, child.GetUserDataKey());
+                
+                // Incrememt totalPercentPlayed
+                if (itemUserData != null)
+                {
+                    if (itemUserData.Played)
+                    {
+                        totalPercentPlayed += 100;
+
+                        isUnplayed = false;
+                    }
+                    else if (itemUserData.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0)
+                    {
+                        double itemPercent = itemUserData.PlaybackPositionTicks;
+                        itemPercent /= child.RunTimeTicks.Value;
+                        totalPercentPlayed += itemPercent;
+                    }
+                }
+
+                if (isUnplayed)
+                {
+                    unplayed++;
+                }
+            }
+
+            dto.UnplayedItemCount = unplayed;
+
+            if (recursiveItemCount > 0)
+            {
+                dto.PlayedPercentage = totalPercentPlayed / recursiveItemCount;
+                dto.Played = dto.PlayedPercentage.Value >= 100;
+            }
+        }
     }
 }

+ 17 - 1
MediaBrowser.Controller/Entities/IHasUserData.cs

@@ -1,4 +1,6 @@
-
+using MediaBrowser.Model.Dto;
+using System;
+
 namespace MediaBrowser.Controller.Entities
 {
     /// <summary>
@@ -6,10 +8,24 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public interface IHasUserData
     {
+        /// <summary>
+        /// Gets or sets the identifier.
+        /// </summary>
+        /// <value>The identifier.</value>
+        Guid Id { get; set; }
+
         /// <summary>
         /// Gets the user data key.
         /// </summary>
         /// <returns>System.String.</returns>
         string GetUserDataKey();
+
+        /// <summary>
+        /// Fills the user data dto values.
+        /// </summary>
+        /// <param name="dto">The dto.</param>
+        /// <param name="userData">The user data.</param>
+        /// <param name="user">The user.</param>
+        void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user);
     }
 }

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

@@ -91,7 +91,7 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             get
             {
-                return FindParent<Season>();
+                return Season;
             }
         }
 

+ 6 - 0
MediaBrowser.Controller/Entities/UserRootFolder.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -51,5 +52,10 @@ namespace MediaBrowser.Controller.Entities
                 LibraryManager.RegisterItem(item);
             }
         }
+
+        public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
+        {
+            // Nothing meaninful here and will only waste resources
+        }
     }
 }

+ 1 - 1
MediaBrowser.Controller/Entities/UserView.cs

@@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities
             var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList();
 
             return user.RootFolder
-                .GetChildren(user, true)
+                .GetChildren(user, true, true)
                 .OfType<Folder>()
                 .Where(i => !excludeFolderIds.Contains(i.Id) && !IsExcludedFromGrouping(i));
         }

+ 9 - 0
MediaBrowser.Controller/Library/IUserDataManager.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Threading;
@@ -34,5 +35,13 @@ namespace MediaBrowser.Controller.Library
         /// <param name="key">The key.</param>
         /// <returns>Task{UserItemData}.</returns>
         UserItemData GetUserData(Guid userId, string key);
+
+        /// <summary>
+        /// Gets the user data dto.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>UserItemDataDto.</returns>
+        UserItemDataDto GetUserDataDto(IHasUserData item, User user);
     }
 }

+ 1 - 3
MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.LiveTv
 {
-    public interface ILiveTvRecording : IHasImages, IHasMediaSources
+    public interface ILiveTvRecording : IHasImages, IHasMediaSources, IHasUserData
     {
         string ServiceName { get; set; }
 
@@ -20,8 +20,6 @@ namespace MediaBrowser.Controller.LiveTv
 
         string GetClientTypeName();
 
-        string GetUserDataKey();
-
         bool IsParentalAllowed(User user);
 
         Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);

+ 25 - 14
MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs

@@ -1,6 +1,6 @@
-using System;
+using MediaBrowser.Model.Entities;
+using System;
 using System.Collections.Generic;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Providers
 {
@@ -18,6 +18,11 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         [Obsolete]
         public bool ForceSave { get; set; }
+
+        public MetadataRefreshOptions()
+        {
+            MetadataRefreshMode = MetadataRefreshMode.Default;
+        }
     }
 
     public class ImageRefreshOptions
@@ -38,48 +43,54 @@ namespace MediaBrowser.Controller.Providers
 
         public bool IsReplacingImage(ImageType type)
         {
-            return ReplaceAllImages || ReplaceImages.Contains(type);
+            return ImageRefreshMode == ImageRefreshMode.FullRefresh &&
+                (ReplaceAllImages || ReplaceImages.Contains(type));
         }
     }
 
     public enum MetadataRefreshMode
     {
         /// <summary>
-        /// Providers will be executed based on default rules
+        /// The none
         /// </summary>
-        EnsureMetadata = 0,
+        None = 0,
 
         /// <summary>
-        /// No providers will be executed
+        /// The validation only
         /// </summary>
-        None = 1,
+        ValidationOnly = 1,
 
         /// <summary>
-        /// All providers will be executed to search for new metadata
+        /// Providers will be executed based on default rules
         /// </summary>
-        FullRefresh = 2,
+        Default = 2,
 
         /// <summary>
-        /// The validation only
+        /// All providers will be executed to search for new metadata
         /// </summary>
-        ValidationOnly = 3
+        FullRefresh = 3
     }
 
     public enum ImageRefreshMode
     {
+        /// <summary>
+        /// The none
+        /// </summary>
+        None = 0,
+
         /// <summary>
         /// The default
         /// </summary>
-        Default = 0,
+        Default = 1,
 
         /// <summary>
         /// Existing images will be validated
         /// </summary>
-        ValidationOnly = 1,
+        ValidationOnly = 2,
 
         /// <summary>
         /// All providers will be executed to search for new metadata
         /// </summary>
-        FullRefresh = 2
+        FullRefresh = 3
     }
 }

+ 7 - 1
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -219,7 +219,13 @@ namespace MediaBrowser.Controller.Session
         /// <param name="deviceName">Name of the device.</param>
         /// <param name="remoteEndPoint">The remote end point.</param>
         /// <returns>Task{SessionInfo}.</returns>
-        Task<AuthenticationResult> AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint);
+        Task<AuthenticationResult> AuthenticateNewSession(string username, 
+            string password, 
+            string clientType, 
+            string appVersion, 
+            string deviceId, 
+            string deviceName, 
+            string remoteEndPoint);
 
         /// <summary>
         /// Reports the capabilities.

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

@@ -77,6 +77,9 @@
     <Compile Include="..\MediaBrowser.Model\ApiClient\SessionUpdatesEventArgs.cs">
       <Link>ApiClient\SessionUpdatesEventArgs.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Branding\BrandingOptions.cs">
+      <Link>Branding\BrandingOptions.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Channels\ChannelFeatures.cs">
       <Link>Channels\ChannelFeatures.cs</Link>
     </Compile>
@@ -815,6 +818,9 @@
     <Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs">
       <Link>Session\UserDataChangeInfo.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\System\LogFile.cs">
+      <Link>System\LogFile.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\System\SystemInfo.cs">
       <Link>System\SystemInfo.cs</Link>
     </Compile>

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

@@ -64,6 +64,9 @@
     <Compile Include="..\MediaBrowser.Model\ApiClient\SessionUpdatesEventArgs.cs">
       <Link>ApiClient\SessionUpdatesEventArgs.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Branding\BrandingOptions.cs">
+      <Link>Branding\BrandingOptions.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Channels\ChannelFeatures.cs">
       <Link>Channels\ChannelFeatures.cs</Link>
     </Compile>
@@ -796,6 +799,9 @@
     <Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs">
       <Link>Session\UserDataChangeInfo.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\System\LogFile.cs">
+      <Link>System\LogFile.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\System\SystemInfo.cs">
       <Link>System\SystemInfo.cs</Link>
     </Compile>

+ 12 - 0
MediaBrowser.Model/Branding/BrandingOptions.cs

@@ -0,0 +1,12 @@
+
+namespace MediaBrowser.Model.Branding
+{
+    public class BrandingOptions
+    {
+        /// <summary>
+        /// Gets or sets the login disclaimer.
+        /// </summary>
+        /// <value>The login disclaimer.</value>
+        public string LoginDisclaimer { get; set; }
+    }
+}

+ 0 - 3
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -158,9 +158,6 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableTmdbUpdates { get; set; }
         public bool EnableFanArtUpdates { get; set; }
 
-        public bool RequireMobileManualLogin { get; set; }
-        public bool RequireNonMobileManualLogin { get; set; }
-
         /// <summary>
         /// Gets or sets the image saving convention.
         /// </summary>

+ 3 - 0
MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs

@@ -10,12 +10,15 @@ namespace MediaBrowser.Model.Configuration
         public bool SaveImagePathsInNfo { get; set; }
         public bool EnablePathSubstitution { get; set; }
 
+        public bool EnableExtraThumbsDuplication { get; set; }
+
         public XbmcMetadataOptions()
         {
             ReleaseDateFormat = "yyyy-MM-dd";
 
             SaveImagePathsInNfo = true;
             EnablePathSubstitution = true;
+            EnableExtraThumbsDuplication = true;
         }
     }
 }

+ 14 - 2
MediaBrowser.Model/Dto/UserItemDataDto.cs

@@ -1,6 +1,6 @@
-using System;
+using MediaBrowser.Model.Extensions;
+using System;
 using System.ComponentModel;
-using MediaBrowser.Model.Extensions;
 
 namespace MediaBrowser.Model.Dto
 {
@@ -15,6 +15,18 @@ namespace MediaBrowser.Model.Dto
         /// <value>The rating.</value>
         public double? Rating { get; set; }
 
+        /// <summary>
+        /// Gets or sets the played percentage.
+        /// </summary>
+        /// <value>The played percentage.</value>
+        public double? PlayedPercentage { get; set; }
+
+        /// <summary>
+        /// Gets or sets the unplayed item count.
+        /// </summary>
+        /// <value>The unplayed item count.</value>
+        public int? UnplayedItemCount { get; set; }
+
         /// <summary>
         /// Gets or sets the playback position ticks.
         /// </summary>

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

@@ -65,6 +65,7 @@
     <Compile Include="ApiClient\IServerEvents.cs" />
     <Compile Include="ApiClient\GeneralCommandEventArgs.cs" />
     <Compile Include="ApiClient\SessionUpdatesEventArgs.cs" />
+    <Compile Include="Branding\BrandingOptions.cs" />
     <Compile Include="Channels\ChannelFeatures.cs" />
     <Compile Include="Channels\ChannelInfo.cs" />
     <Compile Include="Channels\ChannelItemQuery.cs" />
@@ -294,6 +295,7 @@
     <Compile Include="Session\SessionInfoDto.cs" />
     <Compile Include="Session\SessionUserInfo.cs" />
     <Compile Include="Session\UserDataChangeInfo.cs" />
+    <Compile Include="System\LogFile.cs" />
     <Compile Include="Themes\AppTheme.cs" />
     <Compile Include="Themes\AppThemeInfo.cs" />
     <Compile Include="Themes\ThemeImage.cs" />

+ 1 - 2
MediaBrowser.Model/Notifications/NotificationRequest.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Configuration;
-using System;
+using System;
 using System.Collections.Generic;
 
 namespace MediaBrowser.Model.Notifications

+ 1 - 31
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -1,32 +1,20 @@
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Diagnostics;
-using MediaBrowser.Model.Extensions;
 
 namespace MediaBrowser.Model.Session
 {
     [DebuggerDisplay("Client = {Client}, Username = {UserName}")]
     public class SessionInfoDto : IHasPropertyChangedEvent
     {
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance can seek.
-        /// </summary>
-        /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
-        public bool CanSeek { get; set; }
-
         /// <summary>
         /// Gets or sets the supported commands.
         /// </summary>
         /// <value>The supported commands.</value>
         public List<string> SupportedCommands { get; set; }
-        
-        /// <summary>
-        /// Gets or sets the remote end point.
-        /// </summary>
-        /// <value>The remote end point.</value>
-        public string RemoteEndPoint { get; set; }
 
         /// <summary>
         /// Gets or sets the queueable media types.
@@ -99,18 +87,6 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         /// <value>The name of the device.</value>
         public string DeviceName { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is paused.
-        /// </summary>
-        /// <value><c>true</c> if this instance is paused; otherwise, <c>false</c>.</value>
-        public bool IsPaused { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is muted.
-        /// </summary>
-        /// <value><c>true</c> if this instance is muted; otherwise, <c>false</c>.</value>
-        public bool IsMuted { get; set; }
         
         /// <summary>
         /// Gets or sets the now playing item.
@@ -118,12 +94,6 @@ namespace MediaBrowser.Model.Session
         /// <value>The now playing item.</value>
         public BaseItemInfo NowPlayingItem { get; set; }
 
-        /// <summary>
-        /// Gets or sets the now playing position ticks.
-        /// </summary>
-        /// <value>The now playing position ticks.</value>
-        public long? NowPlayingPositionTicks { get; set; }
-
         /// <summary>
         /// Gets or sets the device id.
         /// </summary>

+ 31 - 0
MediaBrowser.Model/System/LogFile.cs

@@ -0,0 +1,31 @@
+using System;
+
+namespace MediaBrowser.Model.System
+{
+    public class LogFile
+    {
+        /// <summary>
+        /// Gets or sets the date created.
+        /// </summary>
+        /// <value>The date created.</value>
+        public DateTime DateCreated { get; set; }
+
+        /// <summary>
+        /// Gets or sets the date modified.
+        /// </summary>
+        /// <value>The date modified.</value>
+        public DateTime DateModified { get; set; }
+
+        /// <summary>
+        /// Gets or sets the size.
+        /// </summary>
+        /// <value>The size.</value>
+        public long Size { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name { get; set; }
+    }
+}

+ 22 - 6
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -441,11 +442,16 @@ namespace MediaBrowser.Providers.Manager
 
                 var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex);
 
-                return new[]
-                    {
-                        Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension),
-                        Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)
-                    };
+                var list = new List<string>
+                {
+                    Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension)
+                };
+
+                if (EnableExtraThumbsDuplication)
+                {
+                    list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension));
+                }
+                return list.ToArray();
             }
 
             if (type == ImageType.Primary)
@@ -528,6 +534,16 @@ namespace MediaBrowser.Providers.Manager
             return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, true) };
         }
 
+        private bool EnableExtraThumbsDuplication
+        {
+            get
+            {
+                var config = _config.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
+
+                return config.EnableExtraThumbsDuplication;
+            }
+        }
+
         /// <summary>
         /// Gets the save path for item in mixed folder.
         /// </summary>

+ 2 - 2
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -234,7 +234,7 @@ namespace MediaBrowser.Providers.MediaInfo
             await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);
 
             if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
-                options.MetadataRefreshMode == MetadataRefreshMode.EnsureMetadata)
+                options.MetadataRefreshMode == MetadataRefreshMode.Default)
             {
                 try
                 {
@@ -460,7 +460,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, options.DirectoryService, false).ToList();
 
-            var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.EnsureMetadata ||
+            var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
                                             options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
 
             if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&

+ 21 - 0
MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs

@@ -0,0 +1,21 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Branding;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Server.Implementations.Branding
+{
+    public class BrandingConfigurationFactory : IConfigurationFactory
+    {
+        public IEnumerable<ConfigurationStore> GetConfigurations()
+        {
+            return new[]
+            {
+                new ConfigurationStore
+                {
+                     ConfigurationType = typeof(BrandingOptions),
+                     Key = "branding"
+                }
+            };
+        }
+    }
+}

+ 31 - 21
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -212,6 +212,12 @@ namespace MediaBrowser.Server.Implementations.Dto
         {
             if (item.IsFolder)
             {
+                var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey());
+
+                // Skip the user data manager because we've already looped through the recursive tree and don't want to do it twice
+                // TODO: Improve in future
+                dto.UserData = GetUserItemDataDto(userData);
+
                 var folder = (Folder)item;
 
                 dto.ChildCount = GetChildCount(folder, user);
@@ -220,15 +226,15 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                     SetSpecialCounts(folder, user, dto, fields);
                 }
-            }
 
-            var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey());
-
-            dto.UserData = GetUserItemDataDto(userData);
+                dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100;
+                dto.UserData.PlayedPercentage = dto.PlayedPercentage;
+                dto.UserData.UnplayedItemCount = dto.RecursiveUnplayedItemCount;
+            }
 
-            if (item.IsFolder)
+            else
             {
-                dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100;
+                dto.UserData = _userDataRepository.GetUserDataDto(item, user);
             }
 
             dto.PlayAccess = item.GetPlayAccess(user);
@@ -1110,16 +1116,17 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             if (episode != null)
             {
-                series = item.FindParent<Series>();
+                series = episode.Series;
 
-                dto.SeriesId = GetDtoId(series);
-                dto.SeriesName = series.Name;
-                dto.AirTime = series.AirTime;
-                dto.SeriesStudio = series.Studios.FirstOrDefault();
-
-                dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb);
-
-                dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
+                if (series != null)
+                {
+                    dto.SeriesId = GetDtoId(series);
+                    dto.SeriesName = series.Name;
+                    dto.AirTime = series.AirTime;
+                    dto.SeriesStudio = series.Studios.FirstOrDefault();
+                    dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb);
+                    dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
+                }
             }
 
             // Add SeasonInfo
@@ -1127,14 +1134,17 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             if (season != null)
             {
-                series = item.FindParent<Series>();
+                series = season.Series;
 
-                dto.SeriesId = GetDtoId(series);
-                dto.SeriesName = series.Name;
-                dto.AirTime = series.AirTime;
-                dto.SeriesStudio = series.Studios.FirstOrDefault();
+                if (series != null)
+                {
+                    dto.SeriesId = GetDtoId(series);
+                    dto.SeriesName = series.Name;
+                    dto.AirTime = series.AirTime;
+                    dto.SeriesStudio = series.Studios.FirstOrDefault();
 
-                dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
+                    dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
+                }
             }
 
             var game = item as Game;

+ 31 - 14
MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs

@@ -1,10 +1,11 @@
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Session;
+using MoreLinq;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -17,21 +18,21 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
     {
         private readonly ISessionManager _sessionManager;
         private readonly ILogger _logger;
-        private readonly IDtoService _dtoService;
         private readonly IUserDataManager _userDataManager;
+        private readonly IUserManager _userManager;
 
         private readonly object _syncLock = new object();
         private Timer UpdateTimer { get; set; }
         private const int UpdateDuration = 500;
 
-        private readonly Dictionary<Guid, List<string>> _changedKeys = new Dictionary<Guid, List<string>>();
+        private readonly Dictionary<Guid, List<IHasUserData>> _changedItems = new Dictionary<Guid, List<IHasUserData>>();
 
-        public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IDtoService dtoService, ILogger logger)
+        public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager)
         {
             _userDataManager = userDataManager;
             _sessionManager = sessionManager;
-            _dtoService = dtoService;
             _logger = logger;
+            _userManager = userManager;
         }
 
         public void Run()
@@ -58,15 +59,28 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
                     UpdateTimer.Change(UpdateDuration, Timeout.Infinite);
                 }
 
-                List<string> keys;
+                List<IHasUserData> keys;
 
-                if (!_changedKeys.TryGetValue(e.UserId, out keys))
+                if (!_changedItems.TryGetValue(e.UserId, out keys))
                 {
-                    keys = new List<string>();
-                    _changedKeys[e.UserId] = keys;
+                    keys = new List<IHasUserData>();
+                    _changedItems[e.UserId] = keys;
                 }
 
-                keys.Add(e.Key);
+                keys.Add(e.Item);
+                
+                var baseItem = e.Item as BaseItem;
+
+                // Go up one level for indicators
+                if (baseItem != null)
+                {
+                    var parent = baseItem.Parent;
+
+                    if (parent != null)
+                    {
+                        keys.Add(parent);
+                    }
+                }
             }
         }
 
@@ -75,8 +89,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             lock (_syncLock)
             {
                 // Remove dupes in case some were saved multiple times
-                var changes = _changedKeys.ToList();
-                _changedKeys.Clear();
+                var changes = _changedItems.ToList();
+                _changedItems.Clear();
 
                 SendNotifications(changes, CancellationToken.None);
 
@@ -88,7 +102,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             }
         }
 
-        private async Task SendNotifications(IEnumerable<KeyValuePair<Guid, List<string>>> changes, CancellationToken cancellationToken)
+        private async Task SendNotifications(IEnumerable<KeyValuePair<Guid, List<IHasUserData>>> changes, CancellationToken cancellationToken)
         {
             foreach (var pair in changes)
             {
@@ -99,8 +113,11 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
                 if (userSessions.Count > 0)
                 {
+                    var user = _userManager.GetUserById(userId);
+
                     var dtoList = pair.Value
-                        .Select(i => _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(userId, i)))
+                        .DistinctBy(i => i.Id)
+                        .Select(i => _userDataManager.GetUserDataDto(i, user))
                         .ToList();
 
                     var info = new UserDataChangeInfo

+ 16 - 10
MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -363,19 +363,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         {
             try
             {
-                var errorResponse = new ErrorResponse
-                {
-                    ResponseStatus = new ResponseStatus
-                    {
-                        ErrorCode = ex.GetType().GetOperationName(),
-                        Message = ex.Message,
-                        StackTrace = ex.StackTrace,
-                    }
-                };
-
                 var operationName = context.Request.GetOperationName();
                 var httpReq = GetRequest(context, operationName);
                 var httpRes = httpReq.Response;
+
+                if (httpRes.IsClosed)
+                {
+                    return;
+                }
+
                 var contentType = httpReq.ResponseContentType;
 
                 var serializer = HostContext.ContentTypes.GetResponseSerializer(contentType);
@@ -398,6 +394,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
                 httpRes.ContentType = contentType;
 
+                var errorResponse = new ErrorResponse
+                {
+                    ResponseStatus = new ResponseStatus
+                    {
+                        ErrorCode = ex.GetType().GetOperationName(),
+                        Message = ex.Message,
+                        StackTrace = ex.StackTrace,
+                    }
+                };
+
                 serializer(httpReq, errorResponse, httpRes);
 
                 httpRes.Close();

+ 8 - 1
MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs

@@ -36,6 +36,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
                 auth.TryGetValue("Version", out version);
             }
 
+            var token = httpReq.Headers["X-MediaBrowser-Token"];
+
+            if (string.IsNullOrWhiteSpace(token))
+            {
+                token = httpReq.QueryString["api_key"];
+            }
+
             return new AuthorizationInfo
             {
                 Client = client,
@@ -43,7 +50,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
                 DeviceId = deviceId,
                 UserId = userId,
                 Version = version,
-                Token = httpReq.Headers["X-AUTH-TOKEN"]
+                Token = token
             };
         }
 

+ 37 - 0
MediaBrowser.Server.Implementations/Library/UserDataManager.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
@@ -125,5 +126,41 @@ namespace MediaBrowser.Server.Implementations.Library
         {
             return userId + key;
         }
+
+        public UserItemDataDto GetUserDataDto(IHasUserData item, User user)
+        {
+            var userData = GetUserData(user.Id, item.GetUserDataKey());
+            var dto = GetUserItemDataDto(userData);
+
+            item.FillUserDataDtoValues(dto, userData, user);
+
+            return dto;
+        }
+
+        /// <summary>
+        /// Converts a UserItemData to a DTOUserItemData
+        /// </summary>
+        /// <param name="data">The data.</param>
+        /// <returns>DtoUserItemData.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        private UserItemDataDto GetUserItemDataDto(UserItemData data)
+        {
+            if (data == null)
+            {
+                throw new ArgumentNullException("data");
+            }
+
+            return new UserItemDataDto
+            {
+                IsFavorite = data.IsFavorite,
+                Likes = data.Likes,
+                PlaybackPositionTicks = data.PlaybackPositionTicks,
+                PlayCount = data.PlayCount,
+                Rating = data.Rating,
+                Played = data.Played,
+                LastPlayedDate = data.LastPlayedDate,
+                Key = data.Key
+            };
+        }
     }
 }

+ 4 - 7
MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs

@@ -4,7 +4,6 @@ using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Logging;
@@ -23,15 +22,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         private readonly IUserDataManager _userDataManager;
         private readonly IDtoService _dtoService;
-        private readonly IItemRepository _itemRepo;
 
-        public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger, IItemRepository itemRepo)
+        public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger)
         {
             _dtoService = dtoService;
             _userDataManager = userDataManager;
             _imageProcessor = imageProcessor;
             _logger = logger;
-            _itemRepo = itemRepo;
         }
 
         public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service, LiveTvProgram program, LiveTvChannel channel)
@@ -249,7 +246,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             if (user != null)
             {
-                dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, recording.GetUserDataKey()));
+                dto.UserData = _userDataManager.GetUserDataDto(recording, user);
 
                 dto.PlayAccess = recording.GetPlayAccess(user);
             }
@@ -322,7 +319,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             if (user != null)
             {
-                dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
+                dto.UserData = _userDataManager.GetUserDataDto(info, user);
 
                 dto.PlayAccess = info.GetPlayAccess(user);
             }
@@ -401,7 +398,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             if (user != null)
             {
-                dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, item.GetUserDataKey()));
+                dto.UserData = _userDataManager.GetUserDataDto(item, user);
 
                 dto.PlayAccess = item.GetPlayAccess(user);
             }

+ 2 - 4
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -40,7 +40,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         private readonly IUserDataManager _userDataManager;
         private readonly ILibraryManager _libraryManager;
         private readonly ITaskManager _taskManager;
-        private readonly IJsonSerializer _json;
 
         private readonly IDtoService _dtoService;
         private readonly ILocalizationManager _localization;
@@ -58,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1);
 
-        public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, IJsonSerializer json, ILocalizationManager localization)
+        public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization)
         {
             _config = config;
             _fileSystem = fileSystem;
@@ -67,12 +66,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             _userManager = userManager;
             _libraryManager = libraryManager;
             _taskManager = taskManager;
-            _json = json;
             _localization = localization;
             _dtoService = dtoService;
             _userDataManager = userDataManager;
 
-            _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, _itemRepo);
+            _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger);
         }
 
         /// <summary>

+ 13 - 4
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -258,11 +258,11 @@
 	"LabelCachePath": "Cache path:",
 	"LabelCachePathHelp": "This folder contains server cache files, such as images.",
 	"LabelImagesByNamePath": "Images by name path:",
-	"LabelImagesByNamePathHelp": "This folder contains actor, artist, genre and studio images.",
+	"LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.",
 	"LabelMetadataPath": "Metadata path:",
 	"LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.",
 	"LabelTranscodingTempPath": "Transcoding temporary path:",
-	"LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder.",
+	"LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.",
 	"TabBasics": "Basics",
 	"TabTV": "TV",
 	"TabGames": "Games",
@@ -284,7 +284,7 @@
 	"ButtonAutoScroll": "Auto-scroll",
 	"LabelImageSavingConvention": "Image saving convention:",
 	"LabelImageSavingConventionHelp": "Media Browser recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.",
-	"OptionImageSavingCompatible": "Compatible - Media Browser/Plex/Xbmc",
+	"OptionImageSavingCompatible": "Compatible - Media Browser/Xbmc/Plex",
 	"OptionImageSavingStandard": "Standard - MB2",
 	"ButtonSignIn": "Sign In",
 	"TitleSignIn": "Sign In",
@@ -849,5 +849,14 @@
 	"LabelXbmcMetadataEnablePathSubstitutionHelp2": "See path substitution.",
 	"LabelGroupChannelsIntoViews": "Display the following channels directly within my views:",
 	"LabelGroupChannelsIntoViewsHelp": "If enabled, these channels will be displayed directly alongside other views. If disabled, they'll be displayed within a separate Channels view.",
-	"LabelDisplayCollectionsView": "Display a Collections view to show movie collections"
+	"LabelDisplayCollectionsView": "Display a Collections view to show movie collections",
+	"LabelXbmcMetadataEnableExtraThumbs": "Copy extrafanart into extrathumbs",
+	"LabelXbmcMetadataEnableExtraThumbsHelp": "When downloading images they can be saved into both extrafanart and extrathumbs for maximum Xbmc skin compatibility.",
+	"TabServices": "Services",
+	"TabLogs": "Logs",
+	"HeaderServerLogFiles": "Server log files:",
+	"TabBranding": "Branding",
+	"HeaderBrandingHelp": "Customize the appearance of Media Browser to fit the needs of your group or organization.",
+	"LabelLoginDisclaimer": "Login disclaimer:",
+	"LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page."
 }

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

@@ -101,6 +101,7 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
+    <Compile Include="Branding\BrandingConfigurationFactory.cs" />
     <Compile Include="Channels\ChannelConfigurations.cs" />
     <Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
     <Compile Include="Channels\ChannelImageProvider.cs" />

+ 3 - 2
MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs

@@ -4,7 +4,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Notifications;
-using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Notifications;
 using System;
@@ -93,7 +92,9 @@ namespace MediaBrowser.Server.Implementations.Notifications
 
             if (options != null && !string.IsNullOrWhiteSpace(request.NotificationType))
             {
-                return _userManager.Users.Where(i => _config.Configuration.NotificationOptions.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Configuration))
+                var config = GetConfiguration();
+
+                return _userManager.Users.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Configuration))
                    .Select(i => i.Id.ToString("N"));
             }
 

+ 21 - 6
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Common.Events;
+using System.Security.Cryptography;
+using System.Text;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
@@ -1185,6 +1187,24 @@ namespace MediaBrowser.Server.Implementations.Session
             };
         }
 
+        private bool IsLocal(string remoteEndpoint)
+        {
+            if (string.IsNullOrWhiteSpace(remoteEndpoint))
+            {
+                throw new ArgumentNullException("remoteEndpoint");
+            }
+
+            // Private address space:
+            // http://en.wikipedia.org/wiki/Private_network
+
+            return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 ||
+                remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) ||
+                remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) ||
+                remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) ||
+                remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
+                remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase);
+        }
+
         /// <summary>
         /// Reports the capabilities.
         /// </summary>
@@ -1283,15 +1303,10 @@ namespace MediaBrowser.Server.Implementations.Session
                 DeviceName = session.DeviceName,
                 Id = session.Id,
                 LastActivityDate = session.LastActivityDate,
-                NowPlayingPositionTicks = session.PlayState.PositionTicks,
-                IsPaused = session.PlayState.IsPaused,
-                IsMuted = session.PlayState.IsMuted,
                 NowViewingItem = session.NowViewingItem,
                 ApplicationVersion = session.ApplicationVersion,
-                CanSeek = session.PlayState.CanSeek,
                 QueueableMediaTypes = session.QueueableMediaTypes,
                 PlayableMediaTypes = session.PlayableMediaTypes,
-                RemoteEndPoint = session.RemoteEndPoint,
                 AdditionalUsers = session.AdditionalUsers,
                 SupportedCommands = session.SupportedCommands,
                 UserName = session.UserName,

+ 28 - 5
MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs

@@ -26,13 +26,36 @@ namespace MediaBrowser.Server.Implementations.Sorting
         /// <returns>System.String.</returns>
         private DateTime GetValue(BaseItem x)
         {
-            var series = (x as Series) ?? x.FindParent<Series>();
+            var series = x as Series;
 
-            DateTime result;
-            if (series != null && DateTime.TryParse(series.AirTime, out result))
+            if (series == null)
             {
-                return result;
-            } 
+                var season = x as Season;
+
+                if (season != null)
+                {
+                    series = season.Series;
+                }
+                else
+                {
+                    var episode = x as Episode;
+
+                    if (episode != null)
+                    {
+                        series = episode.Series;
+                    }
+                }
+            }
+
+            if (series != null)
+            {
+                DateTime result;
+                if (DateTime.TryParse(series.AirTime, out result))
+                {
+                    return result;
+                }
+            }
+
             return DateTime.MinValue;
         }
 

+ 1 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -654,7 +654,7 @@ namespace MediaBrowser.ServerApplication
             var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);
             RegisterSingleInstance<ICollectionManager>(collectionManager);
 
-            LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, JsonSerializer, LocalizationManager);
+            LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager);
             RegisterSingleInstance(LiveTvManager);
 
             UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, FileSystemManager, UserManager, ChannelManager, LiveTvManager);

+ 0 - 12
MediaBrowser.ServerApplication/Native/BrowserLauncher.cs

@@ -86,18 +86,6 @@ namespace MediaBrowser.ServerApplication.Native
                       appHost.WebApplicationName + "/swagger-ui/index.html", logger);
         }
 
-        /// <summary>
-        /// Opens the standard API documentation.
-        /// </summary>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="appHost">The app host.</param>
-        /// <param name="logger">The logger.</param>
-        public static void OpenStandardApiDocumentation(IServerConfigurationManager configurationManager, IServerApplicationHost appHost, ILogger logger)
-        {
-            OpenUrl("http://localhost:" + configurationManager.Configuration.HttpServerPortNumber + "/" +
-                      appHost.WebApplicationName + "/metadata", logger);
-        }
-
         /// <summary>
         /// Opens the URL.
         /// </summary>

+ 0 - 15
MediaBrowser.ServerApplication/ServerNotifyIcon.cs

@@ -29,7 +29,6 @@ namespace MediaBrowser.ServerApplication
         private System.Windows.Forms.ToolStripMenuItem cmdLogWindow;
         private System.Windows.Forms.ToolStripMenuItem cmdCommunity;
         private System.Windows.Forms.ToolStripMenuItem cmdApiDocs;
-        private System.Windows.Forms.ToolStripMenuItem cmdStandardDocs;
         private System.Windows.Forms.ToolStripMenuItem cmdSwagger;
         private System.Windows.Forms.ToolStripMenuItem cmdGtihub;
 
@@ -90,7 +89,6 @@ namespace MediaBrowser.ServerApplication
             cmdConfigure = new System.Windows.Forms.ToolStripMenuItem();
             cmdBrowse = new System.Windows.Forms.ToolStripMenuItem();
             cmdApiDocs = new System.Windows.Forms.ToolStripMenuItem();
-            cmdStandardDocs = new System.Windows.Forms.ToolStripMenuItem();
             cmdSwagger = new System.Windows.Forms.ToolStripMenuItem();
             cmdGtihub = new System.Windows.Forms.ToolStripMenuItem();
             
@@ -169,17 +167,11 @@ namespace MediaBrowser.ServerApplication
             // cmdApiDocs
             // 
             cmdApiDocs.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
-            cmdStandardDocs,
             cmdSwagger,
             cmdGtihub});
             cmdApiDocs.Name = "cmdApiDocs";
             cmdApiDocs.Size = new System.Drawing.Size(208, 22);
             // 
-            // cmdStandardDocs
-            // 
-            cmdStandardDocs.Name = "cmdStandardDocs";
-            cmdStandardDocs.Size = new System.Drawing.Size(136, 22);
-            // 
             // cmdSwagger
             // 
             cmdSwagger.Name = "cmdSwagger";
@@ -199,7 +191,6 @@ namespace MediaBrowser.ServerApplication
             cmdLibraryExplorer.Click += cmdLibraryExplorer_Click;
 
             cmdSwagger.Click += cmdSwagger_Click;
-            cmdStandardDocs.Click += cmdStandardDocs_Click;
             cmdGtihub.Click += cmdGtihub_Click;
 
             LoadLogWindow(null, EventArgs.Empty);
@@ -224,7 +215,6 @@ namespace MediaBrowser.ServerApplication
             cmdCommunity.Text = _localization.GetLocalizedString("LabelVisitCommunity");
             cmdGtihub.Text = _localization.GetLocalizedString("LabelGithubWiki");
             cmdSwagger.Text = _localization.GetLocalizedString("LabelSwagger");
-            cmdStandardDocs.Text = _localization.GetLocalizedString("LabelStandard");
             cmdApiDocs.Text = _localization.GetLocalizedString("LabelViewApiDocumentation");
             cmdBrowse.Text = _localization.GetLocalizedString("LabelBrowseLibrary");
             cmdConfigure.Text = _localization.GetLocalizedString("LabelConfigureMediaBrowser");
@@ -346,11 +336,6 @@ namespace MediaBrowser.ServerApplication
             BrowserLauncher.OpenGithub(_logger);
         }
 
-        void cmdStandardDocs_Click(object sender, EventArgs e)
-        {
-            BrowserLauncher.OpenStandardApiDocumentation(_configurationManager, _appHost, _logger);
-        }
-
         void cmdSwagger_Click(object sender, EventArgs e)
         {
             BrowserLauncher.OpenSwagger(_configurationManager, _appHost, _logger);

+ 1 - 2
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -521,6 +521,7 @@ namespace MediaBrowser.WebDashboard.Api
                                 "mediacontroller.js",
                                 "chromecast.js",
                                 "backdrops.js",
+                                "branding.js",
 
                                 "mediaplayer.js",
                                 "mediaplayer-video.js",
@@ -529,7 +530,6 @@ namespace MediaBrowser.WebDashboard.Api
 
                                 "ratingdialog.js",
                                 "aboutpage.js",
-                                "allusersettings.js",
                                 "alphapicker.js",
                                 "addpluginpage.js",
                                 "advancedconfigurationpage.js",
@@ -537,7 +537,6 @@ namespace MediaBrowser.WebDashboard.Api
                                 "advancedserversettings.js",
                                 "metadataadvanced.js",
                                 "appsplayback.js",
-                                "appsweather.js",
                                 "autoorganizetv.js",
                                 "autoorganizelog.js",
                                 "channels.js",

+ 6 - 16
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -98,6 +98,9 @@
     <Content Include="dashboard-ui\appsplayback.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\branding.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\channelitems.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -392,9 +395,6 @@
     <Content Include="dashboard-ui\musicalbumartists.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\allusersettings.html">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\collections.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -617,6 +617,9 @@
     <Content Include="dashboard-ui\scripts\backdrops.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\branding.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\channelitems.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -1499,9 +1502,6 @@
     <Content Include="dashboard-ui\musicvideos.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\scripts\allusersettings.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\scripts\alphapicker.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -1748,11 +1748,6 @@
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
   </ItemGroup>
-  <ItemGroup>
-    <Content Include="dashboard-ui\appsweather.html">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-  </ItemGroup>
   <ItemGroup>
     <Content Include="dashboard-ui\useredit.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -1814,11 +1809,6 @@
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
   </ItemGroup>
-  <ItemGroup>
-    <Content Include="dashboard-ui\scripts\appsweather.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-  </ItemGroup>
   <ItemGroup>
     <Content Include="dashboard-ui\scripts\pluginspage.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>