sqlite
This commit is contained in:
parent
e677a57bf1
commit
e56433a0ef
|
@ -82,6 +82,12 @@
|
|||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.SQLite">
|
||||
<HintPath>..\packages\System.Data.SQLite.x86.1.0.86.0\lib\net45\System.Data.SQLite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SQLite.Linq">
|
||||
<HintPath>..\packages\System.Data.SQLite.x86.1.0.86.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Reactive.Core">
|
||||
<HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -91,6 +97,7 @@
|
|||
<Reference Include="System.Reactive.Linq">
|
||||
<HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
|
@ -132,6 +139,8 @@
|
|||
<Compile Include="Library\UserManager.cs" />
|
||||
<Compile Include="Localization\LocalizationManager.cs" />
|
||||
<Compile Include="MediaEncoder\MediaEncoder.cs" />
|
||||
<Compile Include="Persistence\SqliteExtensions.cs" />
|
||||
<Compile Include="Persistence\SqliteRepository.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Providers\ProviderManager.cs" />
|
||||
<Compile Include="ScheduledTasks\ArtistValidationTask.cs" />
|
||||
|
@ -162,10 +171,10 @@
|
|||
<Compile Include="Sorting\RevenueComparer.cs" />
|
||||
<Compile Include="Sorting\RuntimeComparer.cs" />
|
||||
<Compile Include="Sorting\SortNameComparer.cs" />
|
||||
<Compile Include="Persistence\JsonDisplayPreferencesRepository.cs" />
|
||||
<Compile Include="Persistence\JsonItemRepository.cs" />
|
||||
<Compile Include="Persistence\JsonUserDataRepository.cs" />
|
||||
<Compile Include="Persistence\JsonUserRepository.cs" />
|
||||
<Compile Include="Persistence\SqliteDisplayPreferencesRepository.cs" />
|
||||
<Compile Include="Persistence\SqliteItemRepository.cs" />
|
||||
<Compile Include="Persistence\SqliteUserDataRepository.cs" />
|
||||
<Compile Include="Persistence\SqliteUserRepository.cs" />
|
||||
<Compile Include="Udp\UdpMessageReceivedEventArgs.cs" />
|
||||
<Compile Include="Udp\UdpServer.cs" />
|
||||
<Compile Include="Updates\InstallationManager.cs" />
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Persistence
|
||||
{
|
||||
public class JsonDisplayPreferencesRepository : IDisplayPreferencesRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
private SemaphoreSlim GetLock(string filename)
|
||||
{
|
||||
return _fileLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Json";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _json serializer
|
||||
/// </summary>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
private readonly string _dataPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonUserDataRepository" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// jsonSerializer
|
||||
/// or
|
||||
/// appPaths
|
||||
/// </exception>
|
||||
public JsonDisplayPreferencesRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
|
||||
{
|
||||
if (jsonSerializer == null)
|
||||
{
|
||||
throw new ArgumentNullException("jsonSerializer");
|
||||
}
|
||||
if (appPaths == null)
|
||||
{
|
||||
throw new ArgumentNullException("appPaths");
|
||||
}
|
||||
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_dataPath = Path.Combine(appPaths.DataPath, "display-preferences");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public Task Initialize()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the display preferences associated with an item in the repo
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, CancellationToken cancellationToken)
|
||||
{
|
||||
if (displayPreferences == null)
|
||||
{
|
||||
throw new ArgumentNullException("displayPreferences");
|
||||
}
|
||||
if (displayPreferences.Id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("displayPreferences.Id");
|
||||
}
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!Directory.Exists(_dataPath))
|
||||
{
|
||||
Directory.CreateDirectory(_dataPath);
|
||||
}
|
||||
|
||||
var path = Path.Combine(_dataPath, displayPreferences.Id + ".json");
|
||||
|
||||
var semaphore = GetLock(path);
|
||||
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
_jsonSerializer.SerializeToFile(displayPreferences, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display preferences.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferencesId">The display preferences id.</param>
|
||||
/// <returns>Task{DisplayPreferences}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
public Task<DisplayPreferences> GetDisplayPreferences(Guid displayPreferencesId)
|
||||
{
|
||||
if (displayPreferencesId == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("displayPreferencesId");
|
||||
}
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var path = Path.Combine(_dataPath, displayPreferencesId + ".json");
|
||||
|
||||
try
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromFile<DisplayPreferences>(path);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// File doesn't exist or is currently bring written to
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Wait up to two seconds for any existing writes to finish
|
||||
var locks = _fileLocks.Values.ToList()
|
||||
.Where(i => i.CurrentCount == 1)
|
||||
.Select(i => i.WaitAsync(2000));
|
||||
|
||||
var task = Task.WhenAll(locks);
|
||||
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Persistence
|
||||
{
|
||||
public class JsonItemRepository : IItemRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
private SemaphoreSlim GetLock(string filename)
|
||||
{
|
||||
return _fileLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Json";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the json serializer.
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
private readonly string _criticReviewsPath;
|
||||
|
||||
private readonly FileSystemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonUserDataRepository" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <exception cref="System.ArgumentNullException">appPaths</exception>
|
||||
public JsonItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
|
||||
{
|
||||
if (appPaths == null)
|
||||
{
|
||||
throw new ArgumentNullException("appPaths");
|
||||
}
|
||||
if (jsonSerializer == null)
|
||||
{
|
||||
throw new ArgumentNullException("jsonSerializer");
|
||||
}
|
||||
|
||||
_jsonSerializer = jsonSerializer;
|
||||
|
||||
_criticReviewsPath = Path.Combine(appPaths.DataPath, "critic-reviews");
|
||||
|
||||
_itemRepo = new FileSystemRepository(Path.Combine(appPaths.DataPath, "library"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public Task Initialize()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <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 async Task SaveItem(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(_criticReviewsPath))
|
||||
{
|
||||
Directory.CreateDirectory(_criticReviewsPath);
|
||||
}
|
||||
|
||||
var path = _itemRepo.GetResourcePath(item.Id + ".json");
|
||||
|
||||
var parentPath = Path.GetDirectoryName(path);
|
||||
if (!Directory.Exists(parentPath))
|
||||
{
|
||||
Directory.CreateDirectory(parentPath);
|
||||
}
|
||||
|
||||
var semaphore = GetLock(path);
|
||||
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
_jsonSerializer.SerializeToFile(item, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// items
|
||||
/// or
|
||||
/// cancellationToken
|
||||
/// </exception>
|
||||
public Task SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException("items");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
var tasks = items.Select(i => SaveItem(i, cancellationToken));
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the item.
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">id</exception>
|
||||
public BaseItem RetrieveItem(Guid id, Type type)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
var path = _itemRepo.GetResourcePath(id + ".json");
|
||||
|
||||
try
|
||||
{
|
||||
return (BaseItem)_jsonSerializer.DeserializeFromFile(type, path);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// File doesn't exist or is currently bring written to
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the critic reviews.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <returns>Task{IEnumerable{ItemReview}}.</returns>
|
||||
public Task<IEnumerable<ItemReview>> GetCriticReviews(Guid itemId)
|
||||
{
|
||||
return Task.Run<IEnumerable<ItemReview>>(() =>
|
||||
{
|
||||
var path = Path.Combine(_criticReviewsPath, itemId + ".json");
|
||||
|
||||
try
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromFile<List<ItemReview>>(path);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// File doesn't exist or is currently bring written to
|
||||
return new List<ItemReview>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the critic reviews.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="criticReviews">The critic reviews.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
if (!Directory.Exists(_criticReviewsPath))
|
||||
{
|
||||
Directory.CreateDirectory(_criticReviewsPath);
|
||||
}
|
||||
|
||||
var path = Path.Combine(_criticReviewsPath, itemId + ".json");
|
||||
|
||||
_jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Wait up to two seconds for any existing writes to finish
|
||||
var locks = _fileLocks.Values.ToList()
|
||||
.Where(i => i.CurrentCount == 1)
|
||||
.Select(i => i.WaitAsync(2000));
|
||||
|
||||
var task = Task.WhenAll(locks);
|
||||
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Persistence
|
||||
{
|
||||
public class JsonUserRepository : IUserRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
private SemaphoreSlim GetLock(string filename)
|
||||
{
|
||||
return _fileLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Json";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the json serializer.
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
private readonly string _dataPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonUserRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// appPaths
|
||||
/// or
|
||||
/// jsonSerializer
|
||||
/// </exception>
|
||||
public JsonUserRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
|
||||
{
|
||||
if (appPaths == null)
|
||||
{
|
||||
throw new ArgumentNullException("appPaths");
|
||||
}
|
||||
if (jsonSerializer == null)
|
||||
{
|
||||
throw new ArgumentNullException("jsonSerializer");
|
||||
}
|
||||
|
||||
_jsonSerializer = jsonSerializer;
|
||||
|
||||
_dataPath = Path.Combine(appPaths.DataPath, "users");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public Task Initialize()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <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 async Task SaveUser(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException("user");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!Directory.Exists(_dataPath))
|
||||
{
|
||||
Directory.CreateDirectory(_dataPath);
|
||||
}
|
||||
|
||||
var path = Path.Combine(_dataPath, user.Id + ".json");
|
||||
|
||||
var semaphore = GetLock(path);
|
||||
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
_jsonSerializer.SerializeToFile(user, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all users from the database
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{User}.</returns>
|
||||
public IEnumerable<User> RetrieveAllUsers()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Directory.EnumerateFiles(_dataPath, "*.json", SearchOption.TopDirectoryOnly)
|
||||
.Select(i => _jsonSerializer.DeserializeFromFile<User>(i));
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new List<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 async Task DeleteUser(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException("user");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var path = Path.Combine(_dataPath, user.Id + ".json");
|
||||
|
||||
var semaphore = GetLock(path);
|
||||
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Wait up to two seconds for any existing writes to finish
|
||||
var locks = _fileLocks.Values.ToList()
|
||||
.Where(i => i.CurrentCount == 1)
|
||||
.Select(i => i.WaitAsync(2000));
|
||||
|
||||
var task = Task.WhenAll(locks);
|
||||
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteDisplayPreferencesRepository
|
||||
/// </summary>
|
||||
public 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>
|
||||
/// The _json serializer
|
||||
/// </summary>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// The _app paths
|
||||
/// </summary>
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqliteDisplayPreferencesRepository" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// jsonSerializer
|
||||
/// or
|
||||
/// appPaths
|
||||
/// </exception>
|
||||
public SqliteDisplayPreferencesRepository(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Initialize()
|
||||
{
|
||||
var dbFile = Path.Combine(_appPaths.DataPath, "displaypreferences.db");
|
||||
|
||||
await ConnectToDb(dbFile).ConfigureAwait(false);
|
||||
|
||||
string[] queries = {
|
||||
|
||||
"create table if not exists displaypreferences (id GUID, data BLOB)",
|
||||
"create unique index if not exists displaypreferencesindex on displaypreferences (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="displayPreferences">The display preferences.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, CancellationToken cancellationToken)
|
||||
{
|
||||
if (displayPreferences == null)
|
||||
{
|
||||
throw new ArgumentNullException("displayPreferences");
|
||||
}
|
||||
if (displayPreferences.Id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("displayPreferences.Id");
|
||||
}
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var serialized = _jsonSerializer.SerializeToBytes(displayPreferences);
|
||||
|
||||
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
SQLiteTransaction transaction = null;
|
||||
|
||||
try
|
||||
{
|
||||
transaction = Connection.BeginTransaction();
|
||||
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "replace into displaypreferences (id, data) values (@1, @2)";
|
||||
cmd.AddParam("@1", displayPreferences.Id);
|
||||
cmd.AddParam("@2", serialized);
|
||||
|
||||
cmd.Transaction = transaction;
|
||||
|
||||
await cmd.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorException("Failed to save display preferences:", e);
|
||||
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Dispose();
|
||||
}
|
||||
|
||||
_writeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display preferences.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferencesId">The display preferences id.</param>
|
||||
/// <returns>Task{DisplayPreferences}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
public async Task<DisplayPreferences> GetDisplayPreferences(Guid displayPreferencesId)
|
||||
{
|
||||
if (displayPreferencesId == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("displayPreferencesId");
|
||||
}
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = "select data from displaypreferences where id = @id";
|
||||
|
||||
var idParam = cmd.Parameters.Add("@id", DbType.Guid);
|
||||
idParam.Value = displayPreferencesId;
|
||||
|
||||
using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow).ConfigureAwait(false))
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
using (var stream = GetStream(reader, 0))
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Persistence
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
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 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.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteItemRepository
|
||||
/// </summary>
|
||||
public class SqliteItemRepository : SqliteRepository, IItemRepository
|
||||
{
|
||||
/// <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>
|
||||
/// Gets the json serializer.
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// The _app paths
|
||||
/// </summary>
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
/// <summary>
|
||||
/// The _save item command
|
||||
/// </summary>
|
||||
private SQLiteCommand _saveItemCommand;
|
||||
|
||||
private readonly string _criticReviewsPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// appPaths
|
||||
/// or
|
||||
/// jsonSerializer
|
||||
/// </exception>
|
||||
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;
|
||||
|
||||
_criticReviewsPath = Path.Combine(_appPaths.DataPath, "critic-reviews");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Initialize()
|
||||
{
|
||||
var dbFile = Path.Combine(_appPaths.DataPath, "library.db");
|
||||
|
||||
await ConnectToDb(dbFile).ConfigureAwait(false);
|
||||
|
||||
string[] queries = {
|
||||
|
||||
"create table if not exists baseitems (guid GUID primary key, data BLOB)",
|
||||
"create index if not exists idx_baseitems on baseitems(guid)",
|
||||
"create table if not exists schema_version (table_name primary key, version)",
|
||||
//pragmas
|
||||
"pragma temp_store = memory"
|
||||
};
|
||||
|
||||
RunQueries(queries);
|
||||
|
||||
PrepareStatements();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _write lock
|
||||
/// </summary>
|
||||
private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the statements.
|
||||
/// </summary>
|
||||
private void PrepareStatements()
|
||||
{
|
||||
_saveItemCommand = new SQLiteCommand
|
||||
{
|
||||
CommandText = "replace into baseitems (guid, data) values (@1, @2)"
|
||||
};
|
||||
|
||||
_saveItemCommand.Parameters.Add(new SQLiteParameter("@1"));
|
||||
_saveItemCommand.Parameters.Add(new SQLiteParameter("@2"));
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
|
||||
return SaveItems(new[] { item }, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// items
|
||||
/// or
|
||||
/// cancellationToken
|
||||
/// </exception>
|
||||
public async Task SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException("items");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
SQLiteTransaction transaction = null;
|
||||
|
||||
try
|
||||
{
|
||||
transaction = Connection.BeginTransaction();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_saveItemCommand.Parameters[0].Value = item.Id;
|
||||
_saveItemCommand.Parameters[1].Value = _jsonSerializer.SerializeToBytes(item);
|
||||
|
||||
_saveItemCommand.Transaction = transaction;
|
||||
|
||||
await _saveItemCommand.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorException("Failed to save items:", e);
|
||||
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Dispose();
|
||||
}
|
||||
|
||||
_writeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal retrieve from items or users table
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">id</exception>
|
||||
/// <exception cref="System.ArgumentException"></exception>
|
||||
public BaseItem RetrieveItem(Guid id, Type type)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "select data from baseitems 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())
|
||||
{
|
||||
using (var stream = GetStream(reader, 0))
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromStream(stream, type) as BaseItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the critic reviews.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <returns>Task{IEnumerable{ItemReview}}.</returns>
|
||||
public Task<IEnumerable<ItemReview>> GetCriticReviews(Guid itemId)
|
||||
{
|
||||
return Task.Run<IEnumerable<ItemReview>>(() =>
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(_criticReviewsPath, itemId + ".json");
|
||||
|
||||
return _jsonSerializer.DeserializeFromFile<List<ItemReview>>(path);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return new List<ItemReview>();
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return new List<ItemReview>();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the critic reviews.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="criticReviews">The critic reviews.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
if (!Directory.Exists(_criticReviewsPath))
|
||||
{
|
||||
Directory.CreateDirectory(_criticReviewsPath);
|
||||
}
|
||||
|
||||
var path = Path.Combine(_criticReviewsPath, itemId + ".json");
|
||||
|
||||
_jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Persistence
|
||||
{
|
||||
/// <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>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
protected ILogger Logger { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqliteRepository" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <exception cref="System.ArgumentNullException">logger</exception>
|
||||
protected SqliteRepository(ILogManager logManager)
|
||||
{
|
||||
if (logManager == null)
|
||||
{
|
||||
throw new ArgumentNullException("logManager");
|
||||
}
|
||||
|
||||
Logger = logManager.GetLogger(GetType().Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to DB.
|
||||
/// </summary>
|
||||
/// <param name="dbPath">The db path.</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">dbPath</exception>
|
||||
protected Task ConnectToDb(string dbPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dbPath))
|
||||
{
|
||||
throw new ArgumentNullException("dbPath");
|
||||
}
|
||||
|
||||
DbFileName = dbPath;
|
||||
var connectionstr = new SQLiteConnectionStringBuilder
|
||||
{
|
||||
PageSize = 4096,
|
||||
CacheSize = 40960,
|
||||
SyncMode = SynchronizationModes.Off,
|
||||
DataSource = dbPath,
|
||||
JournalMode = SQLiteJournalModeEnum.Wal
|
||||
};
|
||||
|
||||
Connection = new SQLiteConnection(connectionstr.ConnectionString);
|
||||
|
||||
return Connection.OpenAsync();
|
||||
}
|
||||
|
||||
/// <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">queries</exception>
|
||||
protected void RunQueries(string[] queries)
|
||||
{
|
||||
if (queries == null)
|
||||
{
|
||||
throw new ArgumentNullException("queries");
|
||||
}
|
||||
|
||||
using (var tran = Connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
using (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);
|
||||
}
|
||||
|
||||
private readonly object _disposeLock = new object();
|
||||
|
||||
/// <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)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_disposeLock)
|
||||
{
|
||||
if (Connection != null)
|
||||
{
|
||||
if (Connection.IsOpen())
|
||||
{
|
||||
Connection.Close();
|
||||
}
|
||||
|
||||
Connection.Dispose();
|
||||
Connection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException("Error disposing database", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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">reader</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,29 +1,29 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
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.Data.SQLite;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Persistence
|
||||
{
|
||||
public class JsonUserDataRepository : IUserDataRepository
|
||||
public class SqliteUserDataRepository : SqliteRepository, IUserDataRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
private SemaphoreSlim GetLock(string filename)
|
||||
{
|
||||
return _fileLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, UserItemData> _userData = new ConcurrentDictionary<string, UserItemData>();
|
||||
|
||||
private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The repository name
|
||||
/// </summary>
|
||||
public const string RepositoryName = "SQLite";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository
|
||||
/// </summary>
|
||||
|
@ -32,18 +32,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
{
|
||||
get
|
||||
{
|
||||
return "Json";
|
||||
return RepositoryName;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
private readonly string _dataPath;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
/// <summary>
|
||||
/// The _app paths
|
||||
/// </summary>
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonUserDataRepository" /> class.
|
||||
/// Initializes a new instance of the <see cref="SqliteUserDataRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
|
@ -53,7 +54,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
/// or
|
||||
/// appPaths
|
||||
/// </exception>
|
||||
public JsonUserDataRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
|
||||
public SqliteUserDataRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
|
||||
: base(logManager)
|
||||
{
|
||||
if (jsonSerializer == null)
|
||||
{
|
||||
|
@ -64,18 +66,30 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
throw new ArgumentNullException("appPaths");
|
||||
}
|
||||
|
||||
_logger = logManager.GetLogger(GetType().Name);
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_dataPath = Path.Combine(appPaths.DataPath, "userdata");
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public Task Initialize()
|
||||
public async Task Initialize()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
var dbFile = Path.Combine(_appPaths.DataPath, "userdata.db");
|
||||
|
||||
await ConnectToDb(dbFile).ConfigureAwait(false);
|
||||
|
||||
string[] queries = {
|
||||
|
||||
"create table if not exists userdata (key nvarchar, userId GUID, data BLOB)",
|
||||
"create unique index if not exists userdataindex on userdata (key, userId)",
|
||||
"create table if not exists schema_version (table_name primary key, version)",
|
||||
//pragmas
|
||||
"pragma temp_store = memory"
|
||||
};
|
||||
|
||||
RunQueries(queries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -118,12 +132,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
{
|
||||
await PersistUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var newValue = userData;
|
||||
|
||||
// Once it succeeds, put it into the dictionary to make it available to everyone else
|
||||
_userData.AddOrUpdate(GetInternalKey(userId, key), userData, delegate { return userData; });
|
||||
_userData.AddOrUpdate(GetInternalKey(userId, key), newValue, delegate { return newValue; });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error saving user data", ex);
|
||||
Logger.ErrorException("Error saving user data", ex);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
@ -152,25 +168,60 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var path = GetUserDataPath(userId, key);
|
||||
var serialized = _jsonSerializer.SerializeToBytes(userData);
|
||||
|
||||
var parentPath = Path.GetDirectoryName(path);
|
||||
if (!Directory.Exists(parentPath))
|
||||
{
|
||||
Directory.CreateDirectory(parentPath);
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var semaphore = GetLock(path);
|
||||
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
SQLiteTransaction transaction = null;
|
||||
|
||||
try
|
||||
{
|
||||
_jsonSerializer.SerializeToFile(userData, path);
|
||||
transaction = Connection.BeginTransaction();
|
||||
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "replace into userdata (key, userId, data) values (@1, @2, @3)";
|
||||
cmd.AddParam("@1", key);
|
||||
cmd.AddParam("@2", userId);
|
||||
cmd.AddParam("@3", serialized);
|
||||
|
||||
cmd.Transaction = transaction;
|
||||
|
||||
await cmd.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorException("Failed to save user data:", e);
|
||||
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Dispose();
|
||||
}
|
||||
|
||||
_writeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,40 +258,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
/// <returns>Task{UserItemData}.</returns>
|
||||
private UserItemData RetrieveUserData(Guid userId, string key)
|
||||
{
|
||||
var path = GetUserDataPath(userId, key);
|
||||
|
||||
try
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromFile<UserItemData>(path);
|
||||
cmd.CommandText = "select data from userdata 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;
|
||||
|
||||
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
using (var stream = GetStream(reader, 0))
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromStream<UserItemData>(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new UserItemData();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// File doesn't exist or is currently bring written to
|
||||
return new UserItemData { UserId = userId };
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUserDataPath(Guid userId, string key)
|
||||
{
|
||||
var userFolder = Path.Combine(_dataPath, userId.ToString());
|
||||
|
||||
var keyHash = key.GetMD5().ToString();
|
||||
|
||||
var prefix = keyHash.Substring(0, 1);
|
||||
|
||||
return Path.Combine(userFolder, prefix, keyHash + ".json");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Wait up to two seconds for any existing writes to finish
|
||||
var locks = _fileLocks.Values.ToList()
|
||||
.Where(i => i.CurrentCount == 1)
|
||||
.Select(i => i.WaitAsync(2000));
|
||||
|
||||
var task = Task.WhenAll(locks);
|
||||
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
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.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteUserRepository
|
||||
/// </summary>
|
||||
public class SqliteUserRepository : SqliteRepository, IUserRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The repository name
|
||||
/// </summary>
|
||||
public const string RepositoryName = "SQLite";
|
||||
|
||||
private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return RepositoryName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the json serializer.
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// The _app paths
|
||||
/// </summary>
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqliteUserRepository" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <exception cref="System.ArgumentNullException">appPaths</exception>
|
||||
public SqliteUserRepository(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Initialize()
|
||||
{
|
||||
var dbFile = Path.Combine(_appPaths.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 async Task SaveUser(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException("user");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var serialized = _jsonSerializer.SerializeToBytes(user);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
SQLiteTransaction transaction = null;
|
||||
|
||||
try
|
||||
{
|
||||
transaction = Connection.BeginTransaction();
|
||||
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "replace into users (guid, data) values (@1, @2)";
|
||||
cmd.AddParam("@1", user.Id);
|
||||
cmd.AddParam("@2", serialized);
|
||||
|
||||
cmd.Transaction = transaction;
|
||||
|
||||
await cmd.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorException("Failed to save user:", e);
|
||||
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Dispose();
|
||||
}
|
||||
|
||||
_writeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all users from the database
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{User}.</returns>
|
||||
public IEnumerable<User> RetrieveAllUsers()
|
||||
{
|
||||
using (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 async Task DeleteUser(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException("user");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
SQLiteTransaction transaction = null;
|
||||
|
||||
try
|
||||
{
|
||||
transaction = Connection.BeginTransaction();
|
||||
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "delete from users where guid=@guid";
|
||||
|
||||
var guidParam = cmd.Parameters.Add("@guid", DbType.Guid);
|
||||
guidParam.Value = user.Id;
|
||||
|
||||
cmd.Transaction = transaction;
|
||||
|
||||
await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorException("Failed to delete user:", e);
|
||||
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.Dispose();
|
||||
}
|
||||
|
||||
_writeLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,4 +14,5 @@
|
|||
<package id="ServiceStack.Redis" version="3.9.43" targetFramework="net45" />
|
||||
<package id="ServiceStack.Text" version="3.9.45" targetFramework="net45" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
|
||||
<package id="System.Data.SQLite.x86" version="1.0.86.0" targetFramework="net45" />
|
||||
</packages>
|
|
@ -244,16 +244,16 @@ namespace MediaBrowser.ServerApplication
|
|||
ZipClient = new DotNetZipClient();
|
||||
RegisterSingleInstance(ZipClient);
|
||||
|
||||
UserDataRepository = new JsonUserDataRepository(ApplicationPaths, JsonSerializer, LogManager);
|
||||
UserDataRepository = new SqliteUserDataRepository(ApplicationPaths, JsonSerializer, LogManager);
|
||||
RegisterSingleInstance(UserDataRepository);
|
||||
|
||||
UserRepository = new JsonUserRepository(ApplicationPaths, JsonSerializer, LogManager);
|
||||
UserRepository = new SqliteUserRepository(ApplicationPaths, JsonSerializer, LogManager);
|
||||
RegisterSingleInstance(UserRepository);
|
||||
|
||||
DisplayPreferencesRepository = new JsonDisplayPreferencesRepository(ApplicationPaths, JsonSerializer, LogManager);
|
||||
DisplayPreferencesRepository = new SqliteDisplayPreferencesRepository(ApplicationPaths, JsonSerializer, LogManager);
|
||||
RegisterSingleInstance(DisplayPreferencesRepository);
|
||||
|
||||
ItemRepository = new JsonItemRepository(ApplicationPaths, JsonSerializer, LogManager);
|
||||
ItemRepository = new SqliteItemRepository(ApplicationPaths, JsonSerializer, LogManager);
|
||||
RegisterSingleInstance(ItemRepository);
|
||||
|
||||
UserManager = new UserManager(Logger, ServerConfigurationManager);
|
||||
|
|
Loading…
Reference in New Issue
Block a user