using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Server.Implementations.Reflection;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Sqlite
{
///
/// Class SQLiteItemRepository
///
public class SQLiteItemRepository : SqliteRepository, IItemRepository
{
///
/// The _type mapper
///
private readonly TypeMapper _typeMapper = new TypeMapper();
///
/// The repository name
///
public const string RepositoryName = "SQLite";
///
/// Gets the name of the repository
///
/// The name.
public string Name
{
get
{
return RepositoryName;
}
}
///
/// Gets the json serializer.
///
/// The json serializer.
private readonly IJsonSerializer _jsonSerializer;
///
/// The _app paths
///
private readonly IApplicationPaths _appPaths;
///
/// The _save item command
///
private SQLiteCommand _saveItemCommand;
///
/// The _delete children command
///
private SQLiteCommand _deleteChildrenCommand;
///
/// The _save children command
///
private SQLiteCommand _saveChildrenCommand;
///
/// Initializes a new instance of the class.
///
/// The app paths.
/// The json serializer.
/// The log manager.
/// appPaths
public SQLiteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
: base(logManager)
{
if (appPaths == null)
{
throw new ArgumentNullException("appPaths");
}
if (jsonSerializer == null)
{
throw new ArgumentNullException("jsonSerializer");
}
_appPaths = appPaths;
_jsonSerializer = jsonSerializer;
}
///
/// Opens the connection to the database
///
/// Task.
public async Task Initialize()
{
var dbFile = Path.Combine(_appPaths.DataPath, "library.db");
await ConnectToDb(dbFile).ConfigureAwait(false);
string[] queries = {
"create table if not exists items (guid GUID primary key, obj_type, data BLOB)",
"create index if not exists idx_items on items(guid)",
"create table if not exists children (guid GUID, child GUID)",
"create unique index if not exists idx_children on children(guid, child)",
"create table if not exists schema_version (table_name primary key, version)",
//triggers
TriggerSql,
//pragmas
"pragma temp_store = memory"
};
RunQueries(queries);
PrepareStatements();
}
//cascade delete triggers
///
/// The trigger SQL
///
protected string TriggerSql =
@"CREATE TRIGGER if not exists delete_item
AFTER DELETE
ON items
FOR EACH ROW
BEGIN
DELETE FROM children WHERE children.guid = old.child;
DELETE FROM children WHERE children.child = old.child;
END";
///
/// The _write lock
///
private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1,1);
///
/// Prepares the statements.
///
private void PrepareStatements()
{
_saveItemCommand = new SQLiteCommand
{
CommandText = "replace into items (guid, obj_type, data) values (@1, @2, @3)"
};
_saveItemCommand.Parameters.Add(new SQLiteParameter("@1"));
_saveItemCommand.Parameters.Add(new SQLiteParameter("@2"));
_saveItemCommand.Parameters.Add(new SQLiteParameter("@3"));
_deleteChildrenCommand = new SQLiteCommand
{
CommandText = "delete from children where guid = @guid"
};
_deleteChildrenCommand.Parameters.Add(new SQLiteParameter("@guid"));
_saveChildrenCommand = new SQLiteCommand
{
CommandText = "replace into children (guid, child) values (@guid, @child)"
};
_saveChildrenCommand.Parameters.Add(new SQLiteParameter("@guid"));
_saveChildrenCommand.Parameters.Add(new SQLiteParameter("@child"));
}
///
/// Save a standard item in the repo
///
/// The item.
/// The cancellation token.
/// Task.
/// item
public async Task SaveItem(BaseItem item, CancellationToken cancellationToken)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
if (cancellationToken == null)
{
throw new ArgumentNullException("cancellationToken");
}
cancellationToken.ThrowIfCancellationRequested();
var serialized = _jsonSerializer.SerializeToBytes(item);
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
SQLiteTransaction transaction = null;
try
{
transaction = Connection.BeginTransaction();
_saveItemCommand.Parameters[0].Value = item.Id;
_saveItemCommand.Parameters[1].Value = item.GetType().FullName;
_saveItemCommand.Parameters[2].Value = serialized;
_saveItemCommand.Transaction = transaction;
await _saveItemCommand.ExecuteNonQueryAsync(cancellationToken);
transaction.Commit();
}
catch (OperationCanceledException)
{
if (transaction != null)
{
transaction.Rollback();
}
}
catch (Exception e)
{
Logger.ErrorException("Failed to save item:", e);
if (transaction != null)
{
transaction.Rollback();
}
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
_writeLock.Release();
}
}
///
/// Retrieve a standard item from the repo
///
/// The id.
/// BaseItem.
/// id
///
public BaseItem GetItem(Guid id)
{
if (id == Guid.Empty)
{
throw new ArgumentNullException("id");
}
return RetrieveItemInternal(id);
}
///
/// Retrieves the items.
///
/// The ids.
/// IEnumerable{BaseItem}.
/// ids
public IEnumerable GetItems(IEnumerable ids)
{
if (ids == null)
{
throw new ArgumentNullException("ids");
}
return ids.Select(RetrieveItemInternal);
}
///
/// Internal retrieve from items or users table
///
/// The id.
/// BaseItem.
/// id
///
protected BaseItem RetrieveItemInternal(Guid id)
{
if (id == Guid.Empty)
{
throw new ArgumentNullException("id");
}
using (var cmd = Connection.CreateCommand())
{
cmd.CommandText = "select obj_type,data from items where guid = @guid";
var guidParam = cmd.Parameters.Add("@guid", DbType.Guid);
guidParam.Value = id;
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
if (reader.Read())
{
var type = reader.GetString(0);
using (var stream = GetStream(reader, 1))
{
var itemType = _typeMapper.GetType(type);
if (itemType == null)
{
Logger.Error("Cannot find type {0}. Probably belongs to plug-in that is no longer loaded.", type);
return null;
}
var item = _jsonSerializer.DeserializeFromStream(stream, itemType);
return item as BaseItem;
}
}
}
return null;
}
}
///
/// Retrieve all the children of the given folder
///
/// The parent.
/// IEnumerable{BaseItem}.
///
public IEnumerable RetrieveChildren(Folder parent)
{
if (parent == null)
{
throw new ArgumentNullException();
}
using (var cmd = Connection.CreateCommand())
{
cmd.CommandText = "select obj_type,data from items where guid in (select child from children where guid = @guid)";
var guidParam = cmd.Parameters.Add("@guid", DbType.Guid);
guidParam.Value = parent.Id;
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
{
while (reader.Read())
{
var type = reader.GetString(0);
using (var stream = GetStream(reader, 1))
{
var itemType = _typeMapper.GetType(type);
if (itemType == null)
{
Logger.Error("Cannot find type {0}. Probably belongs to plug-in that is no longer loaded.", type);
continue;
}
var item = _jsonSerializer.DeserializeFromStream(stream, itemType) as BaseItem;
if (item != null)
{
item.Parent = parent;
yield return item;
}
}
}
}
}
}
///
/// Save references to all the children for the given folder
/// (Doesn't actually save the child entities)
///
/// The id.
/// The children.
/// The cancellation token.
/// Task.
/// id
public async Task SaveChildren(Guid id, IEnumerable children, CancellationToken cancellationToken)
{
if (id == Guid.Empty)
{
throw new ArgumentNullException("id");
}
if (children == null)
{
throw new ArgumentNullException("children");
}
if (cancellationToken == null)
{
throw new ArgumentNullException("cancellationToken");
}
cancellationToken.ThrowIfCancellationRequested();
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
SQLiteTransaction transaction = null;
try
{
transaction = Connection.BeginTransaction();
// Delete exising children
_deleteChildrenCommand.Parameters[0].Value = id;
_deleteChildrenCommand.Transaction = transaction;
await _deleteChildrenCommand.ExecuteNonQueryAsync(cancellationToken);
// Save new children
foreach (var child in children)
{
_saveChildrenCommand.Transaction = transaction;
_saveChildrenCommand.Parameters[0].Value = id;
_saveChildrenCommand.Parameters[1].Value = child.Id;
await _saveChildrenCommand.ExecuteNonQueryAsync(cancellationToken);
}
transaction.Commit();
}
catch (OperationCanceledException)
{
if (transaction != null)
{
transaction.Rollback();
}
}
catch (Exception e)
{
Logger.ErrorException("Failed to save item:", e);
if (transaction != null)
{
transaction.Rollback();
}
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
_writeLock.Release();
}
}
///
/// Gets the critic reviews path.
///
/// The critic reviews path.
private string CriticReviewsPath
{
get
{
var path = Path.Combine(_appPaths.DataPath, "critic-reviews");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return path;
}
}
///
/// Gets the critic reviews.
///
/// The item id.
/// Task{IEnumerable{ItemReview}}.
public Task> GetCriticReviews(Guid itemId)
{
return Task.Run>(() =>
{
try
{
var path = Path.Combine(CriticReviewsPath, itemId + ".json");
return _jsonSerializer.DeserializeFromFile>(path);
}
catch (FileNotFoundException)
{
return new List();
}
});
}
///
/// Saves the critic reviews.
///
/// The item id.
/// The critic reviews.
/// Task.
public Task SaveCriticReviews(Guid itemId, IEnumerable criticReviews)
{
return Task.Run(() =>
{
var path = Path.Combine(CriticReviewsPath, itemId + ".json");
_jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
});
}
}
}