jellyfin/Emby.Server.Implementations/Library/UserDataManager.cs
Erwin de Haan ec1f5dc317 Mayor code cleanup
Add Argument*Exceptions now use proper nameof operators.

Added exception messages to quite a few Argument*Exceptions.

Fixed rethorwing to be proper syntax.

Added a ton of null checkes. (This is only a start, there are about 500 places that need proper null handling)

Added some TODOs to log certain exceptions.

Fix sln again.

Fixed all AssemblyInfo's and added proper copyright (where I could find them)

We live in *current year*.

Fixed the use of braces.

Fixed a ton of properties, and made a fair amount of functions static that should be and can be static.

Made more Methods that should be static static.

You can now use static to find bad functions!

Removed unused variable. And added one more proper XML comment.
2019-01-10 20:38:53 +01:00

279 lines
9.1 KiB
C#

using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using MediaBrowser.Controller.Dto;
using System.Globalization;
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;
private Func<IUserManager> _userManager;
public UserDataManager(ILoggerFactory loggerFactory, IServerConfigurationManager config, Func<IUserManager> userManager)
{
_config = config;
_logger = loggerFactory.CreateLogger(GetType().Name);
_userManager = userManager;
}
/// <summary>
/// Gets or sets the repository.
/// </summary>
/// <value>The repository.</value>
public IUserDataRepository Repository { get; set; }
public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
var user = _userManager().GetUserById(userId);
SaveUserData(user, item, userData, reason, cancellationToken);
}
public void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
if (userData == null)
{
throw new ArgumentNullException(nameof(userData));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
cancellationToken.ThrowIfCancellationRequested();
var keys = item.GetUserDataKeys();
var userId = user.InternalId;
foreach (var key in keys)
{
Repository.SaveUserData(userId, key, userData, cancellationToken);
}
var cacheKey = GetCacheKey(userId, item.Id);
_userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
{
Keys = keys,
UserData = userData,
SaveReason = reason,
UserId = user.Id,
Item = item
});
}
/// <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 void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
{
var user = _userManager().GetUserById(userId);
Repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
}
/// <summary>
/// Retrieve all user data for the given user
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public List<UserItemData> GetAllUserData(Guid userId)
{
var user = _userManager().GetUserById(userId);
return Repository.GetAllUserData(user.InternalId);
}
public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
{
var user = _userManager().GetUserById(userId);
return GetUserData(user, itemId, keys);
}
public UserItemData GetUserData(User user, Guid itemId, List<string> keys)
{
var userId = user.InternalId;
var cacheKey = GetCacheKey(userId, itemId);
return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys));
}
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
{
var userData = Repository.GetUserData(internalUserId, keys);
if (userData != null)
{
return userData;
}
if (keys.Count > 0)
{
return new UserItemData
{
Key = keys[0]
};
}
return null;
}
/// <summary>
/// Gets the internal key.
/// </summary>
/// <returns>System.String.</returns>
private static string GetCacheKey(long internalUserId, Guid itemId)
{
return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N");
}
public UserItemData GetUserData(User user, BaseItem item)
{
return GetUserData(user, item.Id, item.GetUserDataKeys());
}
public UserItemData GetUserData(string userId, BaseItem item)
{
return GetUserData(new Guid(userId), item);
}
public UserItemData GetUserData(Guid userId, BaseItem item)
{
return GetUserData(userId, item.Id, item.GetUserDataKeys());
}
public UserItemDataDto GetUserDataDto(BaseItem item, User user)
{
var userData = GetUserData(user, item);
var dto = GetUserItemDataDto(userData);
item.FillUserDataDtoValues(dto, userData, null, user, new DtoOptions());
return dto;
}
public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options)
{
var userData = GetUserData(user, item);
var dto = GetUserItemDataDto(userData);
item.FillUserDataDtoValues(dto, userData, itemDto, user, options);
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(nameof(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 runtimeTicks = item.GetRunTimeTicksForPlayState();
var positionTicks = reportedPositionTicks ?? runtimeTicks;
var hasRuntime = runtimeTicks > 0;
// If a position has been reported, and if we know the duration
if (positionTicks > 0 && hasRuntime)
{
var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 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 >= runtimeTicks)
{
positionTicks = 0;
data.Played = playedToCompletion = true;
}
else
{
// Enforce MinResumeDuration
var durationSeconds = TimeSpan.FromTicks(runtimeTicks).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.SupportsPositionTicksResume)
{
positionTicks = 0;
}
data.PlaybackPositionTicks = positionTicks;
return playedToCompletion;
}
}
}