aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library/UserDataManager.cs
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2016-11-03 03:14:14 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2016-11-03 03:14:14 -0400
commitb76a1abda578b8ff64bad2997b036b0fc43e264f (patch)
tree7b73000abcf71e90c290b6107525969cb904ed2d /Emby.Server.Implementations/Library/UserDataManager.cs
parent3eb4091808735858b01855d298226d239be464af (diff)
move classes to portable server lib
Diffstat (limited to 'Emby.Server.Implementations/Library/UserDataManager.cs')
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs287
1 files changed, 287 insertions, 0 deletions
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
+{
+ /// <summary>
+ /// Class UserDataManager
+ /// </summary>
+ public class UserDataManager : IUserDataManager
+ {
+ public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
+
+ private readonly ConcurrentDictionary<string, UserItemData> _userData =
+ new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
+
+ private readonly ILogger _logger;
+ private readonly IServerConfigurationManager _config;
+
+ public UserDataManager(ILogManager logManager, IServerConfigurationManager config)
+ {
+ _config = config;
+ _logger = logManager.GetLogger(GetType().Name);
+ }
+
+ /// <summary>
+ /// Gets or sets the repository.
+ /// </summary>
+ /// <value>The repository.</value>
+ 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);
+ }
+
+ /// <summary>
+ /// Save the provided user data for the given user. Batch operation. Does not fire any events or update the cache.
+ /// </summary>
+ /// <param name="userId"></param>
+ /// <param name="userData"></param>
+ /// <param name="cancellationToken"></param>
+ /// <returns></returns>
+ public async Task SaveAllUserData(Guid userId, IEnumerable<UserItemData> 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);
+ }
+
+ /// <summary>
+ /// Retrieve all user data for the given user
+ /// </summary>
+ /// <param name="userId"></param>
+ /// <returns></returns>
+ public IEnumerable<UserItemData> GetAllUserData(Guid userId)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ return Repository.GetAllUserData(userId);
+ }
+
+ public UserItemData GetUserData(Guid userId, Guid itemId, List<string> 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<string> 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;
+ }
+
+ /// <summary>
+ /// Gets the internal key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ 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<UserItemDataDto> 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<UserItemDataDto> 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;
+ }
+
+ /// <summary>
+ /// Converts a UserItemData to a DTOUserItemData
+ /// </summary>
+ /// <param name="data">The data.</param>
+ /// <returns>DtoUserItemData.</returns>
+ /// <exception cref="System.ArgumentNullException"></exception>
+ 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;
+ }
+ }
+}