From 6bc263052d2b60abfd9023aed0640a37655b6e87 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 20 Jun 2013 12:44:24 -0400 Subject: [PATCH] move child definitions to db --- .../BaseApplicationHost.cs | 16 ++- MediaBrowser.Controller/Entities/BaseItem.cs | 1 + .../Entities/ChildDefinition.cs | 22 +++ MediaBrowser.Controller/Entities/Folder.cs | 60 ++++---- .../MediaBrowser.Controller.csproj | 1 + .../IDisplayPreferencesRepository.cs | 6 + .../Persistence/IItemRepository.cs | 22 +++ .../Persistence/IRepository.cs | 6 - .../Persistence/IUserDataRepository.cs | 6 + .../Persistence/IUserRepository.cs | 6 + ...MediaBrowser.Server.Implementations.csproj | 6 + .../Persistence/SqliteChapterRepository.cs | 9 +- .../Persistence/SqliteItemRepository.cs | 129 +++++++++++++++++- .../packages.config | 1 + .../ApplicationHost.cs | 5 +- 15 files changed, 249 insertions(+), 47 deletions(-) create mode 100644 MediaBrowser.Controller/Entities/ChildDefinition.cs 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;