2013-03-04 05:43:06 +00:00
using MediaBrowser.Common.Configuration ;
2013-02-21 04:37:50 +00:00
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Persistence ;
2013-02-21 20:26:35 +00:00
using MediaBrowser.Model.Logging ;
2013-02-24 21:53:54 +00:00
using MediaBrowser.Model.Serialization ;
2013-02-21 01:33:05 +00:00
using System ;
2013-04-13 18:02:30 +00:00
using System.Collections.Concurrent ;
2013-02-21 01:33:05 +00:00
using System.Data ;
using System.IO ;
using System.Threading ;
using System.Threading.Tasks ;
2013-02-24 21:53:54 +00:00
namespace MediaBrowser.Server.Implementations.Sqlite
2013-02-21 01:33:05 +00:00
{
/// <summary>
/// Class SQLiteUserDataRepository
/// </summary>
public class SQLiteUserDataRepository : SqliteRepository , IUserDataRepository
{
2013-04-13 18:02:30 +00:00
private readonly ConcurrentDictionary < string , Task < UserItemData > > _userData = new ConcurrentDictionary < string , Task < UserItemData > > ( ) ;
2013-02-21 01:33:05 +00:00
/// <summary>
/// The repository name
/// </summary>
public const string RepositoryName = "SQLite" ;
/// <summary>
/// Gets the name of the repository
/// </summary>
/// <value>The name.</value>
public string Name
{
get
{
return RepositoryName ;
}
}
2013-04-06 01:03:38 +00:00
/// <summary>
/// Gets a value indicating whether [enable delayed commands].
/// </summary>
/// <value><c>true</c> if [enable delayed commands]; otherwise, <c>false</c>.</value>
protected override bool EnableDelayedCommands
{
get
{
return false ;
}
}
2013-04-07 22:09:48 +00:00
private readonly IJsonSerializer _jsonSerializer ;
2013-02-24 21:53:54 +00:00
/// <summary>
/// The _app paths
/// </summary>
private readonly IApplicationPaths _appPaths ;
2013-02-21 20:26:35 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class.
/// </summary>
2013-02-24 21:53:54 +00:00
/// <param name="appPaths">The app paths.</param>
2013-04-07 22:09:48 +00:00
/// <param name="jsonSerializer">The json serializer.</param>
2013-04-03 02:59:27 +00:00
/// <param name="logManager">The log manager.</param>
2013-04-18 19:57:28 +00:00
/// <exception cref="System.ArgumentNullException">
/// jsonSerializer
/// or
/// appPaths
/// </exception>
2013-04-07 22:09:48 +00:00
public SQLiteUserDataRepository ( IApplicationPaths appPaths , IJsonSerializer jsonSerializer , ILogManager logManager )
2013-04-03 02:59:27 +00:00
: base ( logManager )
2013-02-21 20:26:35 +00:00
{
2013-04-07 22:09:48 +00:00
if ( jsonSerializer = = null )
2013-02-24 21:53:54 +00:00
{
2013-04-07 22:09:48 +00:00
throw new ArgumentNullException ( "jsonSerializer" ) ;
2013-02-24 21:53:54 +00:00
}
if ( appPaths = = null )
{
throw new ArgumentNullException ( "appPaths" ) ;
}
2013-04-07 22:09:48 +00:00
_jsonSerializer = jsonSerializer ;
2013-02-24 21:53:54 +00:00
_appPaths = appPaths ;
2013-02-21 20:26:35 +00:00
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
public async Task Initialize ( )
{
2013-02-24 21:53:54 +00:00
var dbFile = Path . Combine ( _appPaths . DataPath , "userdata.db" ) ;
2013-02-21 01:33:05 +00:00
2013-04-15 20:33:43 +00:00
await ConnectToDb ( dbFile ) . ConfigureAwait ( false ) ;
2013-02-21 01:33:05 +00:00
string [ ] queries = {
2013-05-09 13:46:06 +00:00
"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)" ,
2013-02-21 01:33:05 +00:00
"create table if not exists schema_version (table_name primary key, version)" ,
//pragmas
"pragma temp_store = memory"
} ;
RunQueries ( queries ) ;
}
/// <summary>
2013-04-02 19:25:16 +00:00
/// Saves the user data.
2013-02-21 01:33:05 +00:00
/// </summary>
2013-04-02 19:25:16 +00:00
/// <param name="userId">The user id.</param>
2013-04-13 18:02:30 +00:00
/// <param name="key">The key.</param>
2013-04-02 19:25:16 +00:00
/// <param name="userData">The user data.</param>
2013-02-21 01:33:05 +00:00
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
2013-04-13 18:02:30 +00:00
/// <exception cref="System.ArgumentNullException">userData
2013-04-02 19:25:16 +00:00
/// or
/// cancellationToken
/// or
/// userId
/// or
2013-04-13 18:02:30 +00:00
/// userDataId</exception>
public async Task SaveUserData ( Guid userId , string key , UserItemData userData , CancellationToken cancellationToken )
2013-02-21 01:33:05 +00:00
{
2013-04-02 19:25:16 +00:00
if ( userData = = null )
2013-02-21 01:33:05 +00:00
{
2013-04-02 19:25:16 +00:00
throw new ArgumentNullException ( "userData" ) ;
2013-02-21 01:33:05 +00:00
}
if ( cancellationToken = = null )
{
throw new ArgumentNullException ( "cancellationToken" ) ;
}
2013-04-02 19:25:16 +00:00
if ( userId = = Guid . Empty )
{
throw new ArgumentNullException ( "userId" ) ;
}
2013-04-13 18:02:30 +00:00
if ( string . IsNullOrEmpty ( key ) )
2013-04-02 19:25:16 +00:00
{
2013-04-13 18:02:30 +00:00
throw new ArgumentNullException ( "key" ) ;
2013-04-02 19:25:16 +00:00
}
cancellationToken . ThrowIfCancellationRequested ( ) ;
2013-04-13 18:02:30 +00:00
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
2013-04-19 23:43:00 +00:00
_userData . AddOrUpdate ( GetInternalKey ( userId , key ) , newValue , delegate { return newValue ; } ) ;
2013-04-13 18:02:30 +00:00
}
catch ( Exception ex )
{
Logger . ErrorException ( "Error saving user data" , ex ) ;
throw ;
}
}
2013-04-19 23:43:00 +00:00
/// <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 GetInternalKey ( Guid userId , string key )
{
return userId + key ;
}
2013-04-13 18:02:30 +00:00
/// <summary>
/// Persists the user data.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="key">The key.</param>
/// <param name="userData">The user data.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task PersistUserData ( Guid userId , string key , UserItemData userData , CancellationToken cancellationToken )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
2013-05-08 20:58:52 +00:00
using ( var cmd = connection . CreateCommand ( ) )
2013-04-06 01:03:38 +00:00
{
2013-05-09 13:46:06 +00:00
cmd . CommandText = "replace into useritemdata (key, userId, Rating,PlaybackPositionTicks,PlayCount,IsFavorite,Played,LastPlayedDate) values (@1, @2, @3, @4, @5, @6, @7, @8)" ;
2013-05-08 20:58:52 +00:00
cmd . AddParam ( "@1" , key ) ;
cmd . AddParam ( "@2" , userId ) ;
2013-05-09 13:46:06 +00:00
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 ) ;
}
2013-05-08 20:58:52 +00:00
using ( var tran = connection . BeginTransaction ( ) )
2013-04-06 01:03:38 +00:00
{
2013-05-08 20:58:52 +00:00
try
{
cmd . Transaction = tran ;
2013-04-06 01:03:38 +00:00
2013-05-08 20:58:52 +00:00
await cmd . ExecuteNonQueryAsync ( cancellationToken ) ;
2013-04-06 01:03:38 +00:00
2013-05-08 20:58:52 +00:00
tran . Commit ( ) ;
}
catch ( OperationCanceledException )
{
tran . Rollback ( ) ;
}
catch ( Exception e )
{
Logger . ErrorException ( "Failed to commit transaction." , e ) ;
tran . Rollback ( ) ;
}
2013-04-06 01:03:38 +00:00
}
}
2013-02-21 01:33:05 +00:00
}
/// <summary>
2013-04-02 19:25:16 +00:00
/// Gets the user data.
2013-02-21 01:33:05 +00:00
/// </summary>
2013-04-02 19:25:16 +00:00
/// <param name="userId">The user id.</param>
2013-04-13 18:02:30 +00:00
/// <param name="key">The key.</param>
2013-04-02 19:25:16 +00:00
/// <returns>Task{UserItemData}.</returns>
/// <exception cref="System.ArgumentNullException">
/// userId
/// or
2013-04-13 18:02:30 +00:00
/// key
2013-04-02 19:25:16 +00:00
/// </exception>
2013-04-13 18:02:30 +00:00
public Task < UserItemData > GetUserData ( Guid userId , string key )
2013-02-21 01:33:05 +00:00
{
2013-04-02 19:25:16 +00:00
if ( userId = = Guid . Empty )
2013-02-21 01:33:05 +00:00
{
2013-04-02 19:25:16 +00:00
throw new ArgumentNullException ( "userId" ) ;
}
2013-04-13 18:02:30 +00:00
if ( string . IsNullOrEmpty ( key ) )
2013-04-02 19:25:16 +00:00
{
2013-04-13 18:02:30 +00:00
throw new ArgumentNullException ( "key" ) ;
2013-02-21 01:33:05 +00:00
}
2013-04-13 18:02:30 +00:00
2013-04-19 23:43:00 +00:00
return _userData . GetOrAdd ( GetInternalKey ( userId , key ) , keyName = > RetrieveUserData ( userId , key ) ) ;
2013-04-13 18:02:30 +00:00
}
2013-02-21 01:33:05 +00:00
2013-04-13 18:02:30 +00:00
/// <summary>
/// Retrieves the user data.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="key">The key.</param>
/// <returns>Task{UserItemData}.</returns>
private async Task < UserItemData > RetrieveUserData ( Guid userId , string key )
{
2013-05-08 20:58:52 +00:00
using ( var cmd = connection . CreateCommand ( ) )
{
2013-05-09 13:46:06 +00:00
cmd . CommandText = "select Rating,PlaybackPositionTicks,PlayCount,IsFavorite,Played,LastPlayedDate from useritemdata where key = @key and userId=@userId" ;
2013-04-02 19:25:16 +00:00
2013-05-08 20:58:52 +00:00
var idParam = cmd . Parameters . Add ( "@key" , DbType . String ) ;
idParam . Value = key ;
2013-02-21 01:33:05 +00:00
2013-05-08 20:58:52 +00:00
var userIdParam = cmd . Parameters . Add ( "@userId" , DbType . Guid ) ;
userIdParam . Value = userId ;
2013-04-02 19:25:16 +00:00
2013-05-09 13:46:06 +00:00
var userdata = new UserItemData ( ) ;
2013-05-08 20:58:52 +00:00
using ( var reader = await cmd . ExecuteReaderAsync ( CommandBehavior . SequentialAccess | CommandBehavior . SingleResult | CommandBehavior . SingleRow ) . ConfigureAwait ( false ) )
2013-02-21 01:33:05 +00:00
{
2013-05-08 20:58:52 +00:00
if ( reader . Read ( ) )
2013-02-21 01:33:05 +00:00
{
2013-05-09 13:46:06 +00:00
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 )
2013-05-08 20:58:52 +00:00
{
2013-05-09 13:46:06 +00:00
userdata . LastPlayedDate = new DateTime ( ticks . Value ) ;
2013-05-08 20:58:52 +00:00
}
2013-02-21 01:33:05 +00:00
}
}
2013-04-02 19:25:16 +00:00
2013-05-09 13:46:06 +00:00
return userdata ;
2013-05-08 20:58:52 +00:00
}
2013-02-21 01:33:05 +00:00
}
}
}