diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
index 4c1ce8b99d..a0c1a2f0fe 100644
--- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
+++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
@@ -407,6 +407,18 @@ namespace MediaBrowser.Common.Implementations
}
}
+ ///
+ /// Gets the export types.
+ ///
+ ///
+ /// IEnumerable{Type}.
+ public IEnumerable GetExportTypes()
+ {
+ var currentType = typeof(T);
+
+ return AllConcreteTypes.AsParallel().Where(currentType.IsAssignableFrom);
+ }
+
///
/// Gets the exports.
///
@@ -415,9 +427,7 @@ namespace MediaBrowser.Common.Implementations
/// IEnumerable{``0}.
public IEnumerable GetExports(bool manageLiftime = true)
{
- var currentType = typeof(T);
-
- var parts = AllConcreteTypes.AsParallel().Where(currentType.IsAssignableFrom).Select(CreateInstance).Cast().ToArray();
+ var parts = GetExportTypes().Select(CreateInstance).Cast().ToArray();
if (manageLiftime)
{
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index cfabbbadb9..0d7c1862a4 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -143,6 +143,7 @@ namespace MediaBrowser.Controller.Entities
public static IServerConfigurationManager ConfigurationManager { get; set; }
public static IProviderManager ProviderManager { get; set; }
public static ILocalizationManager LocalizationManager { get; set; }
+ public static IItemRepository ItemRepository { get; set; }
///
/// Returns a that represents this instance.
diff --git a/MediaBrowser.Controller/Entities/ChildDefinition.cs b/MediaBrowser.Controller/Entities/ChildDefinition.cs
new file mode 100644
index 0000000000..e8d68b5eab
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/ChildDefinition.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace MediaBrowser.Controller.Entities
+{
+ ///
+ /// Class ChildDefinition
+ ///
+ public class ChildDefinition
+ {
+ ///
+ /// Gets or sets the item id.
+ ///
+ /// The item id.
+ public Guid ItemId { get; set; }
+
+ ///
+ /// Gets or sets the type.
+ ///
+ /// The type.
+ public string Type { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index de965221b8..762209f702 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -22,14 +22,7 @@ namespace MediaBrowser.Controller.Entities
///
public class Folder : BaseItem
{
- private static TypeMapper _typeMapper = new TypeMapper();
-
- public Folder()
- {
- ChildDefinitions = new ConcurrentDictionary();
- }
-
- public ConcurrentDictionary ChildDefinitions { get; set; }
+ private static readonly TypeMapper _typeMapper = new TypeMapper();
///
/// Gets a value indicating whether this instance is folder.
@@ -118,14 +111,19 @@ namespace MediaBrowser.Controller.Entities
item.DateModified = DateTime.Now;
}
- if (!_children.TryAdd(item.Id, item) || !ChildDefinitions.TryAdd(item.Id, item.GetType().FullName))
+ if (!_children.TryAdd(item.Id, item))
{
throw new InvalidOperationException("Unable to add " + item.Name);
}
await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
- await LibraryManager.UpdateItem(this, cancellationToken).ConfigureAwait(false);
+ await ItemRepository.SaveChildren(Id, _children.Values.ToList().Select(i => new ChildDefinition
+ {
+ ItemId = i.Id,
+ Type = i.GetType().FullName
+
+ }), cancellationToken).ConfigureAwait(false);
}
///
@@ -153,18 +151,22 @@ namespace MediaBrowser.Controller.Entities
public Task RemoveChild(BaseItem item, CancellationToken cancellationToken)
{
BaseItem removed;
- string removedType;
- if (!_children.TryRemove(item.Id, out removed) || !ChildDefinitions.TryRemove(item.Id, out removedType))
+ if (!_children.TryRemove(item.Id, out removed))
{
throw new InvalidOperationException("Unable to remove " + item.Name);
}
item.Parent = null;
-
+
LibraryManager.ReportItemRemoved(item);
- return LibraryManager.UpdateItem(this, cancellationToken);
+ return ItemRepository.SaveChildren(Id, _children.Values.ToList().Select(i => new ChildDefinition
+ {
+ ItemId = i.Id,
+ Type = i.GetType().FullName
+
+ }), cancellationToken);
}
#region Indexing
@@ -297,7 +299,7 @@ namespace MediaBrowser.Controller.Entities
.Where(i => i != null)
.Select(a => new IndexFolder(us, a,
songs.Where(i => string.Equals(i.Artist, a.Name, StringComparison.OrdinalIgnoreCase)
- ), currentIndexName)).Concat(indexFolders);
+ ), currentIndexName)).Concat(indexFolders);
}
return indexFolders;
@@ -495,7 +497,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the actual children.
///
/// The actual children.
- protected virtual ConcurrentDictionary ActualChildren
+ protected virtual ConcurrentDictionary ActualChildren
{
get
{
@@ -558,10 +560,10 @@ namespace MediaBrowser.Controller.Entities
/// We want this sychronous.
///
/// ConcurrentBag{BaseItem}.
- protected virtual ConcurrentDictionary LoadChildren()
+ protected virtual ConcurrentDictionary LoadChildren()
{
//just load our children from the repo - the library will be validated and maintained in other processes
- return new ConcurrentDictionary(GetCachedChildren().ToDictionary(i => i.Id));
+ return new ConcurrentDictionary(GetCachedChildren().ToDictionary(i => i.Id));
}
///
@@ -709,9 +711,6 @@ namespace MediaBrowser.Controller.Entities
}
else
{
- string removedType;
- ChildDefinitions.TryRemove(item.Id, out removedType);
-
LibraryManager.ReportItemRemoved(item);
}
}
@@ -726,13 +725,16 @@ namespace MediaBrowser.Controller.Entities
}
else
{
- ChildDefinitions.TryAdd(item.Id, item.GetType().FullName);
-
Logger.Debug("** " + item.Name + " Added to library.");
}
}
- await LibraryManager.UpdateItem(this, CancellationToken.None).ConfigureAwait(false);
+ await ItemRepository.SaveChildren(Id, _children.Values.ToList().Select(i => new ChildDefinition
+ {
+ ItemId = i.Id,
+ Type = i.GetType().FullName
+
+ }), cancellationToken).ConfigureAwait(false);
//force the indexes to rebuild next time
IndexCache.Clear();
@@ -804,7 +806,7 @@ namespace MediaBrowser.Controller.Entities
{
lock (percentages)
{
- percentages[child.Id] = p/100;
+ percentages[child.Id] = p / 100;
var percent = percentages.Values.Sum();
percent /= list.Count;
@@ -862,7 +864,7 @@ namespace MediaBrowser.Controller.Entities
/// IEnumerable{BaseItem}.
protected IEnumerable GetCachedChildren()
{
- var items = ChildDefinitions.ToList().Select(RetrieveChild).Where(i => i != null).ToList();
+ var items = ItemRepository.GetChildren(Id).Select(RetrieveChild).Where(i => i != null).ToList();
foreach (var item in items)
{
@@ -877,9 +879,9 @@ namespace MediaBrowser.Controller.Entities
///
/// The child.
/// BaseItem.
- private BaseItem RetrieveChild(KeyValuePair child)
+ private BaseItem RetrieveChild(ChildDefinition child)
{
- var type = child.Value;
+ var type = child.Type;
var itemType = _typeMapper.GetType(type);
@@ -889,7 +891,7 @@ namespace MediaBrowser.Controller.Entities
return null;
}
- var item = LibraryManager.RetrieveItem(child.Key, itemType);
+ var item = LibraryManager.RetrieveItem(child.ItemId, itemType);
return item is IByReferenceItem ? LibraryManager.GetOrAddByReferenceItem(item) : item;
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index f49221ce85..bea2e9e69f 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -74,6 +74,7 @@
+
diff --git a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
index 4d7345f489..ecd8c1136d 100644
--- a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
@@ -10,6 +10,12 @@ namespace MediaBrowser.Controller.Persistence
///
public interface IDisplayPreferencesRepository : IRepository
{
+ ///
+ /// Opens the connection to the repository
+ ///
+ /// Task.
+ Task Initialize();
+
///
/// Saves display preferences for an item
///
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 534e64a3f0..2331ec32fd 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -13,6 +13,12 @@ namespace MediaBrowser.Controller.Persistence
///
public interface IItemRepository : IRepository
{
+ ///
+ /// Opens the connection to the repository
+ ///
+ /// Task.
+ Task Initialize();
+
///
/// Saves an item
///
@@ -75,6 +81,22 @@ namespace MediaBrowser.Controller.Persistence
/// The cancellation token.
/// Task.
Task SaveChapters(Guid id, IEnumerable chapters, CancellationToken cancellationToken);
+
+ ///
+ /// Gets the children.
+ ///
+ /// The parent id.
+ /// IEnumerable{ChildDefinition}.
+ IEnumerable GetChildren(Guid parentId);
+
+ ///
+ /// Saves the children.
+ ///
+ /// The parent id.
+ /// The children.
+ /// The cancellation token.
+ /// Task.
+ Task SaveChildren(Guid parentId, IEnumerable children, CancellationToken cancellationToken);
}
///
diff --git a/MediaBrowser.Controller/Persistence/IRepository.cs b/MediaBrowser.Controller/Persistence/IRepository.cs
index 2d051aa82c..f6367c3846 100644
--- a/MediaBrowser.Controller/Persistence/IRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IRepository.cs
@@ -8,12 +8,6 @@ namespace MediaBrowser.Controller.Persistence
///
public interface IRepository : IDisposable
{
- ///
- /// Opens the connection to the repository
- ///
- /// Task.
- Task Initialize();
-
///
/// Gets the name of the repository
///
diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
index ad111f4ed4..bdeaf70dc9 100644
--- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
@@ -10,6 +10,12 @@ namespace MediaBrowser.Controller.Persistence
///
public interface IUserDataRepository : IRepository
{
+ ///
+ /// Opens the connection to the repository
+ ///
+ /// Task.
+ Task Initialize();
+
///
/// Saves the user data.
///
diff --git a/MediaBrowser.Controller/Persistence/IUserRepository.cs b/MediaBrowser.Controller/Persistence/IUserRepository.cs
index 80961a369a..0241b8c034 100644
--- a/MediaBrowser.Controller/Persistence/IUserRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IUserRepository.cs
@@ -10,6 +10,12 @@ namespace MediaBrowser.Controller.Persistence
///
public interface IUserRepository : IRepository
{
+ ///
+ /// Opens the connection to the repository
+ ///
+ /// Task.
+ Task Initialize();
+
///
/// Deletes the user.
///
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 34930b34e9..fde1b77b30 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -64,6 +64,12 @@
False
..\packages\ServiceStack.Common.3.9.54\lib\net35\ServiceStack.Interfaces.dll
+
+ ..\packages\ServiceStack.OrmLite.Sqlite32.3.9.54\lib\net40\ServiceStack.OrmLite.dll
+
+
+ ..\packages\ServiceStack.OrmLite.Sqlite32.3.9.54\lib\net40\ServiceStack.OrmLite.SqliteNET.dll
+
False
..\packages\ServiceStack.OrmLite.SqlServer.3.9.43\lib\ServiceStack.OrmLite.SqlServer.dll
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs
index dd6343a677..cac612fc80 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs
@@ -27,16 +27,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
private SQLiteCommand _saveChapterCommand;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The app paths.
- /// The json serializer.
/// The log manager.
- ///
- /// appPaths
+ /// appPaths
/// or
- /// jsonSerializer
- ///
+ /// jsonSerializer
public SqliteChapterRepository(IApplicationPaths appPaths, ILogManager logManager)
{
if (appPaths == null)
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index b3251ddb94..f0597e4ccf 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -56,6 +56,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
private SqliteChapterRepository _chapterRepository;
+ private SQLiteCommand _deleteChildrenCommand;
+ private SQLiteCommand _saveChildrenCommand;
+
///
/// Initializes a new instance of the class.
///
@@ -95,7 +98,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
public async Task Initialize()
{
var dbFile = Path.Combine(_appPaths.DataPath, "library.db");
-
+
_connection = await SqliteExtensions.ConnectToDb(dbFile).ConfigureAwait(false);
string[] queries = {
@@ -103,6 +106,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
"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 ChildDefinitions (ParentId GUID, ItemId GUID, Type TEXT, PRIMARY KEY (ParentId, ItemId))",
+ "create index if not exists idx_baseitems on baseitems(ParentId,ItemId)",
+
//pragmas
"pragma temp_store = memory"
};
@@ -131,6 +137,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.Parameters.Add(new SQLiteParameter("@1"));
_saveItemCommand.Parameters.Add(new SQLiteParameter("@2"));
+
+ _deleteChildrenCommand = new SQLiteCommand
+ {
+ CommandText = "delete from ChildDefinitions where ParentId=@ParentId"
+ };
+
+ _deleteChildrenCommand.Parameters.Add(new SQLiteParameter("@ParentId"));
+
+ _saveChildrenCommand = new SQLiteCommand
+ {
+ CommandText = "replace into ChildDefinitions (ParentId, ItemId, Type) values (@ParentId, @ItemId, @Type)"
+ };
+
+ _saveChildrenCommand.Parameters.Add(new SQLiteParameter("@ParentId"));
+ _saveChildrenCommand.Parameters.Add(new SQLiteParameter("@ItemId"));
+ _saveChildrenCommand.Parameters.Add(new SQLiteParameter("@Type"));
}
///
@@ -401,5 +423,110 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
}
+
+ public IEnumerable GetChildren(Guid parentId)
+ {
+ if (parentId == Guid.Empty)
+ {
+ throw new ArgumentNullException("parentId");
+ }
+
+ using (var cmd = _connection.CreateCommand())
+ {
+ cmd.CommandText = "select ItemId,Type from ChildDefinitions where ParentId = @ParentId";
+
+ cmd.Parameters.Add("@ParentId", DbType.Guid).Value = parentId;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+ {
+ while (reader.Read())
+ {
+ yield return new ChildDefinition
+ {
+ ItemId = reader.GetGuid(0),
+ Type = reader.GetString(1)
+ };
+ }
+ }
+ }
+ }
+
+ public async Task SaveChildren(Guid parentId, IEnumerable children, CancellationToken cancellationToken)
+ {
+ if (parentId == Guid.Empty)
+ {
+ throw new ArgumentNullException("parentId");
+ }
+
+ if (children == null)
+ {
+ throw new ArgumentNullException("children");
+ }
+
+ if (cancellationToken == null)
+ {
+ throw new ArgumentNullException("cancellationToken");
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ SQLiteTransaction transaction = null;
+
+ try
+ {
+ transaction = _connection.BeginTransaction();
+
+ // First delete
+ _deleteChildrenCommand.Parameters[0].Value = parentId;
+ _deleteChildrenCommand.Transaction = transaction;
+ await _deleteChildrenCommand.ExecuteNonQueryAsync(cancellationToken);
+
+ foreach (var chapter in children)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ _saveChildrenCommand.Parameters[0].Value = parentId;
+ _saveChildrenCommand.Parameters[1].Value = chapter.ItemId;
+ _saveChildrenCommand.Parameters[2].Value = chapter.Type;
+
+ _saveChildrenCommand.Transaction = transaction;
+
+ await _saveChildrenCommand.ExecuteNonQueryAsync(cancellationToken);
+ }
+
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
+ }
+ catch (Exception e)
+ {
+ _logger.ErrorException("Failed to save children:", e);
+
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+
+ _writeLock.Release();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index b3294492f3..e13d18747d 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -10,6 +10,7 @@
+
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 0df0d36f92..f50705834b 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -109,7 +109,7 @@ namespace MediaBrowser.ServerApplication
return "http://+:" + ServerConfigurationManager.Configuration.HttpServerPortNumber + "/" + WebApplicationName + "/";
}
}
-
+
///
/// Gets the configuration manager.
///
@@ -359,6 +359,7 @@ namespace MediaBrowser.ServerApplication
BaseItem.LibraryManager = LibraryManager;
BaseItem.ProviderManager = ProviderManager;
BaseItem.LocalizationManager = LocalizationManager;
+ BaseItem.ItemRepository = ItemRepository;
User.XmlSerializer = XmlSerializer;
User.UserManager = UserManager;
LocalizedStrings.ApplicationPaths = ApplicationPaths;
@@ -504,7 +505,7 @@ namespace MediaBrowser.ServerApplication
// Include composable parts in the Providers assembly
yield return typeof(ImagesByNameProvider).Assembly;
-
+
// Common implementations
yield return typeof(TaskManager).Assembly;