using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; using System.Collections.Concurrent; using System.Data; using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Sqlite { /// /// Class SQLiteUserDataRepository /// public class SQLiteUserDataRepository : SqliteRepository, IUserDataRepository { private readonly ConcurrentDictionary> _userData = new ConcurrentDictionary>(); /// /// The repository name /// public const string RepositoryName = "SQLite"; /// /// Gets the name of the repository /// /// The name. public string Name { get { return RepositoryName; } } /// /// Gets a value indicating whether [enable delayed commands]. /// /// true if [enable delayed commands]; otherwise, false. protected override bool EnableDelayedCommands { get { return false; } } private readonly IJsonSerializer _jsonSerializer; /// /// The _app paths /// private readonly IApplicationPaths _appPaths; /// /// Initializes a new instance of the class. /// /// The app paths. /// The json serializer. /// The log manager. /// /// jsonSerializer /// or /// appPaths /// public SQLiteUserDataRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) : base(logManager) { if (jsonSerializer == null) { throw new ArgumentNullException("jsonSerializer"); } if (appPaths == null) { throw new ArgumentNullException("appPaths"); } _jsonSerializer = jsonSerializer; _appPaths = appPaths; } /// /// Opens the connection to the database /// /// Task. public async Task Initialize() { var dbFile = Path.Combine(_appPaths.DataPath, "userdata.db"); await ConnectToDb(dbFile).ConfigureAwait(false); string[] queries = { "create table if not exists useritemdata (key nvarchar, userId GUID, Rating float null, PlaybackPositionTicks bigint, PlayCount int, IsFavorite bit, Played bit, LastPlayedDate bigint null)", "create unique index if not exists useritemdataindex on useritemdata (key, userId)", "create table if not exists schema_version (table_name primary key, version)", //pragmas "pragma temp_store = memory" }; RunQueries(queries); } /// /// Saves the user data. /// /// The user id. /// The key. /// The user data. /// The cancellation token. /// Task. /// userData /// or /// cancellationToken /// or /// userId /// or /// userDataId public async Task SaveUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken) { if (userData == null) { throw new ArgumentNullException("userData"); } if (cancellationToken == null) { throw new ArgumentNullException("cancellationToken"); } if (userId == Guid.Empty) { throw new ArgumentNullException("userId"); } if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } cancellationToken.ThrowIfCancellationRequested(); try { await PersistUserData(userId, key, userData, cancellationToken).ConfigureAwait(false); var newValue = Task.FromResult(userData); // Once it succeeds, put it into the dictionary to make it available to everyone else _userData.AddOrUpdate(GetInternalKey(userId, key), newValue, delegate { return newValue; }); } catch (Exception ex) { Logger.ErrorException("Error saving user data", ex); throw; } } /// /// Gets the internal key. /// /// The user id. /// The key. /// System.String. private string GetInternalKey(Guid userId, string key) { return userId + key; } /// /// Persists the user data. /// /// The user id. /// The key. /// The user data. /// The cancellation token. /// Task. public async Task PersistUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (var cmd = connection.CreateCommand()) { cmd.CommandText = "replace into useritemdata (key, userId, Rating,PlaybackPositionTicks,PlayCount,IsFavorite,Played,LastPlayedDate) values (@1, @2, @3, @4, @5, @6, @7, @8)"; cmd.AddParam("@1", key); cmd.AddParam("@2", userId); cmd.AddParam("@3", userData.Rating); cmd.AddParam("@4", userData.PlaybackPositionTicks); cmd.AddParam("@5", userData.PlayCount); cmd.AddParam("@6", userData.IsFavorite); cmd.AddParam("@7", userData.Played); if (userData.LastPlayedDate.HasValue) { cmd.AddParam("@8", userData.LastPlayedDate.Value.Ticks); } else { cmd.AddParam("@8", null); } using (var tran = connection.BeginTransaction()) { try { cmd.Transaction = tran; await cmd.ExecuteNonQueryAsync(cancellationToken); tran.Commit(); } catch (OperationCanceledException) { tran.Rollback(); } catch (Exception e) { Logger.ErrorException("Failed to commit transaction.", e); tran.Rollback(); } } } } /// /// Gets the user data. /// /// The user id. /// The key. /// Task{UserItemData}. /// /// userId /// or /// key /// public Task GetUserData(Guid userId, string key) { if (userId == Guid.Empty) { throw new ArgumentNullException("userId"); } if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } return _userData.GetOrAdd(GetInternalKey(userId, key), keyName => RetrieveUserData(userId, key)); } /// /// Retrieves the user data. /// /// The user id. /// The key. /// Task{UserItemData}. private async Task RetrieveUserData(Guid userId, string key) { using (var cmd = connection.CreateCommand()) { cmd.CommandText = "select Rating,PlaybackPositionTicks,PlayCount,IsFavorite,Played,LastPlayedDate from useritemdata where key = @key and userId=@userId"; var idParam = cmd.Parameters.Add("@key", DbType.String); idParam.Value = key; var userIdParam = cmd.Parameters.Add("@userId", DbType.Guid); userIdParam.Value = userId; var userdata = new UserItemData(); using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow).ConfigureAwait(false)) { if (reader.Read()) { if (!reader.IsDBNull(0)) { userdata.Rating = reader.GetDouble(0); } userdata.PlaybackPositionTicks = reader.GetInt64(1); userdata.PlayCount = reader.GetInt32(2); userdata.IsFavorite = reader.GetBoolean(3); userdata.Played = reader.GetBoolean(4); var ticks = (long?) reader.GetValue(5); if (ticks.HasValue) { userdata.LastPlayedDate = new DateTime(ticks.Value); } } } return userdata; } } } }