diff options
26 files changed, 343 insertions, 435 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7bc7ba740..002ee8760 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,4 +16,27 @@ - [Logos302] (https://github.com/Logos302) - [TheWorkz](https://github.com/TheWorkz) - [mboehler](https://github.com/mboehler) + - [KaHooli](https://github.com/KaHooli) + - [xzener](https://github.com/xzener) + - [CBers] (https://github.com/CBers) + - [Sagaia] (https://github.com/Sagaia) + - [JHawk111](https://github.com/JHawk111) + - [David3663](https://github.com/david3663) + - [Smyken](https://github.com/Smyken) + - [doron1] (https://github.com/doron1) + - [brainfryd] (https://github.com/brainfryd) + - [DGMayor] (http://github.com/DGMayor) + - [Jon-theHTPC] (https://github.com/Jon-theHTPC) + - [aspdend] (https://github.com/aspdend) + - [RedshirtMB](https://github.com/RedshirtMB) + - [thealienamongus](https://github.com/thealienamongus) + - [brocass](https://github.com/brocass) + - [pjrollo2000](https://github.com/pjrollo2000) + - [abobader](https://github.com/abobader) + - [milli260876](https://github.com/milli260876) + - [vileboy] (https://github.com/vileboy) + - [starkadius] (https://github.com/starkadius) + - [wraslor](https://github.com/wraslor) + - [mrwebsmith](https://github.com/mrwebsmith) + - [rickster53](https://github.com/rickster53) - [Tharnax] (https://github.com/Tharnax) diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 54b2c0ea7..09e175e30 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -273,13 +273,6 @@ namespace MediaBrowser.Api song.Artist = request.Artists[0]; } - var musicAlbum = item as MusicAlbum; - - if (musicAlbum != null) - { - musicAlbum.MusicBrainzReleaseGroupId = request.GetProviderId("MusicBrainzReleaseGroupId"); - } - var musicVideo = item as MusicVideo; if (musicVideo != null) diff --git a/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs b/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs index 4196db497..1e990b3e9 100644 --- a/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs @@ -1,5 +1,7 @@ -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; using ServiceStack.ServiceHost; using ServiceStack.Text.Controller; using System; @@ -18,7 +20,7 @@ namespace MediaBrowser.Api.UserLibrary [Route("/Users/{UserId}/Favorites/MusicGenres/{Name}", "POST")] [Route("/Users/{UserId}/Favorites/GameGenres/{Name}", "POST")] [Api(Description = "Marks something as a favorite")] - public class MarkItemByNameFavorite : IReturnVoid + public class MarkItemByNameFavorite : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -45,7 +47,7 @@ namespace MediaBrowser.Api.UserLibrary [Route("/Users/{UserId}/Favorites/MusicGenres/{Name}", "DELETE")] [Route("/Users/{UserId}/Favorites/GameGenres/{Name}", "DELETE")] [Api(Description = "Unmarks something as a favorite")] - public class UnmarkItemByNameFavorite : IReturnVoid + public class UnmarkItemByNameFavorite : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -71,7 +73,7 @@ namespace MediaBrowser.Api.UserLibrary [Route("/Users/{UserId}/Ratings/Genres/{Name}", "POST")] [Route("/Users/{UserId}/Ratings/MusicGenres/{Name}", "POST")] [Api(Description = "Updates a user's rating for an item")] - public class UpdateItemByNameRating : IReturnVoid + public class UpdateItemByNameRating : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -105,7 +107,7 @@ namespace MediaBrowser.Api.UserLibrary [Route("/Users/{UserId}/Ratings/MusicGenres/{Name}", "DELETE")] [Route("/Users/{UserId}/Ratings/GameGenres/{Name}", "DELETE")] [Api(Description = "Deletes a user's saved personal rating for an item")] - public class DeleteItemByNameRating : IReturnVoid + public class DeleteItemByNameRating : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -152,56 +154,56 @@ namespace MediaBrowser.Api.UserLibrary /// Posts the specified request. /// </summary> /// <param name="request">The request.</param> - public void Post(MarkItemByNameFavorite request) + public object Post(MarkItemByNameFavorite request) { var pathInfo = PathInfo.Parse(RequestContext.PathInfo); var type = pathInfo.GetArgumentValue<string>(3); var task = MarkFavorite(request.UserId, type, request.Name, true); - Task.WaitAll(task); + return ToOptimizedResult(task.Result); } /// <summary> /// Posts the specified request. /// </summary> /// <param name="request">The request.</param> - public void Post(UpdateItemByNameRating request) + public object Post(UpdateItemByNameRating request) { var pathInfo = PathInfo.Parse(RequestContext.PathInfo); var type = pathInfo.GetArgumentValue<string>(3); var task = MarkLike(request.UserId, type, request.Name, request.Likes); - Task.WaitAll(task); + return ToOptimizedResult(task.Result); } /// <summary> /// Deletes the specified request. /// </summary> /// <param name="request">The request.</param> - public void Delete(UnmarkItemByNameFavorite request) + public object Delete(UnmarkItemByNameFavorite request) { var pathInfo = PathInfo.Parse(RequestContext.PathInfo); var type = pathInfo.GetArgumentValue<string>(3); var task = MarkFavorite(request.UserId, type, request.Name, false); - Task.WaitAll(task); + return ToOptimizedResult(task.Result); } /// <summary> /// Deletes the specified request. /// </summary> /// <param name="request">The request.</param> - public void Delete(DeleteItemByNameRating request) + public object Delete(DeleteItemByNameRating request) { var pathInfo = PathInfo.Parse(RequestContext.PathInfo); var type = pathInfo.GetArgumentValue<string>(3); var task = MarkLike(request.UserId, type, request.Name, null); - Task.WaitAll(task); + return ToOptimizedResult(task.Result); } /// <summary> @@ -212,7 +214,7 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="name">The name.</param> /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param> /// <returns>Task.</returns> - protected async Task MarkFavorite(Guid userId, string type, string name, bool isFavorite) + protected async Task<UserItemDataDto> MarkFavorite(Guid userId, string type, string name, bool isFavorite) { var item = await GetItemByName(name, type, LibraryManager).ConfigureAwait(false); @@ -225,6 +227,10 @@ namespace MediaBrowser.Api.UserLibrary data.IsFavorite = isFavorite; await UserDataRepository.SaveUserData(userId, key, data, CancellationToken.None).ConfigureAwait(false); + + data = UserDataRepository.GetUserData(userId, key); + + return DtoBuilder.GetUserItemDataDto(data); } /// <summary> @@ -235,7 +241,7 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="name">The name.</param> /// <param name="likes">if set to <c>true</c> [likes].</param> /// <returns>Task.</returns> - protected async Task MarkLike(Guid userId, string type, string name, bool? likes) + protected async Task<UserItemDataDto> MarkLike(Guid userId, string type, string name, bool? likes) { var item = await GetItemByName(name, type, LibraryManager).ConfigureAwait(false); @@ -247,6 +253,10 @@ namespace MediaBrowser.Api.UserLibrary data.Likes = likes; await UserDataRepository.SaveUserData(userId, key, data, CancellationToken.None).ConfigureAwait(false); + + data = UserDataRepository.GetUserData(userId, key); + + return DtoBuilder.GetUserItemDataDto(data); } } } diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 46254435f..304ff27d0 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> [Route("/Users/{UserId}/FavoriteItems/{Id}", "POST")] [Api(Description = "Marks an item as a favorite")] - public class MarkFavoriteItem : IReturnVoid + public class MarkFavoriteItem : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> [Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE")] [Api(Description = "Unmarks an item as a favorite")] - public class UnmarkFavoriteItem : IReturnVoid + public class UnmarkFavoriteItem : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -123,7 +123,7 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> [Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE")] [Api(Description = "Deletes a user's saved personal rating for an item")] - public class DeleteUserItemRating : IReturnVoid + public class DeleteUserItemRating : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -145,7 +145,7 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> [Route("/Users/{UserId}/Items/{Id}/Rating", "POST")] [Api(Description = "Updates a user's rating for an item")] - public class UpdateUserItemRating : IReturnVoid + public class UpdateUserItemRating : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -174,7 +174,7 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> [Route("/Users/{UserId}/PlayedItems/{Id}", "POST")] [Api(Description = "Marks an item as played")] - public class MarkPlayedItem : IReturnVoid + public class MarkPlayedItem : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -196,7 +196,7 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE")] [Api(Description = "Marks an item as unplayed")] - public class MarkUnplayedItem : IReturnVoid + public class MarkUnplayedItem : IReturn<UserItemDataDto> { /// <summary> /// Gets or sets the user id. @@ -372,7 +372,6 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="itemRepo">The item repo.</param> /// <exception cref="System.ArgumentNullException">jsonSerializer</exception> public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo, ISessionManager sessionManager) - : base() { _userManager = userManager; _libraryManager = libraryManager; @@ -388,6 +387,13 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>System.Object.</returns> public object Get(GetSpecialFeatures request) { + var result = GetAsync(request).Result; + + return ToOptimizedResult(result); + } + + private Task<BaseItemDto[]> GetAsync(GetSpecialFeatures request) + { var user = _userManager.GetUserById(request.UserId); var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); @@ -399,14 +405,12 @@ namespace MediaBrowser.Api.UserLibrary var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository, _itemRepo); - var items = movie.SpecialFeatureIds + var tasks = movie.SpecialFeatureIds .Select(_itemRepo.RetrieveItem) .OrderBy(i => i.SortName) - .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, movie)) - .Select(t => t.Result) - .ToList(); + .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, movie)); - return ToOptimizedResult(items); + return Task.WhenAll(tasks); } /// <summary> @@ -416,6 +420,13 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>System.Object.</returns> public object Get(GetLocalTrailers request) { + var result = GetAsync(request).Result; + + return ToOptimizedResult(result); + } + + private Task<BaseItemDto[]> GetAsync(GetLocalTrailers request) + { var user = _userManager.GetUserById(request.UserId); var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); @@ -425,14 +436,12 @@ namespace MediaBrowser.Api.UserLibrary var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository, _itemRepo); - var items = item.LocalTrailerIds + var tasks = item.LocalTrailerIds .Select(_itemRepo.RetrieveItem) .OrderBy(i => i.SortName) - .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, item)) - .Select(t => t.Result) - .ToList(); + .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, item)); - return ToOptimizedResult(items); + return Task.WhenAll(tasks); } /// <summary> @@ -497,34 +506,29 @@ namespace MediaBrowser.Api.UserLibrary /// Posts the specified request. /// </summary> /// <param name="request">The request.</param> - public void Post(MarkFavoriteItem request) + public object Post(MarkFavoriteItem request) { - var user = _userManager.GetUserById(request.UserId); + var dto = MarkFavorite(request.UserId, request.Id, true).Result; - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); - - // Get the user data for this item - var key = item.GetUserDataKey(); - - var data = _userDataRepository.GetUserData(user.Id, key); - - // Set favorite status - data.IsFavorite = true; - - var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None); - - Task.WaitAll(task); + return ToOptimizedResult(dto); } /// <summary> /// Deletes the specified request. /// </summary> /// <param name="request">The request.</param> - public void Delete(UnmarkFavoriteItem request) + public object Delete(UnmarkFavoriteItem request) { - var user = _userManager.GetUserById(request.UserId); + var dto = MarkFavorite(request.UserId, request.Id, false).Result; - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); + return ToOptimizedResult(dto); + } + + private async Task<UserItemDataDto> MarkFavorite(Guid userId, string itemId, bool isFavorite) + { + var user = _userManager.GetUserById(userId); + + var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id); var key = item.GetUserDataKey(); @@ -532,68 +536,68 @@ namespace MediaBrowser.Api.UserLibrary var data = _userDataRepository.GetUserData(user.Id, key); // Set favorite status - data.IsFavorite = false; + data.IsFavorite = isFavorite; - var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None); + await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false); - Task.WaitAll(task); + data = _userDataRepository.GetUserData(user.Id, key); + + return DtoBuilder.GetUserItemDataDto(data); } /// <summary> /// Deletes the specified request. /// </summary> /// <param name="request">The request.</param> - public void Delete(DeleteUserItemRating request) + public object Delete(DeleteUserItemRating request) { - var user = _userManager.GetUserById(request.UserId); - - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); - - var key = item.GetUserDataKey(); - - // Get the user data for this item - var data = _userDataRepository.GetUserData(user.Id, key); - - data.Rating = null; - - var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None); + var dto = UpdateUserItemRating(request.UserId, request.Id, null).Result; - Task.WaitAll(task); + return ToOptimizedResult(dto); } /// <summary> /// Posts the specified request. /// </summary> /// <param name="request">The request.</param> - public void Post(UpdateUserItemRating request) + public object Post(UpdateUserItemRating request) { - var user = _userManager.GetUserById(request.UserId); + var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes).Result; - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); + return ToOptimizedResult(dto); + } + + private async Task<UserItemDataDto> UpdateUserItemRating(Guid userId, string itemId, bool? likes) + { + var user = _userManager.GetUserById(userId); + + var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id); var key = item.GetUserDataKey(); // Get the user data for this item var data = _userDataRepository.GetUserData(user.Id, key); - data.Likes = request.Likes; + data.Likes = likes; - var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None); + await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false); - Task.WaitAll(task); - } + data = _userDataRepository.GetUserData(user.Id, key); + return DtoBuilder.GetUserItemDataDto(data); + } + /// <summary> /// Posts the specified request. /// </summary> /// <param name="request">The request.</param> - public void Post(MarkPlayedItem request) + public object Post(MarkPlayedItem request) { var user = _userManager.GetUserById(request.UserId); var task = UpdatePlayedStatus(user, request.Id, true); - Task.WaitAll(task); + return ToOptimizedResult(task.Result); } private SessionInfo GetSession() @@ -660,13 +664,13 @@ namespace MediaBrowser.Api.UserLibrary /// Deletes the specified request. /// </summary> /// <param name="request">The request.</param> - public void Delete(MarkUnplayedItem request) + public object Delete(MarkUnplayedItem request) { var user = _userManager.GetUserById(request.UserId); var task = UpdatePlayedStatus(user, request.Id, false); - Task.WaitAll(task); + return ToOptimizedResult(task.Result); } /// <summary> @@ -676,11 +680,13 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="itemId">The item id.</param> /// <param name="wasPlayed">if set to <c>true</c> [was played].</param> /// <returns>Task.</returns> - private Task UpdatePlayedStatus(User user, string itemId, bool wasPlayed) + private async Task<UserItemDataDto> UpdatePlayedStatus(User user, string itemId, bool wasPlayed) { var item = DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id); - return item.SetPlayedStatus(user, wasPlayed, _userDataRepository); + await item.SetPlayedStatus(user, wasPlayed, _userDataRepository).ConfigureAwait(false); + + return DtoBuilder.GetUserItemDataDto(_userDataRepository.GetUserData(user.Id, item.GetUserDataKey())); } } } diff --git a/MediaBrowser.Controller/Dto/DtoBuilder.cs b/MediaBrowser.Controller/Dto/DtoBuilder.cs index 85d2a2003..b2b858b6d 100644 --- a/MediaBrowser.Controller/Dto/DtoBuilder.cs +++ b/MediaBrowser.Controller/Dto/DtoBuilder.cs @@ -90,6 +90,11 @@ namespace MediaBrowser.Controller.Dto } } + if (fields.Contains(ItemFields.DisplayPreferencesId)) + { + dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N"); + } + if (user != null) { AttachUserSpecificInfo(dto, item, user, fields); @@ -272,11 +277,6 @@ namespace MediaBrowser.Controller.Dto /// <param name="fields">The fields.</param> private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields) { - if (item.IsFolder && fields.Contains(ItemFields.DisplayPreferencesId)) - { - dto.DisplayPreferencesId = ((Folder) item).DisplayPreferencesId.ToString("N"); - } - if (item.IsFolder) { var hasItemCounts = fields.Contains(ItemFields.ItemCounts); diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 1526cf46a..1668b91d4 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -91,11 +91,5 @@ namespace MediaBrowser.Controller.Entities.Audio { return RecursiveChildren.OfType<Audio>().Any(i => i.HasArtist(artist)); } - - /// <summary> - /// Gets or sets the music brainz release group id. - /// </summary> - /// <value>The music brainz release group id.</value> - public string MusicBrainzReleaseGroupId { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cc2e82b1f..a75dc47e1 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; @@ -84,6 +85,21 @@ namespace MediaBrowser.Controller.Entities public Guid Id { get; set; } /// <summary> + /// Return the id that should be used to key display prefs for this item. + /// Default is based on the type for everything except actual generic folders. + /// </summary> + /// <value>The display prefs id.</value> + [IgnoreDataMember] + public virtual Guid DisplayPreferencesId + { + get + { + var thisType = GetType(); + return thisType == typeof(Folder) ? Id : thisType.FullName.GetMD5(); + } + } + + /// <summary> /// Gets or sets the path. /// </summary> /// <value>The path.</value> @@ -393,7 +409,12 @@ namespace MediaBrowser.Controller.Entities { get { - return ForcedSortName ?? _sortName ?? (_sortName = CreateSortName()); + if (!string.IsNullOrEmpty(ForcedSortName)) + { + return ForcedSortName; + } + + return _sortName ?? (_sortName = CreateSortName()); } } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index ca43ca616..91f4504c2 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -16,6 +16,7 @@ using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; +using MoreLinq; namespace MediaBrowser.Controller.Entities { @@ -66,21 +67,6 @@ namespace MediaBrowser.Controller.Entities } } - /// <summary> - /// Return the id that should be used to key display prefs for this item. - /// Default is based on the type for everything except actual generic folders. - /// </summary> - /// <value>The display prefs id.</value> - [IgnoreDataMember] - public virtual Guid DisplayPreferencesId - { - get - { - var thisType = GetType(); - return thisType == typeof(Folder) ? Id : thisType.FullName.GetMD5(); - } - } - public virtual List<LinkedChild> LinkedChildren { get; set; } protected virtual bool SupportsShortcutChildren @@ -974,7 +960,31 @@ namespace MediaBrowser.Controller.Entities /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param> /// <returns>IEnumerable{BaseItem}.</returns> /// <exception cref="System.ArgumentNullException"></exception> - public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = false) + public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true) + { + if (user == null) + { + throw new ArgumentNullException(); + } + + var children = GetRecursiveChildrenInternal(user, includeLinkedChildren); + + if (includeLinkedChildren) + { + children = children.DistinctBy(i => i.Id); + } + + return children; + } + + /// <summary> + /// Gets allowed recursive children of an item + /// </summary> + /// <param name="user">The user.</param> + /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param> + /// <returns>IEnumerable{BaseItem}.</returns> + /// <exception cref="System.ArgumentNullException"></exception> + private IEnumerable<BaseItem> GetRecursiveChildrenInternal(User user, bool includeLinkedChildren) { if (user == null) { @@ -989,7 +999,7 @@ namespace MediaBrowser.Controller.Entities if (subFolder != null) { - foreach (var subitem in subFolder.GetRecursiveChildren(user, includeLinkedChildren)) + foreach (var subitem in subFolder.GetRecursiveChildrenInternal(user, includeLinkedChildren)) { yield return subitem; } diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs index c1ca5336b..dc2b49c75 100644 --- a/MediaBrowser.Controller/IO/FileData.cs +++ b/MediaBrowser.Controller/IO/FileData.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using MoreLinq; namespace MediaBrowser.Controller.IO { @@ -38,7 +39,10 @@ namespace MediaBrowser.Controller.IO if (!resolveShortcuts && flattenFolderDepth == 0) { - return entries.ToDictionary(i => i.FullName, StringComparer.OrdinalIgnoreCase); + // Seeing dupes on some users file system for some reason + return entries + .DistinctBy(i => i.FullName, StringComparer.OrdinalIgnoreCase) + .ToDictionary(i => i.FullName, StringComparer.OrdinalIgnoreCase); } var dict = new Dictionary<string, FileSystemInfo>(StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 2761c51e0..ba4f84ac2 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -173,8 +173,16 @@ namespace MediaBrowser.Controller.Providers break; } case "SortTitle": - item.ForcedSortName = reader.ReadElementContentAsString(); - break; + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + item.ForcedSortName = val; + } + + break; + } case "Overview": case "Description": @@ -503,7 +511,7 @@ namespace MediaBrowser.Controller.Providers { DateTime airDate; - if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850) + if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out airDate) && airDate.Year > 1850) { item.PremiereDate = airDate.ToUniversalTime(); item.ProductionYear = airDate.Year; @@ -522,7 +530,7 @@ namespace MediaBrowser.Controller.Providers { DateTime airDate; - if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850) + if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out airDate) && airDate.Year > 1850) { item.EndDate = airDate.ToUniversalTime(); } @@ -548,13 +556,23 @@ namespace MediaBrowser.Controller.Providers break; case "MusicbrainzId": - var mbz = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(mbz)) { - item.SetProviderId(MetadataProviders.Musicbrainz, mbz); + var mbz = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(mbz)) + { + item.SetProviderId(MetadataProviders.Musicbrainz, mbz); + } + break; + } + case "MusicBrainzReleaseGroupId": + { + var mbz = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(mbz)) + { + item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, mbz); + } + break; } - break; - case "RottenTomatoesId": var rtId = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(rtId)) diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index ecb13f83a..f919c2c83 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -437,7 +437,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="wasPlayed">if set to <c>true</c> [was played].</param> /// <returns>Task.</returns> /// <exception cref="ArgumentNullException">itemId</exception> - Task UpdatePlayedStatusAsync(string itemId, string userId, bool wasPlayed); + Task<UserItemDataDto> UpdatePlayedStatusAsync(string itemId, string userId, bool wasPlayed); /// <summary> /// Updates the favorite status async. @@ -447,7 +447,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param> /// <returns>Task.</returns> /// <exception cref="ArgumentNullException">itemId</exception> - Task UpdateFavoriteStatusAsync(string itemId, string userId, bool isFavorite); + Task<UserItemDataDto> UpdateFavoriteStatusAsync(string itemId, string userId, bool isFavorite); /// <summary> /// Reports to the server that the user has begun playing an item @@ -518,7 +518,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="userId">The user id.</param> /// <returns>Task{UserItemDataDto}.</returns> /// <exception cref="ArgumentNullException">itemId</exception> - Task ClearUserItemRatingAsync(string itemId, string userId); + Task<UserItemDataDto> ClearUserItemRatingAsync(string itemId, string userId); /// <summary> /// Updates a user's rating for an item, based on likes or dislikes @@ -528,7 +528,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="likes">if set to <c>true</c> [likes].</param> /// <returns>Task.</returns> /// <exception cref="ArgumentNullException">itemId</exception> - Task UpdateUserItemRatingAsync(string itemId, string userId, bool likes); + Task<UserItemDataDto> UpdateUserItemRatingAsync(string itemId, string userId, bool likes); /// <summary> /// Authenticates a user and returns the result diff --git a/MediaBrowser.Model/Entities/MetadataProviders.cs b/MediaBrowser.Model/Entities/MetadataProviders.cs index b089b668c..db784a35f 100644 --- a/MediaBrowser.Model/Entities/MetadataProviders.cs +++ b/MediaBrowser.Model/Entities/MetadataProviders.cs @@ -34,6 +34,7 @@ namespace MediaBrowser.Model.Entities /// <summary> /// Tmdb Collection Id /// </summary> - TmdbCollection + TmdbCollection, + MusicBrainzReleaseGroup } } diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index e80ba2b7d..01f532bae 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -181,7 +181,6 @@ namespace MediaBrowser.Providers.Movies private const string TmdbConfigUrl = "http://api.themoviedb.org/3/configuration?api_key={0}"; private const string Search3 = @"http://api.themoviedb.org/3/search/movie?api_key={1}&query={0}&language={2}"; - private const string AltTitleSearch = @"http://api.themoviedb.org/3/movie/{0}/alternative_titles?api_key={1}&country={2}"; private const string GetMovieInfo3 = @"http://api.themoviedb.org/3/movie/{0}?api_key={1}&language={2}&append_to_response=casts,releases,images,keywords,trailers"; private const string GetBoxSetInfo3 = @"http://api.themoviedb.org/3/collection/{0}?api_key={1}&language={2}&append_to_response=images"; @@ -248,8 +247,19 @@ namespace MediaBrowser.Providers.Movies /// <returns>Task.</returns> private async Task FetchMovieData(BaseItem item, CancellationToken cancellationToken) { - string id = item.GetProviderId(MetadataProviders.Tmdb) ?? await FindId(item, item.ProductionYear, cancellationToken).ConfigureAwait(false); - if (id != null) + var id = item.GetProviderId(MetadataProviders.Tmdb); + + if (string.IsNullOrEmpty(id)) + { + id = item.GetProviderId(MetadataProviders.Imdb); + } + + if (string.IsNullOrEmpty(id)) + { + id = await FindId(item, cancellationToken).ConfigureAwait(false); + } + + if (!string.IsNullOrEmpty(id)) { Logger.Debug("MovieDbProvider - getting movie info with id: " + id); @@ -291,19 +301,15 @@ namespace MediaBrowser.Providers.Movies /// Finds the id. /// </summary> /// <param name="item">The item.</param> - /// <param name="productionYear">The production year.</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>Task{System.String}.</returns> - public async Task<string> FindId(BaseItem item, int? productionYear, CancellationToken cancellationToken) + public async Task<string> FindId(BaseItem item, CancellationToken cancellationToken) { - int? year; + int? yearInName; string name = item.Name; - ParseName(name, out name, out year); + ParseName(name, out name, out yearInName); - if (year == null && productionYear != null) - { - year = productionYear; - } + var year = item.ProductionYear ?? yearInName; Logger.Info("MovieDbProvider: Finding id for movie: " + name); string language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower(); @@ -392,95 +398,12 @@ namespace MediaBrowser.Providers.Movies searchResult = JsonSerializer.DeserializeFromStream<TmdbMovieSearchResults>(json); } - if (searchResult == null || searchResult.results.Count == 0) - { - //try replacing numbers - foreach (var pair in ReplaceStartNumbers) - { - if (name.StartsWith(pair.Key)) - { - name = name.Remove(0, pair.Key.Length); - name = pair.Value + name; - } - } - foreach (var pair in ReplaceEndNumbers) - { - if (name.EndsWith(pair.Key)) - { - name = name.Remove(name.IndexOf(pair.Key), pair.Key.Length); - name = name + pair.Value; - } - } - Logger.Info("MovieDBProvider - No results. Trying replacement numbers: " + name); - url3 = string.Format(Search3, UrlEncode(name), ApiKey, language); - - using (var json = await GetMovieDbResponse(new HttpRequestOptions - { - Url = url3, - CancellationToken = cancellationToken, - AcceptHeader = AcceptHeader - - }).ConfigureAwait(false)) - { - searchResult = JsonSerializer.DeserializeFromStream<TmdbMovieSearchResults>(json); - } - } if (searchResult != null) { - string compName = GetComparableName(name, Logger); foreach (var possible in searchResult.results) { - string matchedName = null; + string matchedName = possible.title; string id = possible.id.ToString(CultureInfo.InvariantCulture); - string n = possible.title; - if (GetComparableName(n, Logger) == compName) - { - matchedName = n; - } - else - { - n = possible.original_title; - if (GetComparableName(n, Logger) == compName) - { - matchedName = n; - } - } - - Logger.Debug("MovieDbProvider - " + compName + " didn't match " + n); - //if main title matches we don't have to look for alternatives - if (matchedName == null) - { - //that title didn't match - look for alternatives - url3 = string.Format(AltTitleSearch, id, ApiKey, ConfigurationManager.Configuration.MetadataCountryCode); - - using (var json = await GetMovieDbResponse(new HttpRequestOptions - { - Url = url3, - CancellationToken = cancellationToken, - AcceptHeader = AcceptHeader - - }).ConfigureAwait(false)) - { - var response = JsonSerializer.DeserializeFromStream<TmdbAltTitleResults>(json); - - if (response != null && response.titles != null) - { - foreach (var title in response.titles) - { - var t = GetComparableName(title.title, Logger); - if (t == compName) - { - Logger.Debug("MovieDbProvider - " + compName + - " matched " + t); - matchedName = t; - break; - } - Logger.Debug("MovieDbProvider - " + compName + - " did not match " + t); - } - } - } - } if (matchedName != null) { @@ -850,126 +773,6 @@ namespace MediaBrowser.Providers.Movies } } - /// <summary> - /// The remove - /// </summary> - const string Remove = "\"'!`?"; - // "Face/Off" support. - /// <summary> - /// The spacers - /// </summary> - const string Spacers = "/,.:;\\(){}[]+-_=–*"; // (there are not actually two - in the they are different char codes) - /// <summary> - /// The replace start numbers - /// </summary> - static readonly Dictionary<string, string> ReplaceStartNumbers = new Dictionary<string, string> { - {"1 ","one "}, - {"2 ","two "}, - {"3 ","three "}, - {"4 ","four "}, - {"5 ","five "}, - {"6 ","six "}, - {"7 ","seven "}, - {"8 ","eight "}, - {"9 ","nine "}, - {"10 ","ten "}, - {"11 ","eleven "}, - {"12 ","twelve "}, - {"13 ","thirteen "}, - {"100 ","one hundred "}, - {"101 ","one hundred one "} - }; - - /// <summary> - /// The replace end numbers - /// </summary> - static readonly Dictionary<string, string> ReplaceEndNumbers = new Dictionary<string, string> { - {" 1"," i"}, - {" 2"," ii"}, - {" 3"," iii"}, - {" 4"," iv"}, - {" 5"," v"}, - {" 6"," vi"}, - {" 7"," vii"}, - {" 8"," viii"}, - {" 9"," ix"}, - {" 10"," x"} - }; - - /// <summary> - /// Gets the name of the comparable. - /// </summary> - /// <param name="name">The name.</param> - /// <param name="logger">The logger.</param> - /// <returns>System.String.</returns> - internal static string GetComparableName(string name, ILogger logger) - { - name = name.ToLower(); - name = name.Replace("á", "a"); - name = name.Replace("é", "e"); - name = name.Replace("í", "i"); - name = name.Replace("ó", "o"); - name = name.Replace("ú", "u"); - name = name.Replace("ü", "u"); - name = name.Replace("ñ", "n"); - foreach (var pair in ReplaceStartNumbers) - { - if (name.StartsWith(pair.Key)) - { - name = name.Remove(0, pair.Key.Length); - name = pair.Value + name; - logger.Info("MovieDbProvider - Replaced Start Numbers: " + name); - } - } - foreach (var pair in ReplaceEndNumbers) - { - if (name.EndsWith(pair.Key)) - { - name = name.Remove(name.IndexOf(pair.Key), pair.Key.Length); - name = name + pair.Value; - logger.Info("MovieDbProvider - Replaced End Numbers: " + name); - } - } - name = name.Normalize(NormalizationForm.FormKD); - var sb = new StringBuilder(); - foreach (var c in name) - { - if (c >= 0x2B0 && c <= 0x0333) - { - // skip char modifier and diacritics - } - else if (Remove.IndexOf(c) > -1) - { - // skip chars we are removing - } - else if (Spacers.IndexOf(c) > -1) - { - sb.Append(" "); - } - else if (c == '&') - { - sb.Append(" and "); - } - else - { - sb.Append(c); - } - } - name = sb.ToString(); - name = name.Replace(", the", ""); - name = name.Replace(" the ", " "); - name = name.Replace("the ", ""); - - string prev_name; - do - { - prev_name = name; - name = name.Replace(" ", " "); - } while (name.Length != prev_name.Length); - - return name.Trim(); - } - #region Result Objects diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index b02128a37..fa7daaf25 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -182,10 +182,13 @@ namespace MediaBrowser.Providers.Music var releaseEntryId = item.GetProviderId(MetadataProviders.Musicbrainz); + var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); // Fanart uses the release group id so we'll have to get that now using the release entry id - if (string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId)) + if (string.IsNullOrEmpty(musicBrainzReleaseGroupId)) { - album.MusicBrainzReleaseGroupId = await GetReleaseGroupId(releaseEntryId, cancellationToken).ConfigureAwait(false); + musicBrainzReleaseGroupId = await GetReleaseGroupId(releaseEntryId, cancellationToken).ConfigureAwait(false); + + album.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, musicBrainzReleaseGroupId); } var doc = new XmlDocument(); @@ -199,9 +202,9 @@ namespace MediaBrowser.Providers.Music // Try try with the release entry Id, if that doesn't produce anything try the release group id var node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + releaseEntryId + "\"]/cdart/@url"); - if (node == null && !string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId)) + if (node == null && !string.IsNullOrEmpty(musicBrainzReleaseGroupId)) { - node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + album.MusicBrainzReleaseGroupId + "\"]/cdart/@url"); + node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + musicBrainzReleaseGroupId + "\"]/cdart/@url"); } var path = node != null ? node.Value : null; @@ -218,9 +221,9 @@ namespace MediaBrowser.Providers.Music // Try try with the release entry Id, if that doesn't produce anything try the release group id var node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + releaseEntryId + "\"]/albumcover/@url"); - if (node == null && !string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId)) + if (node == null && !string.IsNullOrEmpty(musicBrainzReleaseGroupId)) { - node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + album.MusicBrainzReleaseGroupId + "\"]/albumcover/@url"); + node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + musicBrainzReleaseGroupId + "\"]/albumcover/@url"); } var path = node != null ? node.Value : null; diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index 5c9e90312..edeea611a 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -65,6 +65,7 @@ namespace MediaBrowser.Providers.Savers "IMDbId", "TMDbId", "TVcomId", + "TvDbId", "RottenTomatoesId", "MusicbrainzId", "TMDbCollectionId", @@ -81,7 +82,8 @@ namespace MediaBrowser.Providers.Savers "BirthDate", "DeathDate", "LockedFields", - "Chapters" + "Chapters", + "MusicBrainzReleaseGroupId" }); var position = xml.ToString().LastIndexOf("</", StringComparison.OrdinalIgnoreCase); @@ -332,6 +334,13 @@ namespace MediaBrowser.Providers.Savers builder.Append("<MusicbrainzId>" + SecurityElement.Escape(mbz) + "</MusicbrainzId>"); } + mbz = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); + + if (!string.IsNullOrEmpty(mbz)) + { + builder.Append("<MusicBrainzReleaseGroupId>" + SecurityElement.Escape(mbz) + "</MusicBrainzReleaseGroupId>"); + } + var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb); if (!string.IsNullOrEmpty(gamesdb)) diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs index e98a0a2c5..cb6097504 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications private readonly INotificationsRepository _notificationsRepo; private readonly IUserManager _userManager; - private readonly TimeSpan _frequency = TimeSpan.FromHours(3); + private readonly TimeSpan _frequency = TimeSpan.FromHours(6); private readonly TimeSpan _maxAge = TimeSpan.FromDays(31); public RemoteNotifications(IApplicationPaths appPaths, ILogger logger, IHttpClient httpClient, IJsonSerializer json, INotificationsRepository notificationsRepo, IUserManager userManager) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index d408e082a..4991db8e2 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -230,8 +230,6 @@ <EmbeddedResource Include="MediaEncoder\readme.txt" /> </ItemGroup> <ItemGroup> - <EmbeddedResource Include="MediaEncoder\fonts\ARIALUNI.TTF" /> - <EmbeddedResource Include="MediaEncoder\fonts\fonts.conf" /> <EmbeddedResource Include="MediaEncoder\ffmpeg20130813.zip" /> <None Include="packages.config" /> </ItemGroup> diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index f5b5ea6c0..2ce49aabb 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; +using MediaBrowser.Common.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -47,6 +48,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// <value>The json serializer.</value> private readonly IJsonSerializer _jsonSerializer; + private readonly IHttpClient _httpClient; + /// <summary> /// The video image resource pool /// </summary> @@ -81,12 +84,13 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// <param name="appPaths">The app paths.</param> /// <param name="jsonSerializer">The json serializer.</param> public MediaEncoder(ILogger logger, IZipClient zipClient, IApplicationPaths appPaths, - IJsonSerializer jsonSerializer) + IJsonSerializer jsonSerializer, IHttpClient httpClient) { _logger = logger; _zipClient = zipClient; _appPaths = appPaths; _jsonSerializer = jsonSerializer; + _httpClient = httpClient; // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | @@ -194,22 +198,30 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// <param name="assembly">The assembly.</param> /// <param name="zipFileResourcePath">The zip file resource path.</param> /// <param name="targetPath">The target path.</param> - private void ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath) + private async void ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath) { using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath)) { _zipClient.ExtractAll(resourceStream, targetPath, false); } - ExtractFonts(assembly, targetPath); + try + { + await DownloadFonts(targetPath).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting ffmpeg font files", ex); + } } + private const string FontUrl = "https://www.dropbox.com/s/9nb76tybcsw5xrk/ARIALUNI.zip?dl=1"; + /// <summary> /// Extracts the fonts. /// </summary> - /// <param name="assembly">The assembly.</param> /// <param name="targetPath">The target path.</param> - private async void ExtractFonts(Assembly assembly, string targetPath) + private async Task DownloadFonts(string targetPath) { var fontsDirectory = Path.Combine(targetPath, "fonts"); @@ -224,54 +236,44 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder if (!File.Exists(fontFile)) { - using (var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontFilename)) + var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions { - using ( - var fileStream = new FileStream(fontFile, FileMode.Create, FileAccess.Write, FileShare.Read, - StreamDefaults.DefaultFileStreamBufferSize, - FileOptions.Asynchronous)) - { - await stream.CopyToAsync(fileStream).ConfigureAwait(false); - } + Url = FontUrl, + Progress = new Progress<double>() + }); + + _zipClient.ExtractAll(tempFile, fontsDirectory, true); + + try + { + File.Delete(tempFile); + } + catch (IOException ex) + { + // Log this, but don't let it fail the operation + _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); } } - await ExtractFontConfigFile(assembly, fontsDirectory).ConfigureAwait(false); + await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); } - /// <summary> - /// Extracts the font config file. - /// </summary> - /// <param name="assembly">The assembly.</param> - /// <param name="fontsDirectory">The fonts directory.</param> - /// <returns>Task.</returns> - private async Task ExtractFontConfigFile(Assembly assembly, string fontsDirectory) + private async Task WriteFontConfigFile(string fontsDirectory) { const string fontConfigFilename = "fonts.conf"; var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); if (!File.Exists(fontConfigFile)) { - using ( - var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontConfigFilename) - ) - { - using (var streamReader = new StreamReader(stream)) - { - var contents = await streamReader.ReadToEndAsync().ConfigureAwait(false); + var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory); - contents = contents.Replace("<dir></dir>", "<dir>" + fontsDirectory + "</dir>"); + var bytes = Encoding.UTF8.GetBytes(contents); - var bytes = Encoding.UTF8.GetBytes(contents); - - using ( - var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write, - FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, - FileOptions.Asynchronous)) - { - await fileStream.WriteAsync(bytes, 0, bytes.Length); - } - } + using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write, + FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, + FileOptions.Asynchronous)) + { + await fileStream.WriteAsync(bytes, 0, bytes.Length); } } } diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf b/MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf deleted file mode 100644 index 648bdb7b2..000000000 --- a/MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0"?> -<fontconfig> - -<dir></dir> - <alias> - <family>Arial</family> - <prefer>Arial Unicode MS</prefer> - </alias> -</fontconfig>
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 099b9a3df..eb8a73049 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -272,7 +272,7 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance<ILibrarySearchEngine>(() => new LuceneSearchEngine(ApplicationPaths, LogManager, LibraryManager)); - MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ZipClient, ApplicationPaths, JsonSerializer); + MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ZipClient, ApplicationPaths, JsonSerializer, HttpClient); RegisterSingleInstance(MediaEncoder); var clientConnectionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository); diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index a0d4b399d..8a7db8b76 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -2695,7 +2695,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: method, - url: url + url: url, + dataType: "json" }); }; @@ -2721,7 +2722,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: method, - url: url + url: url, + dataType: "json" }); }; @@ -2747,7 +2749,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "POST", - url: url + url: url, + dataType: "json" }); }; @@ -2773,7 +2776,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: method, - url: url + url: url, + dataType: "json" }); }; @@ -2793,7 +2797,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: method, - url: url + url: url, + dataType: "json" }); }; @@ -2813,7 +2818,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: method, - url: url + url: url, + dataType: "json" }); }; @@ -2833,7 +2839,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: method, - url: url + url: url, + dataType: "json" }); }; @@ -2853,7 +2860,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: method, - url: url + url: url, + dataType: "json" }); }; @@ -2873,7 +2881,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: method, - url: url + url: url, + dataType: "json" }); }; @@ -2899,7 +2908,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "POST", - url: url + url: url, + dataType: "json" }); }; @@ -2919,7 +2929,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "POST", - url: url + url: url, + dataType: "json" }); }; @@ -2939,7 +2950,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "POST", - url: url + url: url, + dataType: "json" }); }; @@ -2959,7 +2971,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "POST", - url: url + url: url, + dataType: "json" }); }; @@ -2979,7 +2992,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "POST", - url: url + url: url, + dataType: "json" }); }; @@ -2999,7 +3013,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "POST", - url: url + url: url, + dataType: "json" }); }; @@ -3022,7 +3037,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "DELETE", - url: url + url: url, + dataType: "json" }); }; @@ -3040,7 +3056,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "DELETE", - url: url + url: url, + dataType: "json" }); }; @@ -3058,7 +3075,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "DELETE", - url: url + url: url, + dataType: "json" }); }; @@ -3076,7 +3094,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "DELETE", - url: url + url: url, + dataType: "json" }); }; @@ -3094,7 +3113,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "DELETE", - url: url + url: url, + dataType: "json" }); }; @@ -3112,7 +3132,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "DELETE", - url: url + url: url, + dataType: "json" }); }; @@ -3290,7 +3311,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { return self.ajax({ type: "DELETE", - url: url + url: url, + dataType: "json" }); }; diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 517b8c1d2..11c76e205 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.160" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.164" targetFramework="net45" /> <package id="ServiceStack.Common" version="3.9.58" targetFramework="net45" /> <package id="ServiceStack.Text" version="3.9.58" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index f1c8251b2..8bfd28da1 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.183</version> + <version>3.0.184</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.183" /> + <dependency id="MediaBrowser.Common" version="3.0.184" /> <dependency id="NLog" version="2.0.1.2" /> <dependency id="ServiceStack.Text" version="3.9.55" /> <dependency id="SimpleInjector" version="2.3.0" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 0f334c9c1..2ec4ebd58 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.183</version> + <version>3.0.184</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index bcf772e8f..14a401282 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.183</version> + <version>3.0.184</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.183" /> + <dependency id="MediaBrowser.Common" version="3.0.184" /> </dependencies> </metadata> <files> @@ -1,7 +1,7 @@ Media Browser ============ -Media Browser Server is a media cataloging and transcoding server built on top of other popular open source technologies such as **Service Stack**, **jQuery** and **jQuery mobile**. +Media Browser Server is a home media server built on top of other popular open source technologies such as **Service Stack**, **jQuery**, **jQuery mobile** and **Lucene .NET**. It features a REST-based api with built-in documention to facilitate plugin development. |
