浏览代码

Merge branch 'beta' of https://github.com/MediaBrowser/Emby into beta

Luke Pulverenti 9 年之前
父节点
当前提交
365b2d82b1
共有 26 个文件被更改,包括 164 次插入200 次删除
  1. 29 24
      Emby.Drawing/ImageProcessor.cs
  2. 1 0
      MediaBrowser.Api/Images/ImageService.cs
  3. 5 5
      MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
  4. 86 75
      MediaBrowser.Api/Reports/ReportsService.cs
  5. 2 2
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  6. 1 1
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  7. 1 2
      MediaBrowser.Model.Portable/FodyWeavers.xml
  8. 2 10
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  9. 0 5
      MediaBrowser.Model.Portable/packages.config
  10. 0 3
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  11. 3 6
      MediaBrowser.Model/Dto/BaseItemDto.cs
  12. 2 8
      MediaBrowser.Model/Dto/BaseItemPerson.cs
  13. 2 6
      MediaBrowser.Model/Dto/ChapterInfoDto.cs
  14. 1 7
      MediaBrowser.Model/Dto/UserDto.cs
  15. 2 5
      MediaBrowser.Model/Dto/UserItemDataDto.cs
  16. 1 8
      MediaBrowser.Model/Entities/DisplayPreferences.cs
  17. 0 8
      MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs
  18. 1 8
      MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
  19. 1 4
      MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
  20. 7 0
      MediaBrowser.Model/LiveTv/TimerInfoDto.cs
  21. 0 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  22. 1 4
      MediaBrowser.Model/Session/SessionInfoDto.cs
  23. 1 0
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  24. 5 1
      MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
  25. 1 1
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  26. 9 6
      MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs

+ 29 - 24
Emby.Drawing/ImageProcessor.cs

@@ -163,7 +163,7 @@ namespace Emby.Drawing
             return _imageEncoder.SupportedOutputFormats;
         }
 
-        public async Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options)
+        public async Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options)
         {
             if (options == null)
             {
@@ -178,14 +178,13 @@ namespace Emby.Drawing
             }
 
             var originalImagePath = originalImage.Path;
+            var dateModified = originalImage.DateModified;
 
             if (!_imageEncoder.SupportsImageEncoding)
             {
-                return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+                return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
 
-            var dateModified = originalImage.DateModified;
-
             if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
             {
                 var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
@@ -211,7 +210,7 @@ namespace Emby.Drawing
             if (options.HasDefaultOptions(originalImagePath))
             {
                 // Just spit out the original file if all the options are default
-                return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+                return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
 
             ImageSize? originalImageSize;
@@ -221,7 +220,7 @@ namespace Emby.Drawing
                 if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
                 {
                     // Just spit out the original file if all the options are default
-                    return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+                    return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
                 }
             }
             catch
@@ -235,10 +234,6 @@ namespace Emby.Drawing
             var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
             var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
 
-            var semaphore = GetLock(cacheFilePath);
-
-            await semaphore.WaitAsync().ConfigureAwait(false);
-
             var imageProcessingLockTaken = false;
 
             try
@@ -251,15 +246,20 @@ namespace Emby.Drawing
                     var newHeight = Convert.ToInt32(newSize.Height);
 
                     _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
+                    var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
+                    _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
 
                     await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
 
                     imageProcessingLockTaken = true;
 
-                    _imageEncoder.EncodeImage(originalImagePath, cacheFilePath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
+                    _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
+                    CopyFile(tmpPath, cacheFilePath);
+
+                    return new Tuple<string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath));
                 }
 
-                return new Tuple<string, string>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath));
+                return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
             }
             catch (Exception ex)
             {
@@ -267,7 +267,7 @@ namespace Emby.Drawing
                 _logger.ErrorException("Error encoding image", ex);
 
                 // Just spit out the original file if all the options are default
-                return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+                return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
             finally
             {
@@ -275,8 +275,18 @@ namespace Emby.Drawing
                 {
                     _imageProcessingSemaphore.Release();
                 }
+            }
+        }
+
+        private void CopyFile(string src, string destination)
+        {
+            try
+            {
+                File.Copy(src, destination, true);
+            }
+            catch
+            {
 
-                semaphore.Release();
             }
         }
 
