From b76a1abda578b8ff64bad2997b036b0fc43e264f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 3 Nov 2016 03:14:14 -0400 Subject: move classes to portable server lib --- .../Library/UserDataManager.cs | 287 +++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 Emby.Server.Implementations/Library/UserDataManager.cs (limited to 'Emby.Server.Implementations/Library/UserDataManager.cs') diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs new file mode 100644 index 000000000..c8dde1287 --- /dev/null +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -0,0 +1,287 @@ +using MediaBrowser.Common.Events; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Emby.Server.Implementations.Library +{ + /// + /// Class UserDataManager + /// + public class UserDataManager : IUserDataManager + { + public event EventHandler UserDataSaved; + + private readonly ConcurrentDictionary _userData = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + private readonly ILogger _logger; + private readonly IServerConfigurationManager _config; + + public UserDataManager(ILogManager logManager, IServerConfigurationManager config) + { + _config = config; + _logger = logManager.GetLogger(GetType().Name); + } + + /// + /// Gets or sets the repository. + /// + /// The repository. + public IUserDataRepository Repository { get; set; } + + public async Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken) + { + if (userData == null) + { + throw new ArgumentNullException("userData"); + } + if (item == null) + { + throw new ArgumentNullException("item"); + } + if (userId == Guid.Empty) + { + throw new ArgumentNullException("userId"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + var keys = item.GetUserDataKeys(); + + foreach (var key in keys) + { + await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false); + } + + var cacheKey = GetCacheKey(userId, item.Id); + _userData.AddOrUpdate(cacheKey, userData, (k, v) => userData); + + EventHelper.FireEventIfNotNull(UserDataSaved, this, new UserDataSaveEventArgs + { + Keys = keys, + UserData = userData, + SaveReason = reason, + UserId = userId, + Item = item + + }, _logger); + } + + /// + /// Save the provided user data for the given user. Batch operation. Does not fire any events or update the cache. + /// + /// + /// + /// + /// + public async Task SaveAllUserData(Guid userId, IEnumerable userData, CancellationToken cancellationToken) + { + if (userData == null) + { + throw new ArgumentNullException("userData"); + } + if (userId == Guid.Empty) + { + throw new ArgumentNullException("userId"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false); + } + + /// + /// Retrieve all user data for the given user + /// + /// + /// + public IEnumerable GetAllUserData(Guid userId) + { + if (userId == Guid.Empty) + { + throw new ArgumentNullException("userId"); + } + + return Repository.GetAllUserData(userId); + } + + public UserItemData GetUserData(Guid userId, Guid itemId, List keys) + { + if (userId == Guid.Empty) + { + throw new ArgumentNullException("userId"); + } + if (keys == null) + { + throw new ArgumentNullException("keys"); + } + if (keys.Count == 0) + { + throw new ArgumentException("UserData keys cannot be empty."); + } + + var cacheKey = GetCacheKey(userId, itemId); + + return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys)); + } + + private UserItemData GetUserDataInternal(Guid userId, List keys) + { + var userData = Repository.GetUserData(userId, keys); + + if (userData != null) + { + return userData; + } + + if (keys.Count > 0) + { + return new UserItemData + { + UserId = userId, + Key = keys[0] + }; + } + + return null; + } + + /// + /// Gets the internal key. + /// + /// System.String. + private string GetCacheKey(Guid userId, Guid itemId) + { + return userId.ToString("N") + itemId.ToString("N"); + } + + public UserItemData GetUserData(IHasUserData user, IHasUserData item) + { + return GetUserData(user.Id, item); + } + + public UserItemData GetUserData(string userId, IHasUserData item) + { + return GetUserData(new Guid(userId), item); + } + + public UserItemData GetUserData(Guid userId, IHasUserData item) + { + return GetUserData(userId, item.Id, item.GetUserDataKeys()); + } + + public async Task GetUserDataDto(IHasUserData item, User user) + { + var userData = GetUserData(user.Id, item); + var dto = GetUserItemDataDto(userData); + + await item.FillUserDataDtoValues(dto, userData, null, user).ConfigureAwait(false); + return dto; + } + + public async Task GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user) + { + var userData = GetUserData(user.Id, item); + var dto = GetUserItemDataDto(userData); + + await item.FillUserDataDtoValues(dto, userData, itemDto, user).ConfigureAwait(false); + return dto; + } + + /// + /// Converts a UserItemData to a DTOUserItemData + /// + /// The data. + /// DtoUserItemData. + /// + private UserItemDataDto GetUserItemDataDto(UserItemData data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + return new UserItemDataDto + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating, + Played = data.Played, + LastPlayedDate = data.LastPlayedDate, + Key = data.Key + }; + } + + public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks) + { + var playedToCompletion = false; + + var positionTicks = reportedPositionTicks ?? item.RunTimeTicks ?? 0; + var hasRuntime = item.RunTimeTicks.HasValue && item.RunTimeTicks > 0; + + // If a position has been reported, and if we know the duration + if (positionTicks > 0 && hasRuntime) + { + var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100; + + // Don't track in very beginning + if (pctIn < _config.Configuration.MinResumePct) + { + positionTicks = 0; + } + + // If we're at the end, assume completed + else if (pctIn > _config.Configuration.MaxResumePct || positionTicks >= item.RunTimeTicks.Value) + { + positionTicks = 0; + data.Played = playedToCompletion = true; + } + + else + { + // Enforce MinResumeDuration + var durationSeconds = TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds; + + if (durationSeconds < _config.Configuration.MinResumeDurationSeconds) + { + positionTicks = 0; + data.Played = playedToCompletion = true; + } + } + } + else if (!hasRuntime) + { + // If we don't know the runtime we'll just have to assume it was fully played + data.Played = playedToCompletion = true; + positionTicks = 0; + } + + if (!item.SupportsPlayedStatus) + { + positionTicks = 0; + data.Played = false; + } + if (item is Audio) + { + positionTicks = 0; + } + + data.PlaybackPositionTicks = positionTicks; + + return playedToCompletion; + } + } +} -- cgit v1.2.3