rework user data

This commit is contained in:
Luke Pulverenti 2016-06-03 20:15:14 -04:00
parent 7cf424303b
commit ddb6ea6f05
8 changed files with 138 additions and 221 deletions

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
return GetEpisodes(series, user, includeMissingEpisodes, includeVirtualUnairedEpisodes, null);
}
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 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()

View file

@ -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)
{

View file

@ -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>

View file

@ -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)

View file

@ -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>

View file

@ -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);