diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs index c2ccf4dcdd..f1338c44b2 100644 --- a/MediaBrowser.Api/LibraryService.cs +++ b/MediaBrowser.Api/LibraryService.cs @@ -431,7 +431,7 @@ namespace MediaBrowser.Api var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository); - var items = _itemRepo.GetItems(item.ThemeSongIds) + var items = _itemRepo.RetrieveItems<Audio>(item.ThemeSongIds) .OrderBy(i => i.SortName) .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)) .Select(t => t.Result) @@ -471,7 +471,7 @@ namespace MediaBrowser.Api var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository); var items = - _itemRepo.GetItems(item.ThemeVideoIds) + _itemRepo.RetrieveItems<Video>(item.ThemeVideoIds) .OrderBy(i => i.SortName) .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)) .Select(t => t.Result) diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 04a18e40ed..09bdc05479 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -70,7 +70,7 @@ namespace MediaBrowser.Api public class GetSimilarShows : BaseGetSimilarItems { } - + /// <summary> /// Class TvShowsService /// </summary> @@ -110,9 +110,9 @@ namespace MediaBrowser.Api /// <returns>System.Object.</returns> public object Get(GetSimilarShows request) { - var result = SimilarItemsHelper.GetSimilarItems(_userManager, - _libraryManager, - _userDataRepository, + var result = SimilarItemsHelper.GetSimilarItems(_userManager, + _libraryManager, + _userDataRepository, Logger, request, item => item is Series, SimilarItemsHelper.GetSimiliarityScore); @@ -141,20 +141,19 @@ namespace MediaBrowser.Api { var user = _userManager.GetUserById(request.UserId); - var tasks = user.RootFolder + var itemsArray = user.RootFolder .GetRecursiveChildren(user) .OfType<Series>() .AsParallel() - .Select(i => GetNextUp(i, user)); - - var itemsArray = await Task.WhenAll(tasks).ConfigureAwait(false); + .Select(i => GetNextUp(i, user)) + .ToArray(); itemsArray = itemsArray .Where(i => i.Item1 != null) .OrderByDescending(i => { var seriesUserData = - _userDataRepository.GetUserData(user.Id, i.Item1.Series.GetUserDataKey()).Result; + _userDataRepository.GetUserData(user.Id, i.Item1.Series.GetUserDataKey()); if (seriesUserData.IsFavorite) { @@ -190,7 +189,7 @@ namespace MediaBrowser.Api /// <param name="series">The series.</param> /// <param name="user">The user.</param> /// <returns>Task{Episode}.</returns> - private async Task<Tuple<Episode,DateTime>> GetNextUp(Series series, User user) + private Tuple<Episode, DateTime> GetNextUp(Series series, User user) { var allEpisodes = series.GetRecursiveChildren(user) .OfType<Episode>() @@ -205,7 +204,7 @@ namespace MediaBrowser.Api // Go back starting with the most recent episodes foreach (var episode in allEpisodes) { - var userData = await _userDataRepository.GetUserData(user.Id, episode.GetUserDataKey()).ConfigureAwait(false); + var userData = _userDataRepository.GetUserData(user.Id, episode.GetUserDataKey()); if (userData.Played) { diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 26b0aa1921..65ec74bcf5 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -337,7 +337,7 @@ namespace MediaBrowser.Api.UserLibrary public string Name; public BaseItem Item; - private Task<UserItemData> _userData; + private UserItemData _userData; public List<BaseItem> Items { @@ -353,12 +353,7 @@ namespace MediaBrowser.Api.UserLibrary { var item = await GetItem().ConfigureAwait(false); - if (_userData == null) - { - _userData = repo.GetUserData(userId, item.GetUserDataKey()); - } - - return await _userData.ConfigureAwait(false); + return _userData ?? (_userData = repo.GetUserData(userId, item.GetUserDataKey())); } public IbnStub(string name, Func<IEnumerable<BaseItem>> childItems, Func<string,Task<T>> item) diff --git a/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs b/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs index 42b76e29d9..eaa65dc2db 100644 --- a/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs @@ -240,9 +240,9 @@ namespace MediaBrowser.Api.UserLibrary } var key = item.GetUserDataKey(); - + // Get the user data for this item - var data = await UserDataRepository.GetUserData(userId, key).ConfigureAwait(false); + var data = UserDataRepository.GetUserData(userId, key); // Set favorite status data.IsFavorite = isFavorite; @@ -288,9 +288,9 @@ namespace MediaBrowser.Api.UserLibrary } var key = item.GetUserDataKey(); - + // Get the user data for this item - var data = await UserDataRepository.GetUserData(userId, key).ConfigureAwait(false); + var data = UserDataRepository.GetUserData(userId, key); data.Likes = likes; diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index a06ac68b72..a4ed0396ef 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -335,7 +335,7 @@ namespace MediaBrowser.Api.UserLibrary case ItemFilter.Likes: return items.Where(item => { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result; + var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value; }); @@ -343,7 +343,7 @@ namespace MediaBrowser.Api.UserLibrary case ItemFilter.Dislikes: return items.Where(item => { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result; + var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value; }); @@ -351,7 +351,7 @@ namespace MediaBrowser.Api.UserLibrary case ItemFilter.IsFavorite: return items.Where(item => { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result; + var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); return userdata != null && userdata.IsFavorite; }); @@ -362,7 +362,7 @@ namespace MediaBrowser.Api.UserLibrary case ItemFilter.IsResumable: return items.Where(item => { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result; + var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); return userdata != null && userdata.PlaybackPositionTicks > 0; }); @@ -370,7 +370,7 @@ namespace MediaBrowser.Api.UserLibrary case ItemFilter.IsPlayed: return items.Where(item => { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result; + var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); return userdata != null && userdata.Played; }); @@ -378,7 +378,7 @@ namespace MediaBrowser.Api.UserLibrary case ItemFilter.IsUnplayed: return items.Where(item => { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result; + var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); return userdata == null || !userdata.Played; }); diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 8c1f3b500b..786eea5b31 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -399,7 +399,7 @@ namespace MediaBrowser.Api.UserLibrary var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository); - var items = _itemRepo.GetItems(movie.SpecialFeatureIds).OrderBy(i => i.SortName).Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)).Select(t => t.Result).ToList(); + var items = _itemRepo.RetrieveItems<Video>(movie.SpecialFeatureIds).OrderBy(i => i.SortName).Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)).Select(t => t.Result).ToList(); return ToOptimizedResult(items); } @@ -420,7 +420,7 @@ namespace MediaBrowser.Api.UserLibrary var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository); - var items = _itemRepo.GetItems(item.LocalTrailerIds).OrderBy(i => i.SortName).Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)).Select(t => t.Result).ToList(); + var items = _itemRepo.RetrieveItems<Trailer>(item.LocalTrailerIds).OrderBy(i => i.SortName).Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)).Select(t => t.Result).ToList(); return ToOptimizedResult(items); } @@ -496,7 +496,7 @@ namespace MediaBrowser.Api.UserLibrary // Get the user data for this item var key = item.GetUserDataKey(); - var data = _userDataRepository.GetUserData(user.Id, key).Result; + var data = _userDataRepository.GetUserData(user.Id, key); // Set favorite status data.IsFavorite = true; @@ -519,7 +519,7 @@ namespace MediaBrowser.Api.UserLibrary var key = item.GetUserDataKey(); // Get the user data for this item - var data = _userDataRepository.GetUserData(user.Id, key).Result; + var data = _userDataRepository.GetUserData(user.Id, key); // Set favorite status data.IsFavorite = false; @@ -542,7 +542,7 @@ namespace MediaBrowser.Api.UserLibrary var key = item.GetUserDataKey(); // Get the user data for this item - var data = _userDataRepository.GetUserData(user.Id, key).Result; + var data = _userDataRepository.GetUserData(user.Id, key); data.Rating = null; @@ -564,7 +564,7 @@ namespace MediaBrowser.Api.UserLibrary var key = item.GetUserDataKey(); // Get the user data for this item - var data = _userDataRepository.GetUserData(user.Id, key).Result; + var data = _userDataRepository.GetUserData(user.Id, key); data.Likes = request.Likes; diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index d2b58dc968..3c74a42889 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -64,7 +64,7 @@ namespace MediaBrowser.Api var video = (Video)item; - var items = _itemRepo.GetItems(video.AdditionalPartIds) + var items = _itemRepo.RetrieveItems<Video>(video.AdditionalPartIds) .OrderBy(i => i.SortName) .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)) .Select(t => t.Result) diff --git a/MediaBrowser.Controller/Dto/DtoBuilder.cs b/MediaBrowser.Controller/Dto/DtoBuilder.cs index 7fd188acb5..1e0e5286fa 100644 --- a/MediaBrowser.Controller/Dto/DtoBuilder.cs +++ b/MediaBrowser.Controller/Dto/DtoBuilder.cs @@ -73,11 +73,6 @@ namespace MediaBrowser.Controller.Dto tasks.Add(AttachPeople(dto, item)); } - if (user != null) - { - tasks.Add(AttachUserSpecificInfo(dto, item, user, fields)); - } - if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) { try @@ -91,6 +86,11 @@ namespace MediaBrowser.Controller.Dto } } + if (user != null) + { + AttachUserSpecificInfo(dto, item, user, fields); + } + AttachBasicFields(dto, item, fields); // Make sure all the tasks we kicked off have completed. @@ -109,7 +109,7 @@ namespace MediaBrowser.Controller.Dto /// <param name="item">The item.</param> /// <param name="user">The user.</param> /// <param name="fields">The fields.</param> - private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields) + private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields) { if (item.IsFolder && fields.Contains(ItemFields.DisplayPreferencesId)) { @@ -127,13 +127,13 @@ namespace MediaBrowser.Controller.Dto // Skip sorting since all we want is a count dto.ChildCount = folder.GetChildren(user).Count(); - await SetSpecialCounts(folder, user, dto, _userDataRepository).ConfigureAwait(false); + SetSpecialCounts(folder, user, dto, _userDataRepository); } } if (addUserData) { - var userData = await _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()).ConfigureAwait(false); + var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()); dto.UserData = GetUserItemDataDto(userData); @@ -529,7 +529,7 @@ namespace MediaBrowser.Controller.Dto /// <param name="dto">The dto.</param> /// <param name="userDataRepository">The user data repository.</param> /// <returns>Task.</returns> - private static async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, IUserDataRepository userDataRepository) + private static void SetSpecialCounts(Folder folder, User user, BaseItemDto dto, IUserDataRepository userDataRepository) { var rcentlyAddedItemCount = 0; var recursiveItemCount = 0; @@ -540,7 +540,7 @@ namespace MediaBrowser.Controller.Dto // Loop through each recursive child foreach (var child in folder.GetRecursiveChildren(user).Where(i => !i.IsFolder).ToList()) { - var userdata = await userDataRepository.GetUserData(user.Id, child.GetUserDataKey()).ConfigureAwait(false); + var userdata = userDataRepository.GetUserData(user.Id, child.GetUserDataKey()); recursiveItemCount++; @@ -767,7 +767,7 @@ namespace MediaBrowser.Controller.Dto { if (data == null) { - throw new ArgumentNullException(); + throw new ArgumentNullException("data"); } return new UserItemDataDto diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 6ae465aa7e..2850d7092a 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -273,7 +273,7 @@ namespace MediaBrowser.Controller.Entities { return Guid.Empty; } - + try { if (!ResolveArgs.IsDirectory) @@ -681,11 +681,6 @@ namespace MediaBrowser.Controller.Entities /// <returns>List{Video}.</returns> private IEnumerable<Trailer> LoadLocalTrailers() { - if (LocationType != LocationType.FileSystem) - { - return new List<Trailer>(); - } - ItemResolveArgs resolveArgs; try @@ -737,7 +732,7 @@ namespace MediaBrowser.Controller.Entities return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = LibraryManager.RetrieveItem(video.Id) as Trailer; + var dbItem = LibraryManager.RetrieveItem(video.Id, typeof(Trailer)) as Trailer; if (dbItem != null) { @@ -756,11 +751,6 @@ namespace MediaBrowser.Controller.Entities /// <returns>List{Audio.Audio}.</returns> private IEnumerable<Audio.Audio> LoadThemeSongs() { - if (LocationType != LocationType.FileSystem) - { - return new List<Audio.Audio>(); - } - ItemResolveArgs resolveArgs; try @@ -803,7 +793,7 @@ namespace MediaBrowser.Controller.Entities return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio => { // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = LibraryManager.RetrieveItem(audio.Id) as Audio.Audio; + var dbItem = LibraryManager.RetrieveItem(audio.Id, typeof(Audio.Audio)) as Audio.Audio; if (dbItem != null) { @@ -821,11 +811,6 @@ namespace MediaBrowser.Controller.Entities /// <returns>List{Video}.</returns> private IEnumerable<Video> LoadThemeVideos() { - if (LocationType != LocationType.FileSystem) - { - return new List<Video>(); - } - ItemResolveArgs resolveArgs; try @@ -866,7 +851,7 @@ namespace MediaBrowser.Controller.Entities return LibraryManager.ResolvePaths<Video>(files, null).Select(item => { // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = LibraryManager.RetrieveItem(item.Id) as Video; + var dbItem = LibraryManager.RetrieveItem(item.Id, typeof(Video)) as Video; if (dbItem != null) { @@ -896,13 +881,20 @@ namespace MediaBrowser.Controller.Entities cancellationToken.ThrowIfCancellationRequested(); - var themeSongsChanged = await RefreshThemeSongs(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + var themeSongsChanged = false; - var themeVideosChanged = await RefreshThemeVideos(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + var themeVideosChanged = false; - var localTrailersChanged = await RefreshLocalTrailers(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + var localTrailersChanged = false; - cancellationToken.ThrowIfCancellationRequested(); + if (LocationType == LocationType.FileSystem && Parent != null) + { + themeSongsChanged = await RefreshThemeSongs(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + + themeVideosChanged = await RefreshThemeVideos(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + + localTrailersChanged = await RefreshLocalTrailers(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + } cancellationToken.ThrowIfCancellationRequested(); @@ -1096,8 +1088,7 @@ namespace MediaBrowser.Controller.Entities parent = parent.Parent; } - //not found - load from repo - return LibraryManager.RetrieveItem(id); + return null; } /// <summary> @@ -1315,7 +1306,7 @@ namespace MediaBrowser.Controller.Entities var key = GetUserDataKey(); - var data = await userManager.GetUserData(user.Id, key).ConfigureAwait(false); + var data = userManager.GetUserData(user.Id, key); if (wasPlayed) { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index ce36366b43..de965221b8 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Reflection; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using System; @@ -21,6 +22,15 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Folder : BaseItem { + private static TypeMapper _typeMapper = new TypeMapper(); + + public Folder() + { + ChildDefinitions = new ConcurrentDictionary<Guid, string>(); + } + + public ConcurrentDictionary<Guid, string> ChildDefinitions { get; set; } + /// <summary> /// Gets a value indicating whether this instance is folder. /// </summary> @@ -108,16 +118,14 @@ namespace MediaBrowser.Controller.Entities item.DateModified = DateTime.Now; } - if (!_children.TryAdd(item.Id, item)) + if (!_children.TryAdd(item.Id, item) || !ChildDefinitions.TryAdd(item.Id, item.GetType().FullName)) { throw new InvalidOperationException("Unable to add " + item.Name); } - var newChildren = Children.ToList(); - await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false); - await LibraryManager.SaveChildren(Id, newChildren, cancellationToken).ConfigureAwait(false); + await LibraryManager.UpdateItem(this, cancellationToken).ConfigureAwait(false); } /// <summary> @@ -145,19 +153,18 @@ namespace MediaBrowser.Controller.Entities public Task RemoveChild(BaseItem item, CancellationToken cancellationToken) { BaseItem removed; + string removedType; - if (!_children.TryRemove(item.Id, out removed)) + if (!_children.TryRemove(item.Id, out removed) || !ChildDefinitions.TryRemove(item.Id, out removedType)) { throw new InvalidOperationException("Unable to remove " + item.Name); } item.Parent = null; - var newChildren = Children.ToList(); - LibraryManager.ReportItemRemoved(item); - return LibraryManager.SaveChildren(Id, newChildren, cancellationToken); + return LibraryManager.UpdateItem(this, cancellationToken); } #region Indexing @@ -652,7 +659,7 @@ namespace MediaBrowser.Controller.Entities var options = new ParallelOptions { - MaxDegreeOfParallelism = 50 + MaxDegreeOfParallelism = 20 }; Parallel.ForEach(nonCachedChildren, options, child => @@ -702,6 +709,9 @@ namespace MediaBrowser.Controller.Entities } else { + string removedType; + ChildDefinitions.TryRemove(item.Id, out removedType); + LibraryManager.ReportItemRemoved(item); } } @@ -716,11 +726,13 @@ namespace MediaBrowser.Controller.Entities } else { + ChildDefinitions.TryAdd(item.Id, item.GetType().FullName); + Logger.Debug("** " + item.Name + " Added to library."); } } - await LibraryManager.SaveChildren(Id, newChildren, CancellationToken.None).ConfigureAwait(false); + await LibraryManager.UpdateItem(this, CancellationToken.None).ConfigureAwait(false); //force the indexes to rebuild next time IndexCache.Clear(); @@ -848,9 +860,38 @@ namespace MediaBrowser.Controller.Entities /// Get our children from the repo - stubbed for now /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - protected virtual IEnumerable<BaseItem> GetCachedChildren() + protected IEnumerable<BaseItem> GetCachedChildren() + { + var items = ChildDefinitions.ToList().Select(RetrieveChild).Where(i => i != null).ToList(); + + foreach (var item in items) + { + item.Parent = this; + } + + return items; + } + + /// <summary> + /// Retrieves the child. + /// </summary> + /// <param name="child">The child.</param> + /// <returns>BaseItem.</returns> + private BaseItem RetrieveChild(KeyValuePair<Guid,string> child) { - return LibraryManager.RetrieveChildren(this).Select(i => i is IByReferenceItem ? LibraryManager.GetOrAddByReferenceItem(i) : i); + var type = child.Value; + + var itemType = _typeMapper.GetType(type); + + if (itemType == null) + { + Logger.Error("Cannot find type {0}. Probably belongs to plug-in that is no longer loaded.", type); + return null; + } + + var item = LibraryManager.RetrieveItem(child.Key, itemType); + + return item is IByReferenceItem ? LibraryManager.GetOrAddByReferenceItem(item) : item; } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 307fe19544..6e649fd656 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -68,7 +68,14 @@ namespace MediaBrowser.Controller.Entities.Movies // Kick off a task to refresh the main item var result = await base.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); - var specialFeaturesChanged = await RefreshSpecialFeatures(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + var specialFeaturesChanged = false; + + // Must have a parent to have special features + // In other words, it must be part of the Parent/Child tree + if (LocationType == LocationType.FileSystem && Parent != null) + { + specialFeaturesChanged = await RefreshSpecialFeatures(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + } return specialFeaturesChanged || result; } @@ -95,11 +102,6 @@ namespace MediaBrowser.Controller.Entities.Movies /// <returns>IEnumerable{Video}.</returns> private IEnumerable<Video> LoadSpecialFeatures() { - if (LocationType != LocationType.FileSystem) - { - return new List<Video>(); - } - FileSystemInfo folder; try @@ -133,7 +135,7 @@ namespace MediaBrowser.Controller.Entities.Movies return LibraryManager.ResolvePaths<Video>(files, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = LibraryManager.RetrieveItem(video.Id) as Video; + var dbItem = LibraryManager.RetrieveItem(video.Id, typeof(Video)) as Video; if (dbItem != null) { diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index ad4cb2d09d..ee717a1918 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -139,7 +139,10 @@ namespace MediaBrowser.Controller.Entities var additionalPartsChanged = false; - if (IsMultiPart && LocationType == LocationType.FileSystem) + // Must have a parent to have additional parts + // In other words, it must be part of the Parent/Child tree + // The additional parts won't have additional parts themselves + if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null) { try { @@ -164,11 +167,6 @@ namespace MediaBrowser.Controller.Entities /// <returns>Task{System.Boolean}.</returns> private async Task<bool> RefreshAdditionalParts(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) { - if (!IsMultiPart || LocationType != LocationType.FileSystem) - { - return false; - } - var newItems = LoadAdditionalParts().ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); @@ -214,7 +212,7 @@ namespace MediaBrowser.Controller.Entities return LibraryManager.ResolvePaths<Video>(files, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = LibraryManager.RetrieveItem(video.Id) as Video; + var dbItem = LibraryManager.RetrieveItem(video.Id, typeof(Video)) as Video; if (dbItem != null) { diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 7e84350b37..6d6fab3bea 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -216,24 +216,9 @@ namespace MediaBrowser.Controller.Library /// Retrieves the item. /// </summary> /// <param name="id">The id.</param> - /// <returns>Task{BaseItem}.</returns> - BaseItem RetrieveItem(Guid id); - - /// <summary> - /// Saves the children. - /// </summary> - /// <param name="id">The id.</param> - /// <param name="children">The children.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task SaveChildren(Guid id, IEnumerable<BaseItem> children, CancellationToken cancellationToken); - - /// <summary> - /// Retrieves the children. - /// </summary> - /// <param name="parent">The parent.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - IEnumerable<BaseItem> RetrieveChildren(Folder parent); + /// <param name="type">The type.</param> + /// <returns>BaseItem.</returns> + BaseItem RetrieveItem(Guid id, Type type); /// <summary> /// Validates the artists. diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index ba9e9f5bde..8765998f38 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -80,6 +80,7 @@ <Compile Include="Library\ILibraryPrescanTask.cs" /> <Compile Include="Library\IMetadataSaver.cs" /> <Compile Include="Localization\ILocalizationManager.cs" /> + <Compile Include="Reflection\TypeMapper.cs" /> <Compile Include="Session\ISessionManager.cs" /> <Compile Include="Drawing\ImageExtensions.cs" /> <Compile Include="Drawing\ImageHeader.cs" /> diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index bf3bc626af..d854d0e207 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -1,9 +1,10 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Persistence { @@ -20,36 +21,6 @@ namespace MediaBrowser.Controller.Persistence /// <returns>Task.</returns> Task SaveItem(BaseItem item, CancellationToken cancellationToken); - /// <summary> - /// Gets an item - /// </summary> - /// <param name="id">The id.</param> - /// <returns>BaseItem.</returns> - BaseItem GetItem(Guid id); - - /// <summary> - /// Gets children of a given Folder - /// </summary> - /// <param name="parent">The parent.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - IEnumerable<BaseItem> RetrieveChildren(Folder parent); - - /// <summary> - /// Retrieves the items. - /// </summary> - /// <param name="ids">The ids.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - IEnumerable<BaseItem> GetItems(IEnumerable<Guid> ids); - - /// <summary> - /// Saves children of a given Folder - /// </summary> - /// <param name="parentId">The parent id.</param> - /// <param name="children">The children.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task SaveChildren(Guid parentId, IEnumerable<BaseItem> children, CancellationToken cancellationToken); - /// <summary> /// Gets the critic reviews. /// </summary> @@ -72,5 +43,46 @@ namespace MediaBrowser.Controller.Persistence /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> Task SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken); + + /// <summary> + /// Retrieves the item. + /// </summary> + /// <param name="id">The id.</param> + /// <param name="type">The type.</param> + /// <returns>BaseItem.</returns> + BaseItem RetrieveItem(Guid id, Type type); + } + + /// <summary> + /// Class ItemRepositoryExtensions + /// </summary> + public static class ItemRepositoryExtensions + { + /// <summary> + /// Retrieves the item. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="repository">The repository.</param> + /// <param name="id">The id.</param> + /// <returns>``0.</returns> + public static T RetrieveItem<T>(this IItemRepository repository, Guid id) + where T : BaseItem, new() + { + return repository.RetrieveItem(id, typeof(T)) as T; + } + + /// <summary> + /// Retrieves the item. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="repository">The repository.</param> + /// <param name="idList">The id list.</param> + /// <returns>IEnumerable{``0}.</returns> + public static IEnumerable<T> RetrieveItems<T>(this IItemRepository repository, IEnumerable<Guid> idList) + where T : BaseItem, new() + { + return idList.Select(repository.RetrieveItem<T>).Where(i => i != null); + } } } + diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 1b4efc58b1..ad111f4ed4 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -27,6 +27,6 @@ namespace MediaBrowser.Controller.Persistence /// <param name="userId">The user id.</param> /// <param name="key">The key.</param> /// <returns>Task{UserItemData}.</returns> - Task<UserItemData> GetUserData(Guid userId, string key); + UserItemData GetUserData(Guid userId, string key); } } diff --git a/MediaBrowser.Server.Implementations/Reflection/TypeMapper.cs b/MediaBrowser.Controller/Reflection/TypeMapper.cs similarity index 96% rename from MediaBrowser.Server.Implementations/Reflection/TypeMapper.cs rename to MediaBrowser.Controller/Reflection/TypeMapper.cs index 5f411a0023..d968a3b420 100644 --- a/MediaBrowser.Server.Implementations/Reflection/TypeMapper.cs +++ b/MediaBrowser.Controller/Reflection/TypeMapper.cs @@ -2,7 +2,7 @@ using System.Collections.Concurrent; using System.Linq; -namespace MediaBrowser.Server.Implementations.Reflection +namespace MediaBrowser.Controller.Reflection { /// <summary> /// Class TypeMapper diff --git a/MediaBrowser.Model/Entities/IHasProviderIds.cs b/MediaBrowser.Model/Entities/IHasProviderIds.cs index 2ddf8ffad1..1c54455da6 100644 --- a/MediaBrowser.Model/Entities/IHasProviderIds.cs +++ b/MediaBrowser.Model/Entities/IHasProviderIds.cs @@ -39,6 +39,11 @@ namespace MediaBrowser.Model.Entities /// <returns>System.String.</returns> public static string GetProviderId(this IHasProviderIds instance, string name) { + if (instance == null) + { + throw new ArgumentNullException("instance"); + } + if (instance.ProviderIds == null) { return null; @@ -57,6 +62,11 @@ namespace MediaBrowser.Model.Entities /// <param name="value">The value.</param> public static void SetProviderId(this IHasProviderIds instance, string name, string value) { + if (instance == null) + { + throw new ArgumentNullException("instance"); + } + // If it's null remove the key from the dictionary if (string.IsNullOrEmpty(value)) { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 29a85dd9c0..f8aa4e2728 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -78,6 +78,7 @@ <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Savers\MovieXmlSaver.cs" /> <Compile Include="TV\EpisodeImageFromMediaLocationProvider.cs" /> + <Compile Include="TV\EpisodeIndexNumberProvider.cs" /> <Compile Include="TV\EpisodeProviderFromXml.cs" /> <Compile Include="TV\EpisodeXmlParser.cs" /> <Compile Include="TV\FanArtSeasonProvider.cs" /> diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs index 99d1cdbc1e..9e3a468c6c 100644 --- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Providers.MediaInfo } protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - + /// <summary> /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// </summary> @@ -134,7 +134,7 @@ namespace MediaBrowser.Providers.MediaInfo /// <param name="mount">The mount.</param> protected virtual void OnPreFetch(T item, IIsoMount mount) { - + } /// <summary> @@ -187,12 +187,16 @@ namespace MediaBrowser.Providers.MediaInfo var stream = new MediaStream { Codec = streamInfo.codec_name, - Language = GetDictionaryValue(streamInfo.tags, "language"), Profile = streamInfo.profile, Level = streamInfo.level, Index = streamInfo.index }; + if (streamInfo.tags != null) + { + stream.Language = GetDictionaryValue(streamInfo.tags, "language"); + } + if (streamInfo.codec_type.Equals("audio", StringComparison.OrdinalIgnoreCase)) { stream.Type = MediaStreamType.Audio; diff --git a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs new file mode 100644 index 0000000000..aa59ff7cf9 --- /dev/null +++ b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs @@ -0,0 +1,67 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.TV +{ + /// <summary> + /// Making this a provider because of how slow it is + /// It only ever needs to run once + /// </summary> + public class EpisodeIndexNumberProvider : BaseMetadataProvider + { + /// <summary> + /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class. + /// </summary> + /// <param name="logManager">The log manager.</param> + /// <param name="configurationManager">The configuration manager.</param> + public EpisodeIndexNumberProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + : base(logManager, configurationManager) + { + } + + /// <summary> + /// Supportses the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + public override bool Supports(BaseItem item) + { + return item is Episode; + } + + /// <summary> + /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// </summary> + /// <param name="item">The item.</param> + /// <param name="force">if set to <c>true</c> [force].</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{System.Boolean}.</returns> + public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + { + var episode = (Episode)item; + + episode.IndexNumber = TVUtils.GetEpisodeNumberFromFile(item.Path, item.Parent is Season); + episode.IndexNumberEnd = TVUtils.GetEndingEpisodeNumberFromFile(item.Path); + + SetLastRefreshed(item, DateTime.UtcNow); + + return TrueTaskResult; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.First; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index f4d0f9c505..e174b9a23d 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -564,7 +564,7 @@ namespace MediaBrowser.Server.Implementations.Library Directory.CreateDirectory(rootFolderPath); } - var rootFolder = RetrieveItem(rootFolderPath.GetMBId(typeof(AggregateFolder))) as AggregateFolder ?? (AggregateFolder)ResolvePath(new DirectoryInfo(rootFolderPath)); + var rootFolder = RetrieveItem(rootFolderPath.GetMBId(typeof(AggregateFolder)), typeof(AggregateFolder)) as AggregateFolder ?? (AggregateFolder)ResolvePath(new DirectoryInfo(rootFolderPath)); // Add in the plug-in folders foreach (var child in PluginFolderCreators) @@ -589,7 +589,8 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>UserRootFolder.</returns> public UserRootFolder GetUserRootFolder(string userRootPath) { - return _userRootFolders.GetOrAdd(userRootPath, key => RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder))) as UserRootFolder ?? (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath))); + return _userRootFolders.GetOrAdd(userRootPath, key => RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder)), typeof(UserRootFolder)) as UserRootFolder ?? + (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath))); } /// <summary> @@ -779,9 +780,11 @@ namespace MediaBrowser.Server.Implementations.Library cancellationToken.ThrowIfCancellationRequested(); - var id = path.GetMBId(typeof(T)); + var type = typeof(T); - var item = RetrieveItem(id) as T; + var id = path.GetMBId(type); + + var item = RetrieveItem(id, type) as T; if (item == null) { item = new T @@ -816,7 +819,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task.</returns> public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) { - const int maxTasks = 10; + const int maxTasks = 15; var tasks = new List<Task>(); @@ -1166,7 +1169,7 @@ namespace MediaBrowser.Server.Implementations.Library return item; } - return ItemRepository.GetItem(id); + return null; } /// <summary> @@ -1340,39 +1343,11 @@ namespace MediaBrowser.Server.Implementations.Library /// Retrieves the item. /// </summary> /// <param name="id">The id.</param> - /// <returns>Task{BaseItem}.</returns> - public BaseItem RetrieveItem(Guid id) - { - return ItemRepository.GetItem(id); - } - - /// <summary> - /// Saves the children. - /// </summary> - /// <param name="id">The id.</param> - /// <param name="children">The children.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SaveChildren(Guid id, IEnumerable<BaseItem> children, CancellationToken cancellationToken) - { - return ItemRepository.SaveChildren(id, children, cancellationToken); - } - - /// <summary> - /// Retrieves the children. - /// </summary> - /// <param name="parent">The parent.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - public IEnumerable<BaseItem> RetrieveChildren(Folder parent) + /// <param name="type">The type.</param> + /// <returns>BaseItem.</returns> + public BaseItem RetrieveItem(Guid id, Type type) { - var children = ItemRepository.RetrieveChildren(parent).ToList(); - - foreach (var child in children) - { - child.Parent = parent; - } - - return children; + return ItemRepository.RetrieveItem(id, type); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 3969acac78..84d57b9725 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -51,9 +51,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV if (episode != null) { - episode.IndexNumber = TVUtils.GetEpisodeNumberFromFile(args.Path, season != null); - episode.IndexNumberEnd = TVUtils.GetEndingEpisodeNumberFromFile(args.Path); - if (season != null) { episode.ParentIndexNumber = season.IndexNumber; diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index a006161d49..a3a5220a05 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -82,14 +82,6 @@ </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> - <Reference Include="System.Data.SQLite, Version=1.0.86.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\System.Data.SQLite.x86.1.0.86.0\lib\net45\System.Data.SQLite.dll</HintPath> - </Reference> - <Reference Include="System.Data.SQLite.Linq, Version=1.0.86.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\System.Data.SQLite.x86.1.0.86.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath> - </Reference> <Reference Include="System.Reactive.Core"> <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath> </Reference> @@ -142,7 +134,6 @@ <Compile Include="MediaEncoder\MediaEncoder.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Providers\ProviderManager.cs" /> - <Compile Include="Reflection\TypeMapper.cs" /> <Compile Include="ScheduledTasks\ArtistValidationTask.cs" /> <Compile Include="ScheduledTasks\PeopleValidationTask.cs" /> <Compile Include="ScheduledTasks\ChapterImagesTask.cs" /> @@ -171,12 +162,10 @@ <Compile Include="Sorting\RevenueComparer.cs" /> <Compile Include="Sorting\RuntimeComparer.cs" /> <Compile Include="Sorting\SortNameComparer.cs" /> - <Compile Include="Sqlite\SQLiteDisplayPreferencesRepository.cs" /> - <Compile Include="Sqlite\SQLiteExtensions.cs" /> - <Compile Include="Sqlite\SQLiteItemRepository.cs" /> - <Compile Include="Sqlite\SQLiteRepository.cs" /> - <Compile Include="Sqlite\SQLiteUserDataRepository.cs" /> - <Compile Include="Sqlite\SQLiteUserRepository.cs" /> + <Compile Include="Persistence\JsonDisplayPreferencesRepository.cs" /> + <Compile Include="Persistence\JsonItemRepository.cs" /> + <Compile Include="Persistence\JsonUserDataRepository.cs" /> + <Compile Include="Persistence\JsonUserRepository.cs" /> <Compile Include="Udp\UdpMessageReceivedEventArgs.cs" /> <Compile Include="Udp\UdpServer.cs" /> <Compile Include="Updates\InstallationManager.cs" /> diff --git a/MediaBrowser.Server.Implementations/Persistence/JsonDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Persistence/JsonDisplayPreferencesRepository.cs new file mode 100644 index 0000000000..6ac2ff07a6 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Persistence/JsonDisplayPreferencesRepository.cs @@ -0,0 +1,164 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Persistence +{ + public class JsonDisplayPreferencesRepository : IDisplayPreferencesRepository + { + private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); + + private SemaphoreSlim GetLock(string filename) + { + return _fileLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return "Json"; + } + } + + /// <summary> + /// The _json serializer + /// </summary> + private readonly IJsonSerializer _jsonSerializer; + + private readonly string _dataPath; + + /// <summary> + /// Initializes a new instance of the <see cref="JsonUserDataRepository" /> class. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="jsonSerializer">The json serializer.</param> + /// <param name="logManager">The log manager.</param> + /// <exception cref="System.ArgumentNullException"> + /// jsonSerializer + /// or + /// appPaths + /// </exception> + public JsonDisplayPreferencesRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) + { + if (jsonSerializer == null) + { + throw new ArgumentNullException("jsonSerializer"); + } + if (appPaths == null) + { + throw new ArgumentNullException("appPaths"); + } + + _jsonSerializer = jsonSerializer; + _dataPath = Path.Combine(appPaths.DataPath, "display-preferences"); + } + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public Task Initialize() + { + return Task.FromResult(true); + } + + /// <summary> + /// Save the display preferences associated with an item in the repo + /// </summary> + /// <param name="displayPreferences">The display preferences.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, CancellationToken cancellationToken) + { + if (displayPreferences == null) + { + throw new ArgumentNullException("displayPreferences"); + } + if (displayPreferences.Id == Guid.Empty) + { + throw new ArgumentNullException("displayPreferences.Id"); + } + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (!Directory.Exists(_dataPath)) + { + Directory.CreateDirectory(_dataPath); + } + + var path = Path.Combine(_dataPath, displayPreferences.Id + ".json"); + + var semaphore = GetLock(path); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + _jsonSerializer.SerializeToFile(displayPreferences, path); + } + finally + { + semaphore.Release(); + } + } + + /// <summary> + /// Gets the display preferences. + /// </summary> + /// <param name="displayPreferencesId">The display preferences id.</param> + /// <returns>Task{DisplayPreferences}.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public Task<DisplayPreferences> GetDisplayPreferences(Guid displayPreferencesId) + { + if (displayPreferencesId == Guid.Empty) + { + throw new ArgumentNullException("displayPreferencesId"); + } + + return Task.Run(() => + { + var path = Path.Combine(_dataPath, displayPreferencesId + ".json"); + + try + { + return _jsonSerializer.DeserializeFromFile<DisplayPreferences>(path); + } + catch (IOException) + { + // File doesn't exist or is currently bring written to + return null; + } + }); + } + + public void Dispose() + { + // Wait up to two seconds for any existing writes to finish + var locks = _fileLocks.Values.ToList() + .Where(i => i.CurrentCount == 1) + .Select(i => i.WaitAsync(2000)); + + var task = Task.WhenAll(locks); + + Task.WaitAll(task); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Persistence/JsonItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/JsonItemRepository.cs new file mode 100644 index 0000000000..d0333e334e --- /dev/null +++ b/MediaBrowser.Server.Implementations/Persistence/JsonItemRepository.cs @@ -0,0 +1,235 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Persistence +{ + public class JsonItemRepository : IItemRepository + { + private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); + + private SemaphoreSlim GetLock(string filename) + { + return _fileLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return "Json"; + } + } + + /// <summary> + /// Gets the json serializer. + /// </summary> + /// <value>The json serializer.</value> + private readonly IJsonSerializer _jsonSerializer; + + private readonly string _criticReviewsPath; + + private readonly FileSystemRepository _itemRepo; + + /// <summary> + /// Initializes a new instance of the <see cref="JsonUserDataRepository" /> class. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="jsonSerializer">The json serializer.</param> + /// <param name="logManager">The log manager.</param> + /// <exception cref="System.ArgumentNullException">appPaths</exception> + public JsonItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) + { + if (appPaths == null) + { + throw new ArgumentNullException("appPaths"); + } + if (jsonSerializer == null) + { + throw new ArgumentNullException("jsonSerializer"); + } + + _jsonSerializer = jsonSerializer; + + _criticReviewsPath = Path.Combine(appPaths.DataPath, "critic-reviews"); + + _itemRepo = new FileSystemRepository(Path.Combine(appPaths.DataPath, "library")); + } + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public Task Initialize() + { + return Task.FromResult(true); + } + + /// <summary> + /// Save a standard item in the repo + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public async Task SaveItem(BaseItem item, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + if (!Directory.Exists(_criticReviewsPath)) + { + Directory.CreateDirectory(_criticReviewsPath); + } + + var path = _itemRepo.GetResourcePath(item.Id + ".json"); + + var parentPath = Path.GetDirectoryName(path); + if (!Directory.Exists(parentPath)) + { + Directory.CreateDirectory(parentPath); + } + + var semaphore = GetLock(path); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + _jsonSerializer.SerializeToFile(item, path); + } + finally + { + semaphore.Release(); + } + } + + /// <summary> + /// Saves the items. + /// </summary> + /// <param name="items">The items.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException"> + /// items + /// or + /// cancellationToken + /// </exception> + public Task SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken) + { + if (items == null) + { + throw new ArgumentNullException("items"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + var tasks = items.Select(i => SaveItem(i, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// <summary> + /// Retrieves the item. + /// </summary> + /// <param name="id">The id.</param> + /// <param name="type">The type.</param> + /// <returns>BaseItem.</returns> + /// <exception cref="System.ArgumentNullException">id</exception> + public BaseItem RetrieveItem(Guid id, Type type) + { + if (id == Guid.Empty) + { + throw new ArgumentNullException("id"); + } + + var path = _itemRepo.GetResourcePath(id + ".json"); + + try + { + return (BaseItem)_jsonSerializer.DeserializeFromFile(type, path); + } + catch (IOException) + { + // File doesn't exist or is currently bring written to + return null; + } + } + + /// <summary> + /// Gets the critic reviews. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <returns>Task{IEnumerable{ItemReview}}.</returns> + public Task<IEnumerable<ItemReview>> GetCriticReviews(Guid itemId) + { + return Task.Run<IEnumerable<ItemReview>>(() => + { + var path = Path.Combine(_criticReviewsPath, itemId + ".json"); + + try + { + return _jsonSerializer.DeserializeFromFile<List<ItemReview>>(path); + } + catch (IOException) + { + // File doesn't exist or is currently bring written to + return new List<ItemReview>(); + } + }); + } + + /// <summary> + /// Saves the critic reviews. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="criticReviews">The critic reviews.</param> + /// <returns>Task.</returns> + public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews) + { + return Task.Run(() => + { + if (!Directory.Exists(_criticReviewsPath)) + { + Directory.CreateDirectory(_criticReviewsPath); + } + + var path = Path.Combine(_criticReviewsPath, itemId + ".json"); + + _jsonSerializer.SerializeToFile(criticReviews.ToList(), path); + }); + } + + public void Dispose() + { + // Wait up to two seconds for any existing writes to finish + var locks = _fileLocks.Values.ToList() + .Where(i => i.CurrentCount == 1) + .Select(i => i.WaitAsync(2000)); + + var task = Task.WhenAll(locks); + + Task.WaitAll(task); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Persistence/JsonUserDataRepository.cs similarity index 53% rename from MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs rename to MediaBrowser.Server.Implementations/Persistence/JsonUserDataRepository.cs index d378809ffb..2f1129bebc 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/JsonUserDataRepository.cs @@ -1,31 +1,28 @@ -using System.Data.SQLite; -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; using System.Collections.Concurrent; -using System.Data; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace MediaBrowser.Server.Implementations.Sqlite +namespace MediaBrowser.Server.Implementations.Persistence { - /// <summary> - /// Class SQLiteUserDataRepository - /// </summary> - public class SQLiteUserDataRepository : SqliteRepository, IUserDataRepository + public class JsonUserDataRepository : IUserDataRepository { - private readonly ConcurrentDictionary<string, Task<UserItemData>> _userData = new ConcurrentDictionary<string, Task<UserItemData>>(); + private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); - private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); - - /// <summary> - /// The repository name - /// </summary> - public const string RepositoryName = "SQLite"; + private SemaphoreSlim GetLock(string filename) + { + return _fileLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + + private readonly ConcurrentDictionary<string, UserItemData> _userData = new ConcurrentDictionary<string, UserItemData>(); /// <summary> /// Gets the name of the repository @@ -35,19 +32,18 @@ namespace MediaBrowser.Server.Implementations.Sqlite { get { - return RepositoryName; + return "Json"; } } private readonly IJsonSerializer _jsonSerializer; - /// <summary> - /// The _app paths - /// </summary> - private readonly IApplicationPaths _appPaths; + private readonly string _dataPath; + + private readonly ILogger _logger; /// <summary> - /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. + /// Initializes a new instance of the <see cref="JsonUserDataRepository" /> class. /// </summary> /// <param name="appPaths">The app paths.</param> /// <param name="jsonSerializer">The json serializer.</param> @@ -57,8 +53,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// or /// appPaths /// </exception> - public SQLiteUserDataRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) - : base(logManager) + public JsonUserDataRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) { if (jsonSerializer == null) { @@ -69,30 +64,18 @@ namespace MediaBrowser.Server.Implementations.Sqlite throw new ArgumentNullException("appPaths"); } + _logger = logManager.GetLogger(GetType().Name); _jsonSerializer = jsonSerializer; - _appPaths = appPaths; + _dataPath = Path.Combine(appPaths.DataPath, "userdata"); } /// <summary> /// Opens the connection to the database /// </summary> /// <returns>Task.</returns> - public async Task Initialize() + public Task Initialize() { - var dbFile = Path.Combine(_appPaths.DataPath, "userdata.db"); - - await ConnectToDb(dbFile).ConfigureAwait(false); - - string[] queries = { - - "create table if not exists userdata (key nvarchar, userId GUID, data BLOB)", - "create unique index if not exists userdataindex on userdata (key, userId)", - "create table if not exists schema_version (table_name primary key, version)", - //pragmas - "pragma temp_store = memory" - }; - - RunQueries(queries); + return Task.FromResult(true); } /// <summary> @@ -135,14 +118,12 @@ namespace MediaBrowser.Server.Implementations.Sqlite { await PersistUserData(userId, key, userData, cancellationToken).ConfigureAwait(false); - var newValue = Task.FromResult(userData); - // Once it succeeds, put it into the dictionary to make it available to everyone else - _userData.AddOrUpdate(GetInternalKey(userId, key), newValue, delegate { return newValue; }); + _userData.AddOrUpdate(GetInternalKey(userId, key), userData, delegate { return userData; }); } catch (Exception ex) { - Logger.ErrorException("Error saving user data", ex); + _logger.ErrorException("Error saving user data", ex); throw; } @@ -171,60 +152,25 @@ namespace MediaBrowser.Server.Implementations.Sqlite { cancellationToken.ThrowIfCancellationRequested(); - var serialized = _jsonSerializer.SerializeToBytes(userData); - - cancellationToken.ThrowIfCancellationRequested(); - - await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - SQLiteTransaction transaction = null; + var path = GetUserDataPath(userId, key); - try + var parentPath = Path.GetDirectoryName(path); + if (!Directory.Exists(parentPath)) { - transaction = Connection.BeginTransaction(); - - using (var cmd = Connection.CreateCommand()) - { - cmd.CommandText = "replace into userdata (key, userId, data) values (@1, @2, @3)"; - cmd.AddParam("@1", key); - cmd.AddParam("@2", userId); - cmd.AddParam("@3", serialized); - - cmd.Transaction = transaction; - - await cmd.ExecuteNonQueryAsync(cancellationToken); - } - - transaction.Commit(); + Directory.CreateDirectory(parentPath); } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save user data:", e); + var semaphore = GetLock(path); - if (transaction != null) - { - transaction.Rollback(); - } + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - throw; + try + { + _jsonSerializer.SerializeToFile(userData, path); } finally { - if (transaction != null) - { - transaction.Dispose(); - } - - _writeLock.Release(); + semaphore.Release(); } } @@ -239,7 +185,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// or /// key /// </exception> - public Task<UserItemData> GetUserData(Guid userId, string key) + public UserItemData GetUserData(Guid userId, string key) { if (userId == Guid.Empty) { @@ -259,31 +205,42 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// <param name="userId">The user id.</param> /// <param name="key">The key.</param> /// <returns>Task{UserItemData}.</returns> - private async Task<UserItemData> RetrieveUserData(Guid userId, string key) + private UserItemData RetrieveUserData(Guid userId, string key) { - using (var cmd = Connection.CreateCommand()) + var path = GetUserDataPath(userId, key); + + try + { + return _jsonSerializer.DeserializeFromFile<UserItemData>(path); + } + catch (IOException) { - cmd.CommandText = "select data from userdata where key = @key and userId=@userId"; - - var idParam = cmd.Parameters.Add("@key", DbType.String); - idParam.Value = key; - - var userIdParam = cmd.Parameters.Add("@userId", DbType.Guid); - userIdParam.Value = userId; - - using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow).ConfigureAwait(false)) - { - if (reader.Read()) - { - using (var stream = GetStream(reader, 0)) - { - return _jsonSerializer.DeserializeFromStream<UserItemData>(stream); - } - } - } - - return new UserItemData(); + // File doesn't exist or is currently bring written to + return new UserItemData { UserId = userId }; } } + + private string GetUserDataPath(Guid userId, string key) + { + var userFolder = Path.Combine(_dataPath, userId.ToString()); + + var keyHash = key.GetMD5().ToString(); + + var prefix = keyHash.Substring(0, 1); + + return Path.Combine(userFolder, prefix, keyHash + ".json"); + } + + public void Dispose() + { + // Wait up to two seconds for any existing writes to finish + var locks = _fileLocks.Values.ToList() + .Where(i => i.CurrentCount == 1) + .Select(i => i.WaitAsync(2000)); + + var task = Task.WhenAll(locks); + + Task.WaitAll(task); + } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Persistence/JsonUserRepository.cs b/MediaBrowser.Server.Implementations/Persistence/JsonUserRepository.cs new file mode 100644 index 0000000000..0573c6e2ef --- /dev/null +++ b/MediaBrowser.Server.Implementations/Persistence/JsonUserRepository.cs @@ -0,0 +1,189 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Persistence +{ + public class JsonUserRepository : IUserRepository + { + private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); + + private SemaphoreSlim GetLock(string filename) + { + return _fileLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return "Json"; + } + } + + /// <summary> + /// Gets the json serializer. + /// </summary> + /// <value>The json serializer.</value> + private readonly IJsonSerializer _jsonSerializer; + + private readonly string _dataPath; + + /// <summary> + /// Initializes a new instance of the <see cref="JsonUserRepository"/> class. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="jsonSerializer">The json serializer.</param> + /// <param name="logManager">The log manager.</param> + /// <exception cref="System.ArgumentNullException"> + /// appPaths + /// or + /// jsonSerializer + /// </exception> + public JsonUserRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) + { + if (appPaths == null) + { + throw new ArgumentNullException("appPaths"); + } + if (jsonSerializer == null) + { + throw new ArgumentNullException("jsonSerializer"); + } + + _jsonSerializer = jsonSerializer; + + _dataPath = Path.Combine(appPaths.DataPath, "users"); + } + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public Task Initialize() + { + return Task.FromResult(true); + } + + /// <summary> + /// Save a user in the repo + /// </summary> + /// <param name="user">The user.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">user</exception> + public async Task SaveUser(User user, CancellationToken cancellationToken) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (!Directory.Exists(_dataPath)) + { + Directory.CreateDirectory(_dataPath); + } + + var path = Path.Combine(_dataPath, user.Id + ".json"); + + var semaphore = GetLock(path); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + _jsonSerializer.SerializeToFile(user, path); + } + finally + { + semaphore.Release(); + } + } + + /// <summary> + /// Retrieve all users from the database + /// </summary> + /// <returns>IEnumerable{User}.</returns> + public IEnumerable<User> RetrieveAllUsers() + { + try + { + return Directory.EnumerateFiles(_dataPath, "*.json", SearchOption.TopDirectoryOnly) + .Select(i => _jsonSerializer.DeserializeFromFile<User>(i)); + } + catch (IOException) + { + return new List<User>(); + } + } + + /// <summary> + /// Deletes the user. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">user</exception> + public async Task DeleteUser(User user, CancellationToken cancellationToken) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + var path = Path.Combine(_dataPath, user.Id + ".json"); + + var semaphore = GetLock(path); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + File.Delete(path); + } + finally + { + semaphore.Release(); + } + } + + public void Dispose() + { + // Wait up to two seconds for any existing writes to finish + var locks = _fileLocks.Values.ToList() + .Where(i => i.CurrentCount == 1) + .Select(i => i.WaitAsync(2000)); + + var task = Task.WhenAll(locks); + + Task.WaitAll(task); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index 08398e22e3..beab6200f2 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -106,6 +106,11 @@ namespace MediaBrowser.Server.Implementations.Providers /// <returns>Task{System.Boolean}.</returns> public async Task<bool> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true) { + if (item == null) + { + throw new ArgumentNullException("item"); + } + // Allow providers of the same priority to execute in parallel MetadataProviderPriority? currentPriority = null; var currentTasks = new List<Task<bool>>(); diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs index 12f98cef39..d63494c1e8 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs @@ -210,9 +210,9 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks { var allItems = sourceItems.ToList(); - var localTrailers = allItems.SelectMany(i => _itemRepo.GetItems(i.LocalTrailerIds).Cast<Video>()); + var localTrailers = allItems.SelectMany(i => _itemRepo.RetrieveItems<Trailer>(i.LocalTrailerIds)); - var themeVideos = allItems.SelectMany(i => _itemRepo.GetItems(i.ThemeVideoIds).Cast<Video>()); + var themeVideos = allItems.SelectMany(i => _itemRepo.RetrieveItems<Video>(i.ThemeVideoIds)); var videos = allItems.OfType<Video>().ToList(); @@ -222,8 +222,8 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks items.AddRange(themeVideos); - items.AddRange(videos.SelectMany(i => _itemRepo.GetItems(i.AdditionalPartIds).Cast<Video>()).ToList()); - items.AddRange(videos.OfType<Movie>().SelectMany(i => _itemRepo.GetItems(i.SpecialFeatureIds).Cast<Video>()).ToList()); + items.AddRange(videos.SelectMany(i => _itemRepo.RetrieveItems<Video>(i.AdditionalPartIds)).ToList()); + items.AddRange(videos.OfType<Movie>().SelectMany(i => _itemRepo.RetrieveItems<Video>(i.SpecialFeatureIds)).ToList()); return items.Where(i => { diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 29ce2698c3..dda9658d45 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -215,7 +215,7 @@ namespace MediaBrowser.Server.Implementations.Session var key = item.GetUserDataKey(); - var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false); + var data = _userDataRepository.GetUserData(user.Id, key); data.PlayCount++; data.LastPlayedDate = DateTime.UtcNow; @@ -226,7 +226,7 @@ namespace MediaBrowser.Server.Implementations.Session } await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false); - + // Nothing to save here // Fire events to inform plugins EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs @@ -266,7 +266,7 @@ namespace MediaBrowser.Server.Implementations.Session if (positionTicks.HasValue) { - var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false); + var data = _userDataRepository.GetUserData(user.Id, key); UpdatePlayState(item, data, positionTicks.Value); await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false); @@ -307,7 +307,7 @@ namespace MediaBrowser.Server.Implementations.Session var key = item.GetUserDataKey(); - var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false); + var data = _userDataRepository.GetUserData(user.Id, key); if (positionTicks.HasValue) { diff --git a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs index c634c760e4..2abd4d0f20 100644 --- a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting /// <returns>DateTime.</returns> private DateTime GetDate(BaseItem x) { - var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey()).Result; + var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey()); if (userdata != null && userdata.LastPlayedDate.HasValue) { diff --git a/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs index a7cbd21499..d4c22e6e02 100644 --- a/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Sorting /// <returns>DateTime.</returns> private int GetValue(BaseItem x) { - var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey()).Result; + var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey()); return userdata == null ? 0 : userdata.PlayCount; } diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs deleted file mode 100644 index 60c3c1fe02..0000000000 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs +++ /dev/null @@ -1,209 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Data; -using System.Data.SQLite; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.Sqlite -{ - /// <summary> - /// Class SQLiteDisplayPreferencesRepository - /// </summary> - public class SQLiteDisplayPreferencesRepository : SqliteRepository, IDisplayPreferencesRepository - { - /// <summary> - /// The repository name - /// </summary> - public const string RepositoryName = "SQLite"; - - /// <summary> - /// Gets the name of the repository - /// </summary> - /// <value>The name.</value> - public string Name - { - get - { - return RepositoryName; - } - } - - /// <summary> - /// The _json serializer - /// </summary> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// The _app paths - /// </summary> - private readonly IApplicationPaths _appPaths; - - private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); - - /// <summary> - /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. - /// </summary> - /// <param name="appPaths">The app paths.</param> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="logManager">The log manager.</param> - /// <exception cref="System.ArgumentNullException"> - /// jsonSerializer - /// or - /// appPaths - /// </exception> - public SQLiteDisplayPreferencesRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) - : base(logManager) - { - if (jsonSerializer == null) - { - throw new ArgumentNullException("jsonSerializer"); - } - if (appPaths == null) - { - throw new ArgumentNullException("appPaths"); - } - - _jsonSerializer = jsonSerializer; - _appPaths = appPaths; - } - - /// <summary> - /// Opens the connection to the database - /// </summary> - /// <returns>Task.</returns> - public async Task Initialize() - { - var dbFile = Path.Combine(_appPaths.DataPath, "displaypreferences.db"); - - await ConnectToDb(dbFile).ConfigureAwait(false); - - string[] queries = { - - "create table if not exists displaypreferences (id GUID, data BLOB)", - "create unique index if not exists displaypreferencesindex on displaypreferences (id)", - "create table if not exists schema_version (table_name primary key, version)", - //pragmas - "pragma temp_store = memory" - }; - - RunQueries(queries); - } - - /// <summary> - /// Save the display preferences associated with an item in the repo - /// </summary> - /// <param name="displayPreferences">The display preferences.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">item</exception> - public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, CancellationToken cancellationToken) - { - if (displayPreferences == null) - { - throw new ArgumentNullException("displayPreferences"); - } - if (displayPreferences.Id == Guid.Empty) - { - throw new ArgumentNullException("displayPreferences.Id"); - } - if (cancellationToken == null) - { - throw new ArgumentNullException("cancellationToken"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var serialized = _jsonSerializer.SerializeToBytes(displayPreferences); - - await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - SQLiteTransaction transaction = null; - - try - { - transaction = Connection.BeginTransaction(); - - using (var cmd = Connection.CreateCommand()) - { - cmd.CommandText = "replace into displaypreferences (id, data) values (@1, @2)"; - cmd.AddParam("@1", displayPreferences.Id); - cmd.AddParam("@2", serialized); - - cmd.Transaction = transaction; - - await cmd.ExecuteNonQueryAsync(cancellationToken); - } - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save display preferences:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - - _writeLock.Release(); - } - } - - /// <summary> - /// Gets the display preferences. - /// </summary> - /// <param name="displayPreferencesId">The display preferences id.</param> - /// <returns>Task{DisplayPreferences}.</returns> - /// <exception cref="System.ArgumentNullException">item</exception> - public async Task<DisplayPreferences> GetDisplayPreferences(Guid displayPreferencesId) - { - if (displayPreferencesId == Guid.Empty) - { - throw new ArgumentNullException("displayPreferencesId"); - } - - var cmd = Connection.CreateCommand(); - cmd.CommandText = "select data from displaypreferences where id = @id"; - - var idParam = cmd.Parameters.Add("@id", DbType.Guid); - idParam.Value = displayPreferencesId; - - using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow).ConfigureAwait(false)) - { - if (reader.Read()) - { - using (var stream = GetStream(reader, 0)) - { - return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream); - } - } - } - - return null; - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteExtensions.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteExtensions.cs deleted file mode 100644 index 6aed8a3528..0000000000 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Data; -using System.Data.SQLite; - -namespace MediaBrowser.Server.Implementations.Sqlite -{ - /// <summary> - /// Class SQLiteExtensions - /// </summary> - static class SQLiteExtensions - { - /// <summary> - /// Adds the param. - /// </summary> - /// <param name="cmd">The CMD.</param> - /// <param name="param">The param.</param> - /// <returns>SQLiteParameter.</returns> - /// <exception cref="System.ArgumentNullException"></exception> - public static SQLiteParameter AddParam(this SQLiteCommand cmd, string param) - { - if (string.IsNullOrEmpty(param)) - { - throw new ArgumentNullException(); - } - - var sqliteParam = new SQLiteParameter(param); - cmd.Parameters.Add(sqliteParam); - return sqliteParam; - } - - /// <summary> - /// Adds the param. - /// </summary> - /// <param name="cmd">The CMD.</param> - /// <param name="param">The param.</param> - /// <param name="data">The data.</param> - /// <returns>SQLiteParameter.</returns> - /// <exception cref="System.ArgumentNullException"></exception> - public static SQLiteParameter AddParam(this SQLiteCommand cmd, string param, object data) - { - if (string.IsNullOrEmpty(param)) - { - throw new ArgumentNullException(); - } - - var sqliteParam = AddParam(cmd, param); - sqliteParam.Value = data; - return sqliteParam; - } - - /// <summary> - /// Determines whether the specified conn is open. - /// </summary> - /// <param name="conn">The conn.</param> - /// <returns><c>true</c> if the specified conn is open; otherwise, <c>false</c>.</returns> - public static bool IsOpen(this SQLiteConnection conn) - { - return conn.State == ConnectionState.Open; - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs deleted file mode 100644 index a068b7cccc..0000000000 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs +++ /dev/null @@ -1,524 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Server.Implementations.Reflection; -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.SQLite; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.Sqlite -{ - /// <summary> - /// Class SQLiteItemRepository - /// </summary> - public class SQLiteItemRepository : SqliteRepository, IItemRepository - { - /// <summary> - /// The _type mapper - /// </summary> - private readonly TypeMapper _typeMapper = new TypeMapper(); - - /// <summary> - /// The repository name - /// </summary> - public const string RepositoryName = "SQLite"; - - /// <summary> - /// Gets the name of the repository - /// </summary> - /// <value>The name.</value> - public string Name - { - get - { - return RepositoryName; - } - } - - /// <summary> - /// Gets the json serializer. - /// </summary> - /// <value>The json serializer.</value> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// The _app paths - /// </summary> - private readonly IApplicationPaths _appPaths; - - /// <summary> - /// The _save item command - /// </summary> - private SQLiteCommand _saveItemCommand; - /// <summary> - /// The _delete children command - /// </summary> - private SQLiteCommand _deleteChildrenCommand; - /// <summary> - /// The _save children command - /// </summary> - private SQLiteCommand _saveChildrenCommand; - - private string _criticReviewsPath; - - /// <summary> - /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. - /// </summary> - /// <param name="appPaths">The app paths.</param> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="logManager">The log manager.</param> - /// <exception cref="System.ArgumentNullException">appPaths</exception> - public SQLiteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) - : base(logManager) - { - if (appPaths == null) - { - throw new ArgumentNullException("appPaths"); - } - if (jsonSerializer == null) - { - throw new ArgumentNullException("jsonSerializer"); - } - - _appPaths = appPaths; - _jsonSerializer = jsonSerializer; - - _criticReviewsPath = Path.Combine(_appPaths.DataPath, "critic-reviews"); - } - - /// <summary> - /// Opens the connection to the database - /// </summary> - /// <returns>Task.</returns> - public async Task Initialize() - { - var dbFile = Path.Combine(_appPaths.DataPath, "library.db"); - - await ConnectToDb(dbFile).ConfigureAwait(false); - - string[] queries = { - - "create table if not exists items (guid GUID primary key, obj_type, data BLOB)", - "create index if not exists idx_items on items(guid)", - "create table if not exists children (guid GUID, child GUID)", - "create unique index if not exists idx_children on children(guid, child)", - "create table if not exists schema_version (table_name primary key, version)", - //triggers - TriggerSql, - //pragmas - "pragma temp_store = memory" - }; - - RunQueries(queries); - - PrepareStatements(); - } - - //cascade delete triggers - /// <summary> - /// The trigger SQL - /// </summary> - protected string TriggerSql = - @"CREATE TRIGGER if not exists delete_item - AFTER DELETE - ON items - FOR EACH ROW - BEGIN - DELETE FROM children WHERE children.guid = old.child; - DELETE FROM children WHERE children.child = old.child; - END"; - - /// <summary> - /// The _write lock - /// </summary> - private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); - - /// <summary> - /// Prepares the statements. - /// </summary> - private void PrepareStatements() - { - _saveItemCommand = new SQLiteCommand - { - CommandText = "replace into items (guid, obj_type, data) values (@1, @2, @3)" - }; - - _saveItemCommand.Parameters.Add(new SQLiteParameter("@1")); - _saveItemCommand.Parameters.Add(new SQLiteParameter("@2")); - _saveItemCommand.Parameters.Add(new SQLiteParameter("@3")); - - _deleteChildrenCommand = new SQLiteCommand - { - CommandText = "delete from children where guid = @guid" - }; - _deleteChildrenCommand.Parameters.Add(new SQLiteParameter("@guid")); - - _saveChildrenCommand = new SQLiteCommand - { - CommandText = "replace into children (guid, child) values (@guid, @child)" - }; - _saveChildrenCommand.Parameters.Add(new SQLiteParameter("@guid")); - _saveChildrenCommand.Parameters.Add(new SQLiteParameter("@child")); - } - - /// <summary> - /// Save a standard item in the repo - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">item</exception> - public Task SaveItem(BaseItem item, CancellationToken cancellationToken) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - return SaveItems(new[] { item }, cancellationToken); - } - - /// <summary> - /// Saves the items. - /// </summary> - /// <param name="items">The items.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException"> - /// items - /// or - /// cancellationToken - /// </exception> - public async Task SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken) - { - if (items == null) - { - throw new ArgumentNullException("items"); - } - - if (cancellationToken == null) - { - throw new ArgumentNullException("cancellationToken"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - SQLiteTransaction transaction = null; - - try - { - transaction = Connection.BeginTransaction(); - - foreach (var item in items) - { - cancellationToken.ThrowIfCancellationRequested(); - - _saveItemCommand.Parameters[0].Value = item.Id; - _saveItemCommand.Parameters[1].Value = item.GetType().FullName; - _saveItemCommand.Parameters[2].Value = _jsonSerializer.SerializeToBytes(item); - - _saveItemCommand.Transaction = transaction; - - await _saveItemCommand.ExecuteNonQueryAsync(cancellationToken); - } - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save items:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - - _writeLock.Release(); - } - } - - /// <summary> - /// Retrieve a standard item from the repo - /// </summary> - /// <param name="id">The id.</param> - /// <returns>BaseItem.</returns> - /// <exception cref="System.ArgumentNullException">id</exception> - /// <exception cref="System.ArgumentException"></exception> - public BaseItem GetItem(Guid id) - { - if (id == Guid.Empty) - { - throw new ArgumentNullException("id"); - } - - return RetrieveItemInternal(id); - } - - /// <summary> - /// Retrieves the items. - /// </summary> - /// <param name="ids">The ids.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - /// <exception cref="System.ArgumentNullException">ids</exception> - public IEnumerable<BaseItem> GetItems(IEnumerable<Guid> ids) - { - if (ids == null) - { - throw new ArgumentNullException("ids"); - } - - return ids.Select(RetrieveItemInternal); - } - - /// <summary> - /// Internal retrieve from items or users table - /// </summary> - /// <param name="id">The id.</param> - /// <returns>BaseItem.</returns> - /// <exception cref="System.ArgumentNullException">id</exception> - /// <exception cref="System.ArgumentException"></exception> - protected BaseItem RetrieveItemInternal(Guid id) - { - if (id == Guid.Empty) - { - throw new ArgumentNullException("id"); - } - - using (var cmd = Connection.CreateCommand()) - { - cmd.CommandText = "select obj_type,data from items where guid = @guid"; - var guidParam = cmd.Parameters.Add("@guid", DbType.Guid); - guidParam.Value = id; - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) - { - if (reader.Read()) - { - var type = reader.GetString(0); - using (var stream = GetStream(reader, 1)) - { - var itemType = _typeMapper.GetType(type); - - if (itemType == null) - { - Logger.Error("Cannot find type {0}. Probably belongs to plug-in that is no longer loaded.", type); - return null; - } - - var item = _jsonSerializer.DeserializeFromStream(stream, itemType); - return item as BaseItem; - } - } - } - return null; - } - } - - /// <summary> - /// Retrieve all the children of the given folder - /// </summary> - /// <param name="parent">The parent.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - /// <exception cref="System.ArgumentNullException"></exception> - public IEnumerable<BaseItem> RetrieveChildren(Folder parent) - { - if (parent == null) - { - throw new ArgumentNullException(); - } - - using (var cmd = Connection.CreateCommand()) - { - cmd.CommandText = "select obj_type,data from items where guid in (select child from children where guid = @guid)"; - var guidParam = cmd.Parameters.Add("@guid", DbType.Guid); - guidParam.Value = parent.Id; - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) - { - while (reader.Read()) - { - var type = reader.GetString(0); - - using (var stream = GetStream(reader, 1)) - { - var itemType = _typeMapper.GetType(type); - if (itemType == null) - { - Logger.Error("Cannot find type {0}. Probably belongs to plug-in that is no longer loaded.", type); - continue; - } - var item = _jsonSerializer.DeserializeFromStream(stream, itemType) as BaseItem; - if (item != null) - { - item.Parent = parent; - yield return item; - } - } - } - } - } - } - - /// <summary> - /// Save references to all the children for the given folder - /// (Doesn't actually save the child entities) - /// </summary> - /// <param name="id">The id.</param> - /// <param name="children">The children.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">id</exception> - public async Task SaveChildren(Guid id, IEnumerable<BaseItem> children, CancellationToken cancellationToken) - { - if (id == Guid.Empty) - { - throw new ArgumentNullException("id"); - } - - if (children == null) - { - throw new ArgumentNullException("children"); - } - - if (cancellationToken == null) - { - throw new ArgumentNullException("cancellationToken"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - SQLiteTransaction transaction = null; - - try - { - transaction = Connection.BeginTransaction(); - - // Delete exising children - _deleteChildrenCommand.Parameters[0].Value = id; - _deleteChildrenCommand.Transaction = transaction; - await _deleteChildrenCommand.ExecuteNonQueryAsync(cancellationToken); - - // Save new children - foreach (var child in children) - { - _saveChildrenCommand.Transaction = transaction; - - _saveChildrenCommand.Parameters[0].Value = id; - _saveChildrenCommand.Parameters[1].Value = child.Id; - - await _saveChildrenCommand.ExecuteNonQueryAsync(cancellationToken); - } - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save children:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - - _writeLock.Release(); - } - } - - /// <summary> - /// Gets the critic reviews. - /// </summary> - /// <param name="itemId">The item id.</param> - /// <returns>Task{IEnumerable{ItemReview}}.</returns> - public Task<IEnumerable<ItemReview>> GetCriticReviews(Guid itemId) - { - return Task.Run<IEnumerable<ItemReview>>(() => - { - - try - { - var path = Path.Combine(_criticReviewsPath, itemId + ".json"); - - return _jsonSerializer.DeserializeFromFile<List<ItemReview>>(path); - } - catch (DirectoryNotFoundException) - { - return new List<ItemReview>(); - } - catch (FileNotFoundException) - { - return new List<ItemReview>(); - } - - }); - } - - /// <summary> - /// Saves the critic reviews. - /// </summary> - /// <param name="itemId">The item id.</param> - /// <param name="criticReviews">The critic reviews.</param> - /// <returns>Task.</returns> - public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews) - { - return Task.Run(() => - { - if (!Directory.Exists(_criticReviewsPath)) - { - Directory.CreateDirectory(_criticReviewsPath); - } - - var path = Path.Combine(_criticReviewsPath, itemId + ".json"); - - _jsonSerializer.SerializeToFile(criticReviews.ToList(), path); - }); - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs deleted file mode 100644 index 6cd816e51c..0000000000 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs +++ /dev/null @@ -1,183 +0,0 @@ -using MediaBrowser.Model.Logging; -using System; -using System.Data; -using System.Data.Common; -using System.Data.SQLite; -using System.IO; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.Sqlite -{ - /// <summary> - /// Class SqliteRepository - /// </summary> - public abstract class SqliteRepository : IDisposable - { - /// <summary> - /// The db file name - /// </summary> - protected string DbFileName; - /// <summary> - /// The connection - /// </summary> - protected SQLiteConnection Connection; - - /// <summary> - /// Gets the logger. - /// </summary> - /// <value>The logger.</value> - protected ILogger Logger { get; private set; } - - /// <summary> - /// Initializes a new instance of the <see cref="SqliteRepository" /> class. - /// </summary> - /// <param name="logManager">The log manager.</param> - /// <exception cref="System.ArgumentNullException">logger</exception> - protected SqliteRepository(ILogManager logManager) - { - if (logManager == null) - { - throw new ArgumentNullException("logManager"); - } - - Logger = logManager.GetLogger(GetType().Name); - } - - /// <summary> - /// Connects to DB. - /// </summary> - /// <param name="dbPath">The db path.</param> - /// <returns>Task{System.Boolean}.</returns> - /// <exception cref="System.ArgumentNullException">dbPath</exception> - protected Task ConnectToDb(string dbPath) - { - if (string.IsNullOrEmpty(dbPath)) - { - throw new ArgumentNullException("dbPath"); - } - - DbFileName = dbPath; - var connectionstr = new SQLiteConnectionStringBuilder - { - PageSize = 4096, - CacheSize = 40960, - SyncMode = SynchronizationModes.Off, - DataSource = dbPath, - JournalMode = SQLiteJournalModeEnum.Wal - }; - - Connection = new SQLiteConnection(connectionstr.ConnectionString); - - return Connection.OpenAsync(); - } - - /// <summary> - /// Runs the queries. - /// </summary> - /// <param name="queries">The queries.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - /// <exception cref="System.ArgumentNullException">queries</exception> - protected void RunQueries(string[] queries) - { - if (queries == null) - { - throw new ArgumentNullException("queries"); - } - - using (var tran = Connection.BeginTransaction()) - { - try - { - using (var cmd = Connection.CreateCommand()) - { - foreach (var query in queries) - { - cmd.Transaction = tran; - cmd.CommandText = query; - cmd.ExecuteNonQuery(); - } - } - - tran.Commit(); - } - catch (Exception e) - { - Logger.ErrorException("Error running queries", e); - tran.Rollback(); - throw; - } - } - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private readonly object _disposeLock = new object(); - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - try - { - lock (_disposeLock) - { - if (Connection != null) - { - if (Connection.IsOpen()) - { - Connection.Close(); - } - - Connection.Dispose(); - Connection = null; - } - } - } - catch (Exception ex) - { - Logger.ErrorException("Error disposing database", ex); - } - } - } - - /// <summary> - /// Gets a stream from a DataReader at a given ordinal - /// </summary> - /// <param name="reader">The reader.</param> - /// <param name="ordinal">The ordinal.</param> - /// <returns>Stream.</returns> - /// <exception cref="System.ArgumentNullException">reader</exception> - protected static Stream GetStream(IDataReader reader, int ordinal) - { - if (reader == null) - { - throw new ArgumentNullException("reader"); - } - - var memoryStream = new MemoryStream(); - var num = 0L; - var array = new byte[4096]; - long bytes; - do - { - bytes = reader.GetBytes(ordinal, num, array, 0, array.Length); - memoryStream.Write(array, 0, (int)bytes); - num += bytes; - } - while (bytes > 0L); - memoryStream.Position = 0; - return memoryStream; - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs deleted file mode 100644 index 335841549a..0000000000 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs +++ /dev/null @@ -1,271 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.SQLite; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.Sqlite -{ - /// <summary> - /// Class SQLiteUserRepository - /// </summary> - public class SQLiteUserRepository : SqliteRepository, IUserRepository - { - /// <summary> - /// The repository name - /// </summary> - public const string RepositoryName = "SQLite"; - - private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); - - /// <summary> - /// Gets the name of the repository - /// </summary> - /// <value>The name.</value> - public string Name - { - get - { - return RepositoryName; - } - } - - /// <summary> - /// Gets the json serializer. - /// </summary> - /// <value>The json serializer.</value> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// The _app paths - /// </summary> - private readonly IApplicationPaths _appPaths; - - /// <summary> - /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. - /// </summary> - /// <param name="appPaths">The app paths.</param> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="logManager">The log manager.</param> - /// <exception cref="System.ArgumentNullException">appPaths</exception> - public SQLiteUserRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) - : base(logManager) - { - if (appPaths == null) - { - throw new ArgumentNullException("appPaths"); - } - if (jsonSerializer == null) - { - throw new ArgumentNullException("jsonSerializer"); - } - - _appPaths = appPaths; - _jsonSerializer = jsonSerializer; - } - - /// <summary> - /// Opens the connection to the database - /// </summary> - /// <returns>Task.</returns> - public async Task Initialize() - { - var dbFile = Path.Combine(_appPaths.DataPath, "users.db"); - - await ConnectToDb(dbFile).ConfigureAwait(false); - - string[] queries = { - - "create table if not exists users (guid GUID primary key, data BLOB)", - "create index if not exists idx_users on users(guid)", - "create table if not exists schema_version (table_name primary key, version)", - //pragmas - "pragma temp_store = memory" - }; - - RunQueries(queries); - } - - /// <summary> - /// Save a user in the repo - /// </summary> - /// <param name="user">The user.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">user</exception> - public async Task SaveUser(User user, CancellationToken cancellationToken) - { - if (user == null) - { - throw new ArgumentNullException("user"); - } - - if (cancellationToken == null) - { - throw new ArgumentNullException("cancellationToken"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var serialized = _jsonSerializer.SerializeToBytes(user); - - cancellationToken.ThrowIfCancellationRequested(); - - await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - SQLiteTransaction transaction = null; - - try - { - transaction = Connection.BeginTransaction(); - - using (var cmd = Connection.CreateCommand()) - { - cmd.CommandText = "replace into users (guid, data) values (@1, @2)"; - cmd.AddParam("@1", user.Id); - cmd.AddParam("@2", serialized); - - cmd.Transaction = transaction; - - await cmd.ExecuteNonQueryAsync(cancellationToken); - } - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save user:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - - _writeLock.Release(); - } - } - - /// <summary> - /// Retrieve all users from the database - /// </summary> - /// <returns>IEnumerable{User}.</returns> - public IEnumerable<User> RetrieveAllUsers() - { - using (var cmd = Connection.CreateCommand()) - { - cmd.CommandText = "select data from users"; - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) - { - while (reader.Read()) - { - using (var stream = GetStream(reader, 0)) - { - var user = _jsonSerializer.DeserializeFromStream<User>(stream); - yield return user; - } - } - } - } - } - - /// <summary> - /// Deletes the user. - /// </summary> - /// <param name="user">The user.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">user</exception> - public async Task DeleteUser(User user, CancellationToken cancellationToken) - { - if (user == null) - { - throw new ArgumentNullException("user"); - } - - if (cancellationToken == null) - { - throw new ArgumentNullException("cancellationToken"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - SQLiteTransaction transaction = null; - - try - { - transaction = Connection.BeginTransaction(); - - using (var cmd = Connection.CreateCommand()) - { - cmd.CommandText = "delete from users where guid=@guid"; - - var guidParam = cmd.Parameters.Add("@guid", DbType.Guid); - guidParam.Value = user.Id; - - cmd.Transaction = transaction; - - await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); - } - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to delete user:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - - _writeLock.Release(); - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 12b6ef6500..f13933eb6e 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -14,5 +14,4 @@ <package id="ServiceStack.Redis" version="3.9.43" targetFramework="net45" /> <package id="ServiceStack.Text" version="3.9.45" targetFramework="net45" /> <package id="SharpZipLib" version="0.86.0" targetFramework="net45" /> - <package id="System.Data.SQLite.x86" version="1.0.86.0" targetFramework="net45" /> </packages> \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config index e461d8c9ea..a5cbacb614 100644 --- a/MediaBrowser.ServerApplication/App.config +++ b/MediaBrowser.ServerApplication/App.config @@ -33,10 +33,6 @@ <assemblyIdentity name="System.Reactive.Interfaces" publicKeyToken="f300afd708cefcd3" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-2.0.20823.0" newVersion="2.0.20823.0" /> </dependentAssembly> - <dependentAssembly> - <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" /> - <bindingRedirect oldVersion="0.0.0.0-1.0.86.0" newVersion="1.0.86.0" /> - </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-1.5.11.0" newVersion="1.5.11.0" /> diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 583053fa4e..da871cc120 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -38,10 +38,10 @@ using MediaBrowser.Server.Implementations.IO; using MediaBrowser.Server.Implementations.Library; using MediaBrowser.Server.Implementations.Localization; using MediaBrowser.Server.Implementations.MediaEncoder; +using MediaBrowser.Server.Implementations.Persistence; using MediaBrowser.Server.Implementations.Providers; using MediaBrowser.Server.Implementations.ServerManager; using MediaBrowser.Server.Implementations.Session; -using MediaBrowser.Server.Implementations.Sqlite; using MediaBrowser.Server.Implementations.Updates; using MediaBrowser.Server.Implementations.WebSocket; using MediaBrowser.ServerApplication.Implementations; @@ -244,16 +244,16 @@ namespace MediaBrowser.ServerApplication ZipClient = new DotNetZipClient(); RegisterSingleInstance(ZipClient); - UserDataRepository = new SQLiteUserDataRepository(ApplicationPaths, JsonSerializer, LogManager); + UserDataRepository = new JsonUserDataRepository(ApplicationPaths, JsonSerializer, LogManager); RegisterSingleInstance(UserDataRepository); - UserRepository = new SQLiteUserRepository(ApplicationPaths, JsonSerializer, LogManager); + UserRepository = new JsonUserRepository(ApplicationPaths, JsonSerializer, LogManager); RegisterSingleInstance(UserRepository); - DisplayPreferencesRepository = new SQLiteDisplayPreferencesRepository(ApplicationPaths, JsonSerializer, LogManager); + DisplayPreferencesRepository = new JsonDisplayPreferencesRepository(ApplicationPaths, JsonSerializer, LogManager); RegisterSingleInstance(DisplayPreferencesRepository); - ItemRepository = new SQLiteItemRepository(ApplicationPaths, JsonSerializer, LogManager); + ItemRepository = new JsonItemRepository(ApplicationPaths, JsonSerializer, LogManager); RegisterSingleInstance(ItemRepository); UserManager = new UserManager(Logger, ServerConfigurationManager); diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index aaa4ce6f8d..50221ef1c4 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.124</version> + <version>3.0.125</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.124" /> + <dependency id="MediaBrowser.Common" version="3.0.125" /> <dependency id="NLog" version="2.0.1.2" /> <dependency id="ServiceStack.Text" version="3.9.45" /> <dependency id="SimpleInjector" version="2.2.3" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 3e8859441d..225d65bd65 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.124</version> + <version>3.0.125</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 6540d99cd2..1da7b5dcea 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.124</version> + <version>3.0.125</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.124" /> + <dependency id="MediaBrowser.Common" version="3.0.125" /> </dependencies> </metadata> <files>