mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-03 00:41:55 +01:00
isolated sqlite dependancy
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Common.Kernel;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Kernel;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -6,7 +7,6 @@ using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaInfo;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Persistence.SQLite;
|
||||
using MediaBrowser.Controller.Playback;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
@@ -19,10 +19,7 @@ using MediaBrowser.Model.System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -303,8 +300,8 @@ namespace MediaBrowser.Controller
|
||||
/// <summary>
|
||||
/// Creates a kernel based on a Data path, which is akin to our current programdata path
|
||||
/// </summary>
|
||||
public Kernel()
|
||||
: base()
|
||||
public Kernel(IIsoManager isoManager)
|
||||
: base(isoManager)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
@@ -385,19 +382,19 @@ namespace MediaBrowser.Controller
|
||||
await base.OnComposablePartsLoaded().ConfigureAwait(false);
|
||||
|
||||
// Get the current item repository
|
||||
ItemRepository = GetRepository(ItemRepositories, Configuration.ItemRepository, SQLiteItemRepository.RepositoryName);
|
||||
ItemRepository = GetRepository(ItemRepositories, Configuration.ItemRepository);
|
||||
var itemRepoTask = ItemRepository.Initialize();
|
||||
|
||||
// Get the current user repository
|
||||
UserRepository = GetRepository(UserRepositories, Configuration.UserRepository, SQLiteUserRepository.RepositoryName);
|
||||
UserRepository = GetRepository(UserRepositories, Configuration.UserRepository);
|
||||
var userRepoTask = UserRepository.Initialize();
|
||||
|
||||
// Get the current item repository
|
||||
UserDataRepository = GetRepository(UserDataRepositories, Configuration.UserDataRepository, SQLiteUserDataRepository.RepositoryName);
|
||||
UserDataRepository = GetRepository(UserDataRepositories, Configuration.UserDataRepository);
|
||||
var userDataRepoTask = UserDataRepository.Initialize();
|
||||
|
||||
// Get the current display preferences repository
|
||||
DisplayPreferencesRepository = GetRepository(DisplayPreferencesRepositories, Configuration.DisplayPreferencesRepository, SQLiteDisplayPreferencesRepository.RepositoryName);
|
||||
DisplayPreferencesRepository = GetRepository(DisplayPreferencesRepositories, Configuration.DisplayPreferencesRepository);
|
||||
var displayPreferencesRepoTask = DisplayPreferencesRepository.Initialize();
|
||||
|
||||
// Sort the resolvers by priority
|
||||
@@ -418,15 +415,14 @@ namespace MediaBrowser.Controller
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repositories">The repositories.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="defaultName">The default name.</param>
|
||||
/// <returns>``0.</returns>
|
||||
private T GetRepository<T>(IEnumerable<T> repositories, string name, string defaultName)
|
||||
private T GetRepository<T>(IEnumerable<T> repositories, string name)
|
||||
where T : class, IRepository
|
||||
{
|
||||
var enumerable = repositories as T[] ?? repositories.ToArray();
|
||||
|
||||
return enumerable.FirstOrDefault(r => r.Name.Equals(name ?? defaultName, StringComparison.OrdinalIgnoreCase)) ??
|
||||
enumerable.First(r => r.Name.Equals(defaultName, StringComparison.OrdinalIgnoreCase));
|
||||
return enumerable.FirstOrDefault(r => string.Equals(r.Name, name, StringComparison.OrdinalIgnoreCase)) ??
|
||||
enumerable.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.SQLite, Version=1.0.84.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\System.Data.SQLite.1.0.84.0\lib\net45\System.Data.SQLite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SQLite.Linq, Version=1.0.84.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\System.Data.SQLite.1.0.84.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net" />
|
||||
@@ -149,12 +141,6 @@
|
||||
<Compile Include="Persistence\IRepository.cs" />
|
||||
<Compile Include="Persistence\IUserDataRepository.cs" />
|
||||
<Compile Include="Persistence\IUserRepository.cs" />
|
||||
<Compile Include="Persistence\SQLite\SQLiteDisplayPreferencesRepository.cs" />
|
||||
<Compile Include="Persistence\SQLite\SQLiteExtensions.cs" />
|
||||
<Compile Include="Persistence\SQLite\SQLiteItemRepository.cs" />
|
||||
<Compile Include="Persistence\SQLite\SQLiteRepository.cs" />
|
||||
<Compile Include="Persistence\SQLite\SQLiteUserDataRepository.cs" />
|
||||
<Compile Include="Persistence\SQLite\SQLiteUserRepository.cs" />
|
||||
<Compile Include="Persistence\TypeMapper.cs" />
|
||||
<Compile Include="Playback\BaseIntroProvider.cs" />
|
||||
<Compile Include="Plugins\BaseConfigurationPage.cs" />
|
||||
@@ -251,12 +237,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="MediaInfo\readme.txt" />
|
||||
<Content Include="x64\SQLite.Interop.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="x86\SQLite.Interop.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Persistence.SQLite
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteDisplayPreferencesRepository
|
||||
/// </summary>
|
||||
[Export(typeof(IDisplayPreferencesRepository))]
|
||||
class SQLiteDisplayPreferencesRepository : SqliteRepository, IDisplayPreferencesRepository
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Initialize()
|
||||
{
|
||||
var dbFile = Path.Combine(Kernel.Instance.ApplicationPaths.DataPath, "displaypreferences.db");
|
||||
|
||||
await ConnectToDB(dbFile).ConfigureAwait(false);
|
||||
|
||||
string[] queries = {
|
||||
|
||||
"create table if not exists display_prefs (item_id GUID, user_id GUID, data BLOB)",
|
||||
"create unique index if not exists idx_display_prefs on display_prefs (item_id, user_id)",
|
||||
"create table if not exists schema_version (table_name primary key, version)",
|
||||
//pragmas
|
||||
"pragma temp_store = memory"
|
||||
};
|
||||
|
||||
RunQueries(queries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the display preferences associated with an item in the repo
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
public Task SaveDisplayPrefs(Folder item, CancellationToken cancellationToken)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var cmd = connection.CreateCommand();
|
||||
|
||||
cmd.CommandText = "delete from display_prefs where item_id = @guid";
|
||||
cmd.AddParam("@guid", item.DisplayPrefsId);
|
||||
|
||||
QueueCommand(cmd);
|
||||
|
||||
if (item.DisplayPrefs != null)
|
||||
{
|
||||
foreach (var data in item.DisplayPrefs)
|
||||
{
|
||||
cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "insert into display_prefs (item_id, user_id, data) values (@1, @2, @3)";
|
||||
cmd.AddParam("@1", item.DisplayPrefsId);
|
||||
cmd.AddParam("@2", data.UserId);
|
||||
|
||||
cmd.AddParam("@3", Kernel.Instance.ProtobufSerializer.SerializeToBytes(data));
|
||||
|
||||
QueueCommand(cmd);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets display preferences for an item
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>IEnumerable{DisplayPreferences}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public IEnumerable<DisplayPreferences> RetrieveDisplayPrefs(Folder item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "select data from display_prefs where item_id = @guid";
|
||||
var guidParam = cmd.Parameters.Add("@guid", DbType.Guid);
|
||||
guidParam.Value = item.DisplayPrefsId;
|
||||
|
||||
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
using (var stream = GetStream(reader, 0))
|
||||
{
|
||||
var data = Kernel.Instance.ProtobufSerializer.DeserializeFromStream<DisplayPreferences>(stream);
|
||||
if (data != null)
|
||||
{
|
||||
yield return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
|
||||
namespace MediaBrowser.Controller.Persistence.SQLite
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteExtensions
|
||||
/// </summary>
|
||||
static class SQLiteExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the param.
|
||||
/// </summary>
|
||||
/// <param name="cmd">The CMD.</param>
|
||||
/// <param name="param">The param.</param>
|
||||
/// <returns>SQLiteParameter.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public static SQLiteParameter AddParam(this SQLiteCommand cmd, string param)
|
||||
{
|
||||
if (string.IsNullOrEmpty(param))
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
var sqliteParam = new SQLiteParameter(param);
|
||||
cmd.Parameters.Add(sqliteParam);
|
||||
return sqliteParam;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the param.
|
||||
/// </summary>
|
||||
/// <param name="cmd">The CMD.</param>
|
||||
/// <param name="param">The param.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>SQLiteParameter.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public static SQLiteParameter AddParam(this SQLiteCommand cmd, string param, object data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(param))
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
var sqliteParam = AddParam(cmd, param);
|
||||
sqliteParam.Value = data;
|
||||
return sqliteParam;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified conn is open.
|
||||
/// </summary>
|
||||
/// <param name="conn">The conn.</param>
|
||||
/// <returns><c>true</c> if the specified conn is open; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsOpen(this SQLiteConnection conn)
|
||||
{
|
||||
return conn.State == ConnectionState.Open;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
using MediaBrowser.Common.Serialization;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Persistence.SQLite
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteItemRepository
|
||||
/// </summary>
|
||||
[Export(typeof(IItemRepository))]
|
||||
public class SQLiteItemRepository : SqliteRepository, IItemRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The _type mapper
|
||||
/// </summary>
|
||||
private readonly TypeMapper _typeMapper = new TypeMapper();
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Initialize()
|
||||
{
|
||||
var dbFile = Path.Combine(Kernel.Instance.ApplicationPaths.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);
|
||||
}
|
||||
|
||||
//cascade delete triggers
|
||||
/// <summary>
|
||||
/// The trigger SQL
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Save a standard item in the repo
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
public Task SaveItem(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var serialized = JsonSerializer.SerializeToBytes(item);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "replace into items (guid, obj_type, data) values (@1, @2, @3)";
|
||||
cmd.AddParam("@1", item.Id);
|
||||
cmd.AddParam("@2", item.GetType().FullName);
|
||||
cmd.AddParam("@3", serialized);
|
||||
QueueCommand(cmd);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a standard item from the repo
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
/// <exception cref="System.ArgumentException"></exception>
|
||||
public BaseItem RetrieveItem(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
return RetrieveItemInternal(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal retrieve from items or users table
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
/// <exception cref="System.ArgumentException"></exception>
|
||||
protected BaseItem RetrieveItemInternal(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all the children of the given folder
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent.</param>
|
||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public IEnumerable<BaseItem> RetrieveChildren(Folder parent)
|
||||
{
|
||||
if (parent == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save references to all the children for the given folder
|
||||
/// (Doesn't actually save the child entities)
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <param name="children">The children.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">id</exception>
|
||||
public Task SaveChildren(Guid id, IEnumerable<BaseItem> 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();
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var cmd = connection.CreateCommand();
|
||||
|
||||
cmd.CommandText = "delete from children where guid = @guid";
|
||||
cmd.AddParam("@guid", id);
|
||||
|
||||
QueueCommand(cmd);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
var guid = child.Id;
|
||||
cmd = connection.CreateCommand();
|
||||
cmd.AddParam("@guid", id);
|
||||
cmd.CommandText = "replace into children (guid, child) values (@guid, @child)";
|
||||
var childParam = cmd.Parameters.Add("@child", DbType.Guid);
|
||||
|
||||
childParam.Value = guid;
|
||||
QueueCommand(cmd);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
using MediaBrowser.Common.Logging;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Persistence.SQLite
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SqliteRepository
|
||||
/// </summary>
|
||||
public abstract class SqliteRepository : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The db file name
|
||||
/// </summary>
|
||||
protected string dbFileName;
|
||||
/// <summary>
|
||||
/// The connection
|
||||
/// </summary>
|
||||
protected SQLiteConnection connection;
|
||||
/// <summary>
|
||||
/// The delayed commands
|
||||
/// </summary>
|
||||
protected ConcurrentQueue<SQLiteCommand> delayedCommands = new ConcurrentQueue<SQLiteCommand>();
|
||||
/// <summary>
|
||||
/// The flush interval
|
||||
/// </summary>
|
||||
private const int FlushInterval = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// The flush timer
|
||||
/// </summary>
|
||||
private Timer FlushTimer;
|
||||
|
||||
protected ILogger Logger { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Connects to DB.
|
||||
/// </summary>
|
||||
/// <param name="dbPath">The db path.</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
protected async Task ConnectToDB(string dbPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dbPath))
|
||||
{
|
||||
throw new ArgumentNullException("dbPath");
|
||||
}
|
||||
|
||||
Logger = LogManager.GetLogger(GetType().Name);
|
||||
|
||||
dbFileName = dbPath;
|
||||
var connectionstr = new SQLiteConnectionStringBuilder
|
||||
{
|
||||
PageSize = 4096,
|
||||
CacheSize = 40960,
|
||||
SyncMode = SynchronizationModes.Off,
|
||||
DataSource = dbPath,
|
||||
JournalMode = SQLiteJournalModeEnum.Memory
|
||||
};
|
||||
|
||||
connection = new SQLiteConnection(connectionstr.ConnectionString);
|
||||
|
||||
await connection.OpenAsync().ConfigureAwait(false);
|
||||
|
||||
// Run once
|
||||
FlushTimer = new Timer(Flush, null, TimeSpan.FromMilliseconds(FlushInterval), TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the queries.
|
||||
/// </summary>
|
||||
/// <param name="queries">The queries.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
protected void RunQueries(string[] queries)
|
||||
{
|
||||
if (queries == null)
|
||||
{
|
||||
throw new ArgumentNullException("queries");
|
||||
}
|
||||
|
||||
using (var tran = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
var cmd = connection.CreateCommand();
|
||||
|
||||
foreach (var query in queries)
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = query;
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
tran.Commit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorException("Error running queries", e);
|
||||
tran.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
Logger.Info("Disposing " + GetType().Name);
|
||||
|
||||
try
|
||||
{
|
||||
// If we're not already flushing, do it now
|
||||
if (!IsFlushing)
|
||||
{
|
||||
Flush(null);
|
||||
}
|
||||
|
||||
// Don't dispose in the middle of a flush
|
||||
while (IsFlushing)
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
if (FlushTimer != null)
|
||||
{
|
||||
FlushTimer.Dispose();
|
||||
FlushTimer = null;
|
||||
}
|
||||
|
||||
if (connection.IsOpen())
|
||||
{
|
||||
connection.Close();
|
||||
}
|
||||
|
||||
connection.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException("Error disposing database", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues the command.
|
||||
/// </summary>
|
||||
/// <param name="cmd">The CMD.</param>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
protected void QueueCommand(SQLiteCommand cmd)
|
||||
{
|
||||
if (cmd == null)
|
||||
{
|
||||
throw new ArgumentNullException("cmd");
|
||||
}
|
||||
|
||||
delayedCommands.Enqueue(cmd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The is flushing
|
||||
/// </summary>
|
||||
private bool IsFlushing;
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the specified sender.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
private void Flush(object sender)
|
||||
{
|
||||
// Cannot call Count on a ConcurrentQueue since it's an O(n) operation
|
||||
// Use IsEmpty instead
|
||||
if (delayedCommands.IsEmpty)
|
||||
{
|
||||
FlushTimer.Change(TimeSpan.FromMilliseconds(FlushInterval), TimeSpan.FromMilliseconds(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsFlushing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsFlushing = true;
|
||||
var numCommands = 0;
|
||||
|
||||
using (var tran = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!delayedCommands.IsEmpty)
|
||||
{
|
||||
SQLiteCommand command;
|
||||
|
||||
delayedCommands.TryDequeue(out command);
|
||||
|
||||
command.Connection = connection;
|
||||
command.Transaction = tran;
|
||||
|
||||
command.ExecuteNonQuery();
|
||||
numCommands++;
|
||||
}
|
||||
|
||||
tran.Commit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorException("Failed to commit transaction.", e);
|
||||
tran.Rollback();
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info("SQL Delayed writer executed " + numCommands + " commands");
|
||||
|
||||
FlushTimer.Change(TimeSpan.FromMilliseconds(FlushInterval), TimeSpan.FromMilliseconds(-1));
|
||||
IsFlushing = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command.
|
||||
/// </summary>
|
||||
/// <param name="cmd">The CMD.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public async Task ExecuteCommand(DbCommand cmd)
|
||||
{
|
||||
if (cmd == null)
|
||||
{
|
||||
throw new ArgumentNullException("cmd");
|
||||
}
|
||||
|
||||
using (var tran = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
cmd.Connection = connection;
|
||||
cmd.Transaction = tran;
|
||||
|
||||
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||
|
||||
tran.Commit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorException("Failed to commit transaction.", e);
|
||||
tran.Rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a stream from a DataReader at a given ordinal
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader.</param>
|
||||
/// <param name="ordinal">The ordinal.</param>
|
||||
/// <returns>Stream.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
protected static Stream GetStream(IDataReader reader, int ordinal)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
var num = 0L;
|
||||
var array = new byte[4096];
|
||||
long bytes;
|
||||
do
|
||||
{
|
||||
bytes = reader.GetBytes(ordinal, num, array, 0, array.Length);
|
||||
memoryStream.Write(array, 0, (int)bytes);
|
||||
num += bytes;
|
||||
}
|
||||
while (bytes > 0L);
|
||||
memoryStream.Position = 0;
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Persistence.SQLite
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteUserDataRepository
|
||||
/// </summary>
|
||||
[Export(typeof(IUserDataRepository))]
|
||||
public class SQLiteUserDataRepository : SqliteRepository, IUserDataRepository
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Initialize()
|
||||
{
|
||||
var dbFile = Path.Combine(Kernel.Instance.ApplicationPaths.DataPath, "userdata.db");
|
||||
|
||||
await ConnectToDB(dbFile).ConfigureAwait(false);
|
||||
|
||||
string[] queries = {
|
||||
|
||||
"create table if not exists user_data (item_id GUID, user_id GUID, data BLOB)",
|
||||
"create unique index if not exists idx_user_data on user_data (item_id, user_id)",
|
||||
"create table if not exists schema_version (table_name primary key, version)",
|
||||
//pragmas
|
||||
"pragma temp_store = memory"
|
||||
};
|
||||
|
||||
RunQueries(queries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the user specific data associated with an item in the repo
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
public Task SaveUserData(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var cmd = connection.CreateCommand();
|
||||
|
||||
cmd.CommandText = "delete from user_data where item_id = @guid";
|
||||
cmd.AddParam("@guid", item.UserDataId);
|
||||
|
||||
QueueCommand(cmd);
|
||||
|
||||
if (item.UserData != null)
|
||||
{
|
||||
foreach (var data in item.UserData)
|
||||
{
|
||||
cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "insert into user_data (item_id, user_id, data) values (@1, @2, @3)";
|
||||
cmd.AddParam("@1", item.UserDataId);
|
||||
cmd.AddParam("@2", data.UserId);
|
||||
|
||||
cmd.AddParam("@3", Kernel.Instance.ProtobufSerializer.SerializeToBytes(data));
|
||||
|
||||
QueueCommand(cmd);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets user data for an item
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>IEnumerable{UserItemData}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public IEnumerable<UserItemData> RetrieveUserData(BaseItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "select data from user_data where item_id = @guid";
|
||||
var guidParam = cmd.Parameters.Add("@guid", DbType.Guid);
|
||||
guidParam.Value = item.UserDataId;
|
||||
|
||||
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
using (var stream = GetStream(reader, 0))
|
||||
{
|
||||
var data = Kernel.Instance.ProtobufSerializer.DeserializeFromStream<UserItemData>(stream);
|
||||
if (data != null)
|
||||
{
|
||||
yield return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Serialization;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Persistence.SQLite
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteUserRepository
|
||||
/// </summary>
|
||||
[Export(typeof(IUserRepository))]
|
||||
public class SQLiteUserRepository : SqliteRepository, IUserRepository
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Initialize()
|
||||
{
|
||||
var dbFile = Path.Combine(Kernel.Instance.ApplicationPaths.DataPath, "users.db");
|
||||
|
||||
await ConnectToDB(dbFile).ConfigureAwait(false);
|
||||
|
||||
string[] queries = {
|
||||
|
||||
"create table if not exists users (guid GUID primary key, data BLOB)",
|
||||
"create index if not exists idx_users on users(guid)",
|
||||
"create table if not exists schema_version (table_name primary key, version)",
|
||||
//pragmas
|
||||
"pragma temp_store = memory"
|
||||
};
|
||||
|
||||
RunQueries(queries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a user in the repo
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">user</exception>
|
||||
public Task SaveUser(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException("user");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var serialized = JsonSerializer.SerializeToBytes(user);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "replace into users (guid, data) values (@1, @2)";
|
||||
cmd.AddParam("@1", user.Id);
|
||||
cmd.AddParam("@2", serialized);
|
||||
QueueCommand(cmd);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all users from the database
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{User}.</returns>
|
||||
public IEnumerable<User> RetrieveAllUsers()
|
||||
{
|
||||
var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "select data from users";
|
||||
|
||||
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
using (var stream = GetStream(reader, 0))
|
||||
{
|
||||
var user = JsonSerializer.DeserializeFromStream<User>(stream);
|
||||
yield return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">user</exception>
|
||||
public Task DeleteUser(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException("user");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "delete from users where guid=@guid";
|
||||
var guidParam = cmd.Parameters.Add("@guid", DbType.Guid);
|
||||
guidParam.Value = user.Id;
|
||||
|
||||
return ExecuteCommand(cmd);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,4 @@
|
||||
<package id="Rx-Core" version="2.0.21114" targetFramework="net45" />
|
||||
<package id="Rx-Interfaces" version="2.0.21114" targetFramework="net45" />
|
||||
<package id="Rx-Linq" version="2.0.21114" targetFramework="net45" />
|
||||
<package id="System.Data.SQLite" version="1.0.84.0" targetFramework="net45" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user