Parcourir la source

rework user data

Luke Pulverenti il y a 9 ans
Parent
commit
ddb6ea6f05

+ 2 - 67
MediaBrowser.Controller/Entities/Folder.cs

@@ -164,49 +164,15 @@ namespace MediaBrowser.Controller.Entities
                 item.DateModified = DateTime.UtcNow;
             }
 
-            AddChildInternal(item.Id);
-
             await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
         }
 
-        protected void AddChildrenInternal(List<Guid> children)
-        {
-            lock (_childrenSyncLock)
-            {
-                var newChildren = ChildIds.ToList();
-                newChildren.AddRange(children);
-                _children = newChildren.ToList();
-            }
-        }
-        protected void AddChildInternal(Guid child)
-        {
-            lock (_childrenSyncLock)
-            {
-                var childIds = ChildIds.ToList();
-                if (!childIds.Contains(child))
-                {
-                    childIds.Add(child);
-                    _children = childIds.ToList();
-                }
-            }
-        }
-
-        protected void RemoveChildrenInternal(List<Guid> children)
-        {
-            lock (_childrenSyncLock)
-            {
-                _children = ChildIds.Except(children).ToList();
-            }
-        }
-
         /// <summary>
         /// Removes the child.
         /// </summary>
         /// <param name="item">The item.</param>
         public void RemoveChild(BaseItem item)
         {
-            RemoveChildrenInternal(new[] { item.Id }.ToList());
-
             item.SetParent(null);
         }
 
@@ -241,33 +207,6 @@ namespace MediaBrowser.Controller.Entities
 
         #endregion
 
-        /// <summary>
-        /// The children
-        /// </summary>
-        private IReadOnlyList<Guid> _children;
-        /// <summary>
-        /// The _children sync lock
-        /// </summary>
-        private readonly object _childrenSyncLock = new object();
-        /// <summary>
-        /// Gets or sets the actual children.
-        /// </summary>
-        /// <value>The actual children.</value>
-        protected virtual IEnumerable<Guid> ChildIds
-        {
-            get
-            {
-                lock (_childrenSyncLock)
-                {
-                    if (_children == null)
-                    {
-                        _children = LoadChildren().ToList();
-                    }
-                    return _children.ToList();
-                }
-            }
-        }
-
         /// <summary>
         /// Gets the actual children.
         /// </summary>
@@ -277,7 +216,7 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
-                return ChildIds.Select(LibraryManager.GetItemById).Where(i => i != null);
+                return LoadChildren().Select(LibraryManager.GetItemById).Where(i => i != null);
             }
         }
 
@@ -479,8 +418,6 @@ namespace MediaBrowser.Controller.Entities
 
                     if (actualRemovals.Count > 0)
                     {
-                        RemoveChildrenInternal(actualRemovals.Select(i => i.Id).ToList());
-
                         foreach (var item in actualRemovals)
                         {
                             Logger.Debug("Removed item: " + item.Path);
@@ -493,8 +430,6 @@ namespace MediaBrowser.Controller.Entities
                     }
 
                     await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
-
-                    AddChildrenInternal(newItems.Select(i => i.Id).ToList());
                 }
             }
 
@@ -766,7 +701,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 if (!(this is ICollectionFolder))
                 {
-                    Logger.Debug("Query requires post-filtering due to LinkedChildren");
+                    Logger.Debug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name);
                     return true;
                 }
             }

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