@@ -412,14 +422,9 @@ namespace Emby.Drawing
 
             var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
 
-            var semaphore = GetLock(croppedImagePath);
-
-            await semaphore.WaitAsync().ConfigureAwait(false);
-
             // Check again in case of contention
             if (_fileSystem.FileExists(croppedImagePath))
             {
-                semaphore.Release();
                 return GetResult(croppedImagePath);
             }
 
@@ -428,11 +433,15 @@ namespace Emby.Drawing
             try
             {
                 _fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
+                var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
+                _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
 
                 await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
                 imageProcessingLockTaken = true;
 
-                _imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
+                _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
+                CopyFile(tmpPath, croppedImagePath);
+                return GetResult(tmpPath);
             }
             catch (NotImplementedException)
             {
@@ -452,11 +461,7 @@ namespace Emby.Drawing
                 {
                     _imageProcessingSemaphore.Release();
                 }
-
-                semaphore.Release();
             }
-
-            return GetResult(croppedImagePath);
         }
 
         private Tuple<string, DateTime> GetResult(string path)

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

@@ -638,6 +638,7 @@ namespace MediaBrowser.Api.Images
                 CacheDuration = cacheDuration,
                 ResponseHeaders = headers,
                 ContentType = imageResult.Item2,
+                DateLastModified = imageResult.Item3,
                 IsHeadRequest = isHeadRequest,
                 Path = imageResult.Item1,
 

+ 5 - 5
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -108,11 +108,11 @@ namespace MediaBrowser.Api.Playback.Progressive
             var eofCount = 0;
             long position = 0;
 
-            using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, false))
+            using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
             {
                 while (eofCount < 15)
                 {
-                    CopyToInternal(fs, outputStream, BufferSize);
+                    await CopyToInternal(fs, outputStream, BufferSize).ConfigureAwait(false);
 
                     var fsPosition = fs.Position;
 
@@ -138,11 +138,11 @@ namespace MediaBrowser.Api.Playback.Progressive
             }
         }
 
-        private void CopyToInternal(Stream source, Stream destination, int bufferSize)
+        private async Task CopyToInternal(Stream source, Stream destination, int bufferSize)
         {
             var array = new byte[bufferSize];
             int count;
-            while ((count = source.Read(array, 0, array.Length)) != 0)
+            while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
             {
                 //if (_job != null)
                 //{
@@ -168,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Progressive
                 //    }
                 //}
 
-                destination.Write(array, 0, count);
+                await destination.WriteAsync(array, 0, count).ConfigureAwait(false);
 
                 _bytesWritten += count;
 

+ 86 - 75
MediaBrowser.Api/Reports/ReportsService.cs

@@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Reports
 
         /// <summary> Manager for library. </summary>
         private readonly ILibraryManager _libraryManager;   ///< Manager for library
-        /// <summary> The localization. </summary>
+                                                            /// <summary> The localization. </summary>
 
         private readonly ILocalizationManager _localization;    ///< The localization
 
@@ -58,10 +58,10 @@ namespace MediaBrowser.Api.Reports
         /// <summary> Gets the given request. </summary>
         /// <param name="request"> The request. </param>
         /// <returns> A Task&lt;object&gt; </returns>
-        public async Task<object> Get(GetActivityLogs request)
+        public object Get(GetActivityLogs request)
         {
             request.DisplayType = "Screen";
-            ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
+            ReportResult result = GetReportActivities(request);
             return ToOptimizedResult(result);
         }
 
@@ -104,7 +104,8 @@ namespace MediaBrowser.Api.Reports
                 return null;
 
             request.DisplayType = "Screen";
-            var reportResult = await GetReportResult(request);
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+            var reportResult = await GetReportResult(request, user);
 
             return ToOptimizedResult(reportResult);
         }
@@ -117,7 +118,8 @@ namespace MediaBrowser.Api.Reports
             if (string.IsNullOrEmpty(request.IncludeItemTypes))
                 return null;
             request.DisplayType = "Screen";
-            var reportResult = await GetReportStatistic(request);
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+            var reportResult = await GetReportStatistic(request, user);
 
             return ToOptimizedResult(reportResult);
         }
@@ -150,6 +152,7 @@ namespace MediaBrowser.Api.Reports
             headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
             headers["Content-Encoding"] = "UTF-8";
 
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
             ReportResult result = null;
             switch (reportViewType)
             {
@@ -157,12 +160,12 @@ namespace MediaBrowser.Api.Reports
                 case ReportViewType.ReportData:
                     ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
                     ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
-                    QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+                    QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
                     result = dataBuilder.GetResult(queryResult.Items, request);
                     result.TotalRecordCount = queryResult.TotalRecordCount;
                     break;
                 case ReportViewType.ReportActivities:
-                    result = await GetReportActivities(request).ConfigureAwait(false);
+                    result = GetReportActivities(request);
                     break;
             }
 
@@ -177,23 +180,15 @@ namespace MediaBrowser.Api.Reports
                     break;
             }
 
-            object ro = ResultFactory.GetResult(returnResult, contentType, headers);
-            return ro;
+            return ResultFactory.GetResult(returnResult, contentType, headers);
         }
 
         #endregion
 
-        #region [Private Methods]
-
-        /// <summary> Gets items query. </summary>
-        /// <param name="request"> The request. </param>
-        /// <param name="user"> The user. </param>
-        /// <returns> The items query. </returns>
         private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
         {
-            var query = new InternalItemsQuery
+            var query = new InternalItemsQuery(user)
             {
-                User = user,
                 IsPlayed = request.IsPlayed,
                 MediaTypes = request.GetMediaTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
@@ -231,6 +226,7 @@ namespace MediaBrowser.Api.Reports
                 Tags = request.GetTags(),
                 OfficialRatings = request.GetOfficialRatings(),
                 Genres = request.GetGenres(),
+                GenreIds = request.GetGenreIds(),
                 Studios = request.GetStudios(),
                 StudioIds = request.GetStudioIds(),
                 Person = request.Person,
@@ -245,9 +241,11 @@ namespace MediaBrowser.Api.Reports
                 MaxPlayers = request.MaxPlayers,
                 MinCommunityRating = request.MinCommunityRating,
                 MinCriticRating = request.MinCriticRating,
+                ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
                 ParentIndexNumber = request.ParentIndexNumber,
                 AiredDuringSeason = request.AiredDuringSeason,
-                AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
+                AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
+                EnableTotalRecordCount = request.EnableTotalRecordCount
             };
 
             if (!string.IsNullOrWhiteSpace(request.Ids))
@@ -357,98 +355,111 @@ namespace MediaBrowser.Api.Reports
                 query.AlbumNames = request.Albums.Split('|');
             }
 
-            if (request.HasQueryLimit == false)
-            {
-                query.StartIndex = null;
-                query.Limit = null;
-            }
-
             return query;
         }
 
-        /// <summary> Gets query result. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> The query result. </returns>
-        private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
+        private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
         {
-            // Placeholder in case needed later
+            // all report queries currently need this because it's not being specified
             request.Recursive = true;
-            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-            request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
-
-            var parentItem = string.IsNullOrEmpty(request.ParentId) ?
-                (user == null ? _libraryManager.RootFolder : user.RootFolder) :
-                _libraryManager.GetItemById(request.ParentId);
 
             var item = string.IsNullOrEmpty(request.ParentId) ?
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
-                parentItem;
+                _libraryManager.GetItemById(request.ParentId);
 
-            IEnumerable<BaseItem> items;
+            if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
+            {
+                //item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+            }
+            else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
+            {
+                item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+            }
 
-            if (request.Recursive)
+            // Default list type = children
+
+            var folder = item as Folder;
+            if (folder == null)
             {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-                return result;
+                folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
             }
-            else
+
+            if (!string.IsNullOrEmpty(request.Ids))
             {
-                if (user == null)
+                request.Recursive = true;
+                var query = GetItemsQuery(request, user);
+                var result = await folder.GetItems(query).ConfigureAwait(false);
+
+                if (string.IsNullOrWhiteSpace(request.SortBy))
                 {
-                    var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
-                    return result;
+                    var ids = query.ItemIds.ToList();
+
+                    // Try to preserve order
+                    result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
                 }
 
-                var userRoot = item as UserRootFolder;
+                return result;
+            }
 
-                if (userRoot == null)
-                {
-                    var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+            if (request.Recursive)
+            {
+                return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+            }
 
-                    return result;
-                }
+            if (user == null)
+            {
+                return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+            }
+
+            var userRoot = item as UserRootFolder;
 
-                items = ((Folder)item).GetChildren(user, true);
+            if (userRoot == null)
+            {
+                return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
             }
 
-            return new QueryResult<BaseItem> { Items = items.ToArray() };
+            IEnumerable<BaseItem> items = folder.GetChildren(user, true);
 
+            var itemsArray = items.ToArray();
+
+            return new QueryResult<BaseItem>
+            {
+                Items = itemsArray,
+                TotalRecordCount = itemsArray.Length
+            };
         }
 
+        #region [Private Methods]
+
         /// <summary> Gets report activities. </summary>
         /// <param name="request"> The request. </param>
         /// <returns> The report activities. </returns>
-        private Task<ReportResult> GetReportActivities(IReportsDownload request)
+        private ReportResult GetReportActivities(IReportsDownload request)
         {
-            return Task<ReportResult>.Run(() =>
-            {
-                DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
-                (DateTime?)null :
-                DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-
-                QueryResult<ActivityLogEntry> queryResult;
-                 if (request.HasQueryLimit)   
-                   queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
-                 else
-                     queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
-                //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
-
-                ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
-                var result = builder.GetResult(queryResult, request);
-                result.TotalRecordCount = queryResult.TotalRecordCount;
-                return result;
+            DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
+            (DateTime?)null :
+            DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
 
-            });
+            QueryResult<ActivityLogEntry> queryResult;
+            if (request.HasQueryLimit)
+                queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
+            else
+                queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
+            //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
 
+            ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
+            var result = builder.GetResult(queryResult, request);
+            result.TotalRecordCount = queryResult.TotalRecordCount;
+            return result;
         }
 
         /// <summary> Gets report result. </summary>
         /// <param name="request"> The request. </param>
         /// <returns> The report result. </returns>
-        private async Task<ReportResult> GetReportResult(GetItemReport request)
+        private async Task<ReportResult> GetReportResult(GetItemReport request, User user)
         {
             ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
-            QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+            QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
             ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
             reportResult.TotalRecordCount = queryResult.TotalRecordCount;
 
@@ -458,10 +469,10 @@ namespace MediaBrowser.Api.Reports
         /// <summary> Gets report statistic. </summary>
         /// <param name="request"> The request. </param>
         /// <returns> The report statistic. </returns>
-        private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
+        private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request, User user)
         {
             ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-            QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+            QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
 
             ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
             ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);

+ 2 - 2
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
         
-            var result = await GetItemsToSerialize(request, user).ConfigureAwait(false);
+            var result = await GetQueryResult(request, user).ConfigureAwait(false);
 
             if (result == null)
             {
@@ -135,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="user">The user.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        private async Task<QueryResult<BaseItem>> GetItemsToSerialize(GetItems request, User user)
+        private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, User user)
         {
             var item = string.IsNullOrEmpty(request.ParentId) ?
                 user == null ? _libraryManager.RootFolder : user.RootFolder :

+ 1 - 1
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Drawing
         /// </summary>
         /// <param name="options">The options.</param>
         /// <returns>Task.</returns>
-        Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options);
+        Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options);
 
         /// <summary>
         /// Gets the enhanced image.

+ 1 - 2
MediaBrowser.Model.Portable/FodyWeavers.xml

@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Weavers>
-  <PropertyChanged />
 </Weavers>

+ 2 - 10
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -36,6 +36,8 @@
     <UseApplicationTrust>false</UseApplicationTrust>
     <BootstrapperEnabled>true</BootstrapperEnabled>
     <RestorePackages>true</RestorePackages>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -617,9 +619,6 @@
     <Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
       <Link>Extensions\FloatHelper.cs</Link>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
-      <Link>Extensions\IHasPropertyChangedEvent.cs</Link>
-    </Compile>
     <Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
       <Link>Extensions\IntHelper.cs</Link>
     </Compile>
@@ -1233,13 +1232,6 @@
     <PostBuildEvent>
     </PostBuildEvent>
   </PropertyGroup>
-  <Import Project="..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" />
-  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
-    <PropertyGroup>
-      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
-    </PropertyGroup>
-    <Error Condition="!Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets'))" />
-  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 0 - 5
MediaBrowser.Model.Portable/packages.config

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
-  <package id="Fody" version="1.29.2" targetFramework="portable45-net45+win8+wp8+wpa81" developmentDependency="true" />
-  <package id="PropertyChanged.Fody" version="1.50.4" targetFramework="portable45-net45+win8+wp8+wpa81" developmentDependency="true" />
-</packages>

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

@@ -591,9 +591,6 @@
     <Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
       <Link>Extensions\FloatHelper.cs</Link>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
-      <Link>Extensions\IHasPropertyChangedEvent.cs</Link>
-    </Compile>
     <Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
       <Link>Extensions\IntHelper.cs</Link>
     </Compile>

+ 3 - 6
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Dto
     /// This holds information about a BaseItem in a format that is convenient for the client.
     /// </summary>
     [DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
-    public class BaseItemDto : IHasProviderIds, IHasPropertyChangedEvent, IItemDto, IHasServerId, IHasSyncInfo
+    public class BaseItemDto : IHasProviderIds, IItemDto, IHasServerId, IHasSyncInfo
     {
         /// <summary>
         /// Gets or sets the name.
@@ -114,6 +114,8 @@ namespace MediaBrowser.Model.Dto
         /// <value>The synchronize percent.</value>
         public double? SyncPercent { get; set; }
 
+        public string Container { get; set; }
+
         /// <summary>
         /// Gets or sets the DVD season number.
         /// </summary>
@@ -1109,11 +1111,6 @@ namespace MediaBrowser.Model.Dto
             }
         }
 
-        /// <summary>
-        /// Occurs when [property changed].
-        /// </summary>
-        public event PropertyChangedEventHandler PropertyChanged;
-
         /// <summary>
         /// Gets or sets the program identifier.
         /// </summary>

+ 2 - 8
MediaBrowser.Model/Dto/BaseItemPerson.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Extensions;
-using System.ComponentModel;
+using System.ComponentModel;
 using System.Diagnostics;
 using System.Runtime.Serialization;
 
@@ -9,7 +8,7 @@ namespace MediaBrowser.Model.Dto
     /// This is used by the api to get information about a Person within a BaseItem
     /// </summary>
     [DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
-    public class BaseItemPerson : IHasPropertyChangedEvent
+    public class BaseItemPerson
     {
         /// <summary>
         /// Gets or sets the name.
@@ -53,10 +52,5 @@ namespace MediaBrowser.Model.Dto
                 return PrimaryImageTag != null;
             }
         }
-
-        /// <summary>
-        /// Occurs when [property changed].
-        /// </summary>
-        public event PropertyChangedEventHandler PropertyChanged;
     }
 }

+ 2 - 6
MediaBrowser.Model/Dto/ChapterInfoDto.cs

@@ -1,7 +1,5 @@
-using System.ComponentModel;
-using System.Diagnostics;
+using System.Diagnostics;
 using System.Runtime.Serialization;
-using MediaBrowser.Model.Extensions;
 
 namespace MediaBrowser.Model.Dto
 {
@@ -9,7 +7,7 @@ namespace MediaBrowser.Model.Dto
     /// Class ChapterInfo
     /// </summary>
     [DebuggerDisplay("Name = {Name}")]
-    public class ChapterInfoDto : IHasPropertyChangedEvent
+    public class ChapterInfoDto
     {
         /// <summary>
         /// Gets or sets the start position ticks.
@@ -38,7 +36,5 @@ namespace MediaBrowser.Model.Dto
         {
             get { return ImageTag != null; }
         }
-
-        public event PropertyChangedEventHandler PropertyChanged;
     }
 }

+ 1 - 7
MediaBrowser.Model/Dto/UserDto.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Connect;
-using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Users;
 using System;
 using System.ComponentModel;
@@ -13,7 +12,7 @@ namespace MediaBrowser.Model.Dto
     /// Class UserDto
     /// </summary>
     [DebuggerDisplay("Name = {Name}, ID = {Id}, HasPassword = {HasPassword}")]
-    public class UserDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
+    public class UserDto : IItemDto, IHasServerId
     {
         /// <summary>
         /// Gets or sets the name.
@@ -141,11 +140,6 @@ namespace MediaBrowser.Model.Dto
             Policy = new UserPolicy();
         }
 
-        /// <summary>
-        /// Occurs when [property changed].
-        /// </summary>
-        public event PropertyChangedEventHandler PropertyChanged;
-
         public override string ToString()
         {
             return Name ?? base.ToString();

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

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Extensions;
-using System;
+using System;
 using System.ComponentModel;
 
 namespace MediaBrowser.Model.Dto
@@ -7,7 +6,7 @@ namespace MediaBrowser.Model.Dto
     /// <summary>
     /// Class UserItemDataDto
     /// </summary>
-    public class UserItemDataDto : IHasPropertyChangedEvent
+    public class UserItemDataDto
     {
         /// <summary>
         /// Gets or sets the rating.
@@ -74,7 +73,5 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         /// <value>The item identifier.</value>
         public string ItemId { get; set; }
-        
-        public event PropertyChangedEventHandler PropertyChanged;
     }
 }

+ 1 - 8
MediaBrowser.Model/Entities/DisplayPreferences.cs

@@ -1,21 +1,14 @@
 using MediaBrowser.Model.Drawing;
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using MediaBrowser.Model.Extensions;
 
 namespace MediaBrowser.Model.Entities
 {
     /// <summary>
     /// Defines the display preferences for any item that supports them (usually Folders)
     /// </summary>
-    public class DisplayPreferences : IHasPropertyChangedEvent
+    public class DisplayPreferences
     {
-        /// <summary>
-        /// Occurs when [property changed].
-        /// </summary>
-        public event PropertyChangedEventHandler PropertyChanged;
-        
         /// <summary>
         /// The image scale
         /// </summary>

+ 0 - 8
MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs

@@ -1,8 +0,0 @@
-using System.ComponentModel;
-
-namespace MediaBrowser.Model.Extensions
-{
-    public interface IHasPropertyChangedEvent : INotifyPropertyChanged
-    {
-    }
-}

+ 1 - 8
MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs

@@ -1,17 +1,10 @@
 using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Extensions;
 using System;
-using System.ComponentModel;
 
 namespace MediaBrowser.Model.LiveTv
 {
-    public class BaseTimerInfoDto : IHasPropertyChangedEvent, IHasServerId
+    public class BaseTimerInfoDto : IHasServerId
     {
-        /// <summary>
-        /// Occurs when a property value changes.
-        /// </summary>
-        public event PropertyChangedEventHandler PropertyChanged;
-
         /// <summary>
         /// Id of the recording.
         /// </summary>

+ 1 - 4
MediaBrowser.Model/LiveTv/ChannelInfoDto.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Library;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -13,7 +12,7 @@ namespace MediaBrowser.Model.LiveTv
     /// Class ChannelInfoDto
     /// </summary>
     [DebuggerDisplay("Name = {Name}, Number = {Number}")]
-    public class ChannelInfoDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
+    public class ChannelInfoDto : IItemDto, IHasServerId
     {
         /// <summary>
         /// Gets or sets the name.
@@ -120,7 +119,5 @@ namespace MediaBrowser.Model.LiveTv
             ImageTags = new Dictionary<ImageType, string>();
             MediaSources = new List<MediaSourceInfo>();
         }
-
-        public event PropertyChangedEventHandler PropertyChanged;
     }
 }

+ 7 - 0
MediaBrowser.Model/LiveTv/TimerInfoDto.cs

@@ -4,6 +4,11 @@ namespace MediaBrowser.Model.LiveTv
 {
     public class TimerInfoDto : BaseTimerInfoDto
     {
+        public TimerInfoDto()
+        {
+            Type = "Timer";
+        }
+
         /// <summary>
         /// Gets or sets the status.
         /// </summary>
@@ -22,6 +27,8 @@ namespace MediaBrowser.Model.LiveTv
         /// <value>The external series timer identifier.</value>
         public string ExternalSeriesTimerId { get; set; }
 
+        public string Type { get; set; }
+
         /// <summary>
         /// Gets or sets the run time ticks.
         /// </summary>

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

@@ -229,7 +229,6 @@
     <Compile Include="Entities\SortOrder.cs" />
     <Compile Include="Events\GenericEventArgs.cs" />
     <Compile Include="Extensions\DoubleHelper.cs" />
-    <Compile Include="Extensions\IHasPropertyChangedEvent.cs" />
     <Compile Include="Extensions\IntHelper.cs" />
     <Compile Include="Extensions\ListHelper.cs" />
     <Compile Include="Extensions\StringHelper.cs" />

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

@@ -1,5 +1,4 @@
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -8,7 +7,7 @@ using System.Diagnostics;
 namespace MediaBrowser.Model.Session
 {
     [DebuggerDisplay("Client = {Client}, Username = {UserName}")]
-    public class SessionInfoDto : IHasPropertyChangedEvent
+    public class SessionInfoDto
     {
         /// <summary>
         /// Gets or sets the supported commands.
@@ -116,8 +115,6 @@ namespace MediaBrowser.Model.Session
 
         public TranscodingInfo TranscodingInfo { get; set; }
         
-        public event PropertyChangedEventHandler PropertyChanged;
-
         public SessionInfoDto()
         {
             AdditionalUsers = new List<SessionUserInfo>();

+ 1 - 0
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -893,6 +893,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 dto.LockData = item.IsLocked;
                 dto.ForcedSortName = item.ForcedSortName;
             }
+            dto.Container = item.Container;
 
             var hasBudget = item as IHasBudget;
             if (hasBudget != null)

+ 5 - 1
MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -331,7 +331,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 options.ContentType = MimeTypes.GetMimeType(path);
             }
 
-            options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
+            if (!options.DateLastModified.HasValue)
+            {
+                options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
+            }
+
             var cacheKey = path + options.DateLastModified.Value.Ticks;
 
             options.CacheKey = cacheKey.GetMD5();

+ 1 - 1
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -2144,7 +2144,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 {
                     if (query.User != null)
                     {
-                        query.SortBy = new[] { "SimilarityScore", ItemSortBy.IsPlayed, ItemSortBy.Random };
+                        query.SortBy = new[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random };
                     }
                     else
                     {

+ 9 - 6
MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs

@@ -247,15 +247,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
                 return;
             }
 
-            if (newIndex > oldIndex)
-            {
-                newIndex--;
-            }
-
             var item = playlist.LinkedChildren[oldIndex];
 
             playlist.LinkedChildren.Remove(item);
-            playlist.LinkedChildren.Insert(newIndex, item);
+
+            if (newIndex >= playlist.LinkedChildren.Count)
+            {
+                playlist.LinkedChildren.Add(item);
+            }
+            else
+            {
+                playlist.LinkedChildren.Insert(newIndex, item);
+            }
 
             await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
         }