@@ -108,7 +108,13 @@ namespace MediaBrowser.Controller.Entities.TV
             var series = Series;
             if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
             {
-                list.InsertRange(0, series.GetUserDataKeys().Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000")));
+                var seriesUserDataKeys = series.GetUserDataKeys();
+                var take = seriesUserDataKeys.Count;
+                if (seriesUserDataKeys.Count > 1)
+                {
+                    take--;
+                }
+                list.InsertRange(0, seriesUserDataKeys.Take(take).Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000")));
             }
 
             return list;

+ 7 - 42
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -196,52 +196,17 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             var config = user.Configuration;
 
-            return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
+            return GetEpisodes(Series, user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
         }
 
-        public IEnumerable<Episode> GetEpisodes(User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
+        public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
         {
-            var series = Series;
-
-            if (IndexNumber.HasValue && series != null)
-            {
-                return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes);
-            }
-
-            var episodes = GetRecursiveChildren(user)
-                .OfType<Episode>();
-
-            if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
-            {
-                var seasonNumber = IndexNumber;
-                var list = episodes.ToList();
-
-                if (seasonNumber.HasValue)
-                {
-                    list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
-                        .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
-                }
-                else
-                {
-                    list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
-                        .Where(i => !i.ParentIndexNumber.HasValue));
-                }
-
-                episodes = list.DistinctBy(i => i.Id);
-            }
-
-            if (!includeMissingEpisodes)
-            {
-                episodes = episodes.Where(i => !i.IsMissingEpisode);
-            }
-            if (!includeVirtualUnairedEpisodes)
-            {
-                episodes = episodes.Where(i => !i.IsVirtualUnaired);
-            }
+            return GetEpisodes(series, user, includeMissingEpisodes, includeVirtualUnairedEpisodes, null);
+        }
 
-            return LibraryManager
-                .Sort(episodes, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
-                .Cast<Episode>();
+        public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
+        {
+            return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes, allSeriesEpisodes);
         }
 
         public IEnumerable<Episode> GetEpisodes()

+ 30 - 15
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -183,24 +183,20 @@ namespace MediaBrowser.Controller.Entities.TV
 
         protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
         {
+            if (query.User == null)
+            {
+                return base.GetItemsInternal(query);
+            }
+
             var user = query.User;
 
             Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
 
             IEnumerable<BaseItem> items;
 
-            if (query.User == null)
-            {
-                items = query.Recursive
-                   ? GetRecursiveChildren(filter)
-                   : Children.Where(filter);
-            }
-            else
-            {
-                items = query.Recursive
-                   ? GetSeasons(user).Cast<BaseItem>().Concat(GetEpisodes(user)).Where(filter)
-                   : GetSeasons(user).Where(filter);
-            }
+            items = query.Recursive
+               ? GetSeasons(user).Cast<BaseItem>().Concat(GetEpisodes(user)).Where(filter)
+               : GetSeasons(user).Where(filter);
 
             var result = PostFilterAndSort(items, query);
 
@@ -260,8 +256,10 @@ namespace MediaBrowser.Controller.Entities.TV
 
         public IEnumerable<Episode> GetEpisodes(User user, bool includeMissing, bool includeVirtualUnaired)
         {
+            var allSeriesEpisodes = GetAllEpisodes(user).ToList();
+
             var allEpisodes = GetSeasons(user, true, true)
-                .SelectMany(i => i.GetEpisodes(user, includeMissing, includeVirtualUnaired))
+                .SelectMany(i => i.GetEpisodes(this, user, includeMissing, includeVirtualUnaired, allSeriesEpisodes))
                 .Reverse()
                 .ToList();
 
@@ -350,7 +348,7 @@ namespace MediaBrowser.Controller.Entities.TV
             return false;
         }
 
-        public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
+        private IEnumerable<Episode> GetAllEpisodes(User user)
         {
             IEnumerable<Episode> episodes;
 
@@ -388,7 +386,24 @@ namespace MediaBrowser.Controller.Entities.TV
                 }).Cast<Episode>();
             }
 
-            episodes = FilterEpisodesBySeason(episodes, parentSeason, DisplaySpecialsWithSeasons);
+            return episodes;
+        }
+
+        public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
+        {
+            IEnumerable<Episode> episodes = GetAllEpisodes(user);
+
+            return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
+        }
+
+        public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
+        {
+            if (allSeriesEpisodes == null)
+            {
+                return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes);
+            }
+
+            var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, DisplaySpecialsWithSeasons);
 
             if (!includeMissingEpisodes)
             {

+ 2 - 0
MediaBrowser.Controller/Persistence/IUserDataRepository.cs

@@ -29,6 +29,8 @@ namespace MediaBrowser.Controller.Persistence
         /// <returns>Task{UserItemData}.</returns>
         UserItemData GetUserData(Guid userId, string key);
 
+        UserItemData GetUserData(Guid userId, List<string> keys);
+
         /// <summary>
         /// Return all user data associated with the given user
         /// </summary>

+ 24 - 74
MediaBrowser.Server.Implementations/Library/UserDataManager.cs

@@ -23,7 +23,8 @@ namespace MediaBrowser.Server.Implementations.Library
     {
         public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
 
-        private readonly Dictionary<string, UserItemData> _userData = new Dictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
+        private readonly ConcurrentDictionary<string, UserItemData> _userData =
+            new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
 
         private readonly ILogger _logger;
         private readonly IServerConfigurationManager _config;
@@ -64,13 +65,6 @@ namespace MediaBrowser.Server.Implementations.Library
                 try
                 {
                     await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
-
-                    var newValue = userData;
-
-                    lock (_userData)
-                    {
-                        _userData[GetCacheKey(userId, key)] = newValue;
-                    }
                 }
                 catch (Exception ex)
                 {
@@ -80,6 +74,9 @@ namespace MediaBrowser.Server.Implementations.Library
                 }
             }
 
+            var cacheKey = GetCacheKey(userId, item.Id);
+            _userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
+
             EventHelper.FireEventIfNotNull(UserDataSaved, this, new UserDataSaveEventArgs
             {
                 Keys = keys,
@@ -122,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
                 throw;
             }
-            
+
         }
 
         /// <summary>
@@ -140,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library
             return Repository.GetAllUserData(userId);
         }
 
-        public UserItemData GetUserData(Guid userId, List<string> keys)
+        public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
         {
             if (userId == Guid.Empty)
             {
@@ -150,26 +147,23 @@ namespace MediaBrowser.Server.Implementations.Library
             {
                 throw new ArgumentNullException("keys");
             }
-
-            lock (_userData)
+            if (keys.Count == 0)
             {
-                foreach (var key in keys)
-                {
-                    var cacheKey = GetCacheKey(userId, key);
-                    UserItemData value;
-                    if (_userData.TryGetValue(cacheKey, out value))
-                    {
-                        return value;
-                    }
+                throw new ArgumentException("UserData keys cannot be empty.");
+            }
 
-                    value = Repository.GetUserData(userId, key);
+            var cacheKey = GetCacheKey(userId, itemId);
 
-                    if (value != null)
-                    {
-                        _userData[cacheKey] = value;
-                        return value;
-                    }
-                }
+            return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys));
+        }
+
+        private UserItemData GetUserDataInternal(Guid userId, List<string> keys)
+        {
+            var userData = Repository.GetUserData(userId, keys);
+
+            if (userData != null)
+            {
+                return userData;
             }
 
             if (keys.Count > 0)
@@ -184,57 +178,13 @@ namespace MediaBrowser.Server.Implementations.Library
             return null;
         }
 
-        /// <summary>
-        /// Gets the user data.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="key">The key.</param>
-        /// <returns>Task{UserItemData}.</returns>
-        public UserItemData GetUserData(Guid userId, string key)
-        {
-            if (userId == Guid.Empty)
-            {
-                throw new ArgumentNullException("userId");
-            }
-            if (string.IsNullOrEmpty(key))
-            {
-                throw new ArgumentNullException("key");
-            }
-
-            lock (_userData)
-            {
-                var cacheKey = GetCacheKey(userId, key);
-                UserItemData value;
-                if (_userData.TryGetValue(cacheKey, out value))
-                {
-                    return value;
-                }
-
-                value = Repository.GetUserData(userId, key);
-
-                if (value == null)
-                {
-                    value = new UserItemData
-                    {
-                        UserId = userId,
-                        Key = key
-                    };
-                }
-
-                _userData[cacheKey] = value;
-                return value;
-            }
-        }
-
         /// <summary>
         /// Gets the internal key.
         /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="key">The key.</param>
         /// <returns>System.String.</returns>
-        private string GetCacheKey(Guid userId, string key)
+        private string GetCacheKey(Guid userId, Guid itemId)
         {
-            return userId + key;
+            return userId.ToString("N") + itemId.ToString("N");
         }
 
         public UserItemData GetUserData(IHasUserData user, IHasUserData item)
@@ -249,7 +199,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
         public UserItemData GetUserData(Guid userId, IHasUserData item)
         {
-            return GetUserData(userId, item.GetUserDataKeys());
+            return GetUserData(userId, item.Id, item.GetUserDataKeys());
         }
 
         public UserItemDataDto GetUserDataDto(IHasUserData item, User user)

+ 48 - 0
MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs

@@ -5,7 +5,9 @@ using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Generic;
 using System.Data;
+using System.Globalization;
 using System.IO;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -300,6 +302,52 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
         }
 
+        public UserItemData GetUserData(Guid userId, List<string> keys)
+        {
+            if (userId == Guid.Empty)
+            {
+                throw new ArgumentNullException("userId");
+            }
+            if (keys == null)
+            {
+                throw new ArgumentNullException("keys");
+            }
+
+            using (var cmd = _connection.CreateCommand())
+            {
+                var index = 0;
+                var excludeIds = new List<string>();
+                var builder = new StringBuilder();
+                foreach (var key in keys)
+                {
+                    var paramName = "@Key" + index;
+                    excludeIds.Add("Key =" + paramName);
+                    cmd.Parameters.Add(cmd, paramName, DbType.String).Value = key;
+                    builder.Append(" WHEN Key=" + paramName + " THEN " + index);
+                    index++;
+                }
+
+                var keyText = string.Join(" OR ", excludeIds.ToArray());
+
+                cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where userId=@userId AND (" + keyText + ") ";
+
+                cmd.CommandText += " ORDER BY (Case " + builder + " Else " + keys.Count.ToString(CultureInfo.InvariantCulture) + " End )";
+                cmd.CommandText += " LIMIT 1";
+
+                cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
+                {
+                    if (reader.Read())
+                    {
+                        return ReadRow(reader);
+                    }
+                }
+
+                return null;
+            }
+        }
+
         /// <summary>
         /// Return all user-data associated with the given user
         /// </summary>

+ 18 - 22
MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs

@@ -111,24 +111,6 @@ namespace MediaBrowser.Server.Implementations.TV
                 .Select(i => GetNextUp(i, currentUser))
                 // Include if an episode was found, and either the series is not unwatched or the specific series was requested
                 .Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
-                //.OrderByDescending(i =>
-                //{
-                //    var episode = i.Item1;
-
-                //    var seriesUserData = _userDataManager.GetUserData(user, episode.Series);
-
-                //    if (seriesUserData.IsFavorite)
-                //    {
-                //        return 2;
-                //    }
-
-                //    if (seriesUserData.Likes.HasValue)
-                //    {
-                //        return seriesUserData.Likes.Value ? 1 : -1;
-                //    }
-
-                //    return 0;
-                //})
                 .OrderByDescending(i => i.Item2)
                 .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
                 .Select(i => i.Item1);
@@ -143,9 +125,8 @@ namespace MediaBrowser.Server.Implementations.TV
         private Tuple<Episode, DateTime, bool> GetNextUp(Series series, User user)
         {
             // Get them in display order, then reverse
-            var allEpisodes = series.GetSeasons(user, true, true)
-                .Where(i => !i.IndexNumber.HasValue || i.IndexNumber.Value != 0)
-                .SelectMany(i => i.GetEpisodes(user))
+            var allEpisodes = series.GetEpisodes(user, true, true)
+                .Where(i => !i.ParentIndexNumber.HasValue || i.ParentIndexNumber.Value != 0)
                 .Reverse()
                 .ToList();
 
@@ -155,6 +136,8 @@ namespace MediaBrowser.Server.Implementations.TV
 
             var includeMissing = user.Configuration.DisplayMissingEpisodes;
 
+            var unplayedEpisodes = new List<Episode>();
+
             // Go back starting with the most recent episodes
             foreach (var episode in allEpisodes)
             {
@@ -172,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.TV
                 }
                 else
                 {
+                    unplayedEpisodes.Add(episode);
+
                     if (!episode.IsVirtualUnaired && (includeMissing || !episode.IsMissingEpisode))
                     {
                         nextUp = episode;
@@ -184,7 +169,18 @@ namespace MediaBrowser.Server.Implementations.TV
                 return new Tuple<Episode, DateTime, bool>(nextUp, lastWatchedDate, false);
             }
 
-            var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (includeMissing || !i.IsMissingEpisode) && !i.IsPlayed(user));
+            Episode firstEpisode = null;
+            // Find the first unplayed episode. Start from the back of the list since they're in reverse order
+            for (var i = unplayedEpisodes.Count - 1; i >= 0; i--)
+            {
+                var unplayedEpisode = unplayedEpisodes[i];
+
+                if (!unplayedEpisode.IsVirtualUnaired && (includeMissing || !unplayedEpisode.IsMissingEpisode))
+                {
+                    firstEpisode = unplayedEpisode;
+                    break;
+                }
+            }
 
             // Return the first episode
             return new Tuple<Episode, DateTime, bool>(firstEpisode, DateTime.MinValue, true);