From c6fa3cc02bad830037b256ff05726d9b955c5a64 Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Fri, 15 Feb 2013 19:50:22 -0800 Subject: [PATCH] added support for 0 based sequential ids to our object db. --- NzbDrone.Api/RootFolders/RootFolderModule.cs | 4 +- .../Datastore/IndexProviderFixture.cs | 69 +++++++++++++ .../Datastore/ObjectDatabaseFixture.cs | 38 ++++++++ NzbDrone.Core.Test/Framework/ObjectDbTest.cs | 28 ++++++ NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 1 + .../NzbDrone.Core.Test.ncrunchproject | 24 +++++ .../Datastore/BaseRepositoryModel.cs | 9 +- NzbDrone.Core/Datastore/BasicRepository.cs | 8 +- NzbDrone.Core/Datastore/EloqueraDb.cs | 28 ++++-- NzbDrone.Core/Datastore/EloqueraDbFactory.cs | 6 +- NzbDrone.Core/Datastore/IdService.cs | 96 +++++++++++++++++++ NzbDrone.Core/Datastore/IndexProvider.cs | 81 ++++++++++++++++ NzbDrone.Core/NzbDrone.Core.csproj | 2 + .../RootFolders/RootFolderService.cs | 4 +- .../NzbDrone.Services.Api.csproj | 2 +- .../NzbDrone.Services.Service.csproj | 2 +- NzbDrone.ncrunchsolution | 2 +- 17 files changed, 375 insertions(+), 29 deletions(-) create mode 100644 NzbDrone.Core.Test/Datastore/IndexProviderFixture.cs create mode 100644 NzbDrone.Core/Datastore/IdService.cs create mode 100644 NzbDrone.Core/Datastore/IndexProvider.cs diff --git a/NzbDrone.Api/RootFolders/RootFolderModule.cs b/NzbDrone.Api/RootFolders/RootFolderModule.cs index faa152e92..ed44fe195 100644 --- a/NzbDrone.Api/RootFolders/RootFolderModule.cs +++ b/NzbDrone.Api/RootFolders/RootFolderModule.cs @@ -18,7 +18,7 @@ public RootDirModule(RootFolderService rootFolderService) Get["/"] = x => GetRootFolders(); Post["/"] = x => AddRootFolder(); - Delete["/{id}"] = x => DeleteRootFolder((long)x.id); + Delete["/{id}"] = x => DeleteRootFolder((int)x.id); } private Response AddRootFolder() @@ -32,7 +32,7 @@ private Response GetRootFolders() return _rootFolderService.All().AsResponse(); } - private Response DeleteRootFolder(long folderId) + private Response DeleteRootFolder(int folderId) { _rootFolderService.Remove(folderId); return new Response { StatusCode = HttpStatusCode.OK }; diff --git a/NzbDrone.Core.Test/Datastore/IndexProviderFixture.cs b/NzbDrone.Core.Test/Datastore/IndexProviderFixture.cs new file mode 100644 index 000000000..d6891ed35 --- /dev/null +++ b/NzbDrone.Core.Test/Datastore/IndexProviderFixture.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore +{ + [TestFixture] + public class IndexProviderFixture : ObjectDbTest + { + [SetUp] + public void Setup() + { + WithObjectDb(); + } + + [Test] + public void should_be_able_to_get_sequential_numbers() + { + var indexs = new List(); + + + for (var i = 0; i < 1000; i++) + { + indexs.Add(Subject.Next(GetType())); + } + + indexs.Should().OnlyHaveUniqueItems(); + } + + + + [Test] + public void diffrentTypes_should_get_their_own_counter() + { + var seriesIndex = new List(); + var episodeIndex = new List(); + + + for (var i = 0; i < 200; i++) + { + seriesIndex.Add(Subject.Next(typeof(Series))); + } + + for (var i = 0; i < 100; i++) + { + episodeIndex.Add(Subject.Next(typeof(Episode))); + } + + seriesIndex.Should().OnlyHaveUniqueItems(); + episodeIndex.Should().OnlyHaveUniqueItems(); + + seriesIndex.Min(c => c).Should().Be(1); + seriesIndex.Max(c => c).Should().Be(200); + + episodeIndex.Min(c => c).Should().Be(1); + episodeIndex.Max(c => c).Should().Be(100); + } + + } +} + + + diff --git a/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs b/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs index 34a920722..146efd742 100644 --- a/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs +++ b/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using Eloquera.Client; using FizzWare.NBuilder; @@ -85,13 +86,40 @@ public void should_update_nested_objects() [Test] public void new_objects_should_get_id() { + testSeries.Id = 0; Db.Insert(testSeries); testSeries.Id.Should().NotBe(0); } + [Test] + public void new_existing_object_should_get_new_id() + { + testSeries.Id = 0; + Db.Insert(testSeries); + Db.Insert(testSeries); + + Db.AsQueryable().Should().HaveCount(1); + testSeries.Id.Should().Be(1); + } + + + [Test] + public void should_be_able_to_assign_ids_to_nested_objects() + { + var nested = new NestedModel(); + + nested.List.Add(new NestedModel()); + + Db.Insert(nested); + + nested.Id.Should().Be(1); + nested.List.Should().OnlyContain(c => c.Id > 0); + } + [Test] public void should_have_id_when_returned_from_database() { + testSeries.Id = 0; Db.Insert(testSeries); var item = Db.AsQueryable(); @@ -122,5 +150,15 @@ public class UnknownType : BaseRepositoryModel { public string Field1 { get; set; } } + + public class NestedModel : BaseRepositoryModel + { + public NestedModel() + { + List = new List { this }; + } + + public IList List { get; set; } + } } diff --git a/NzbDrone.Core.Test/Framework/ObjectDbTest.cs b/NzbDrone.Core.Test/Framework/ObjectDbTest.cs index e0a13ecca..333e38bc2 100644 --- a/NzbDrone.Core.Test/Framework/ObjectDbTest.cs +++ b/NzbDrone.Core.Test/Framework/ObjectDbTest.cs @@ -6,8 +6,35 @@ namespace NzbDrone.Core.Test.Framework { + + public abstract class ObjectDbTest : ObjectDbTest where TSubject : class + { + private TSubject _subject; + + [SetUp] + public void CoreTestSetup() + { + _subject = null; + } + + protected TSubject Subject + { + get + { + if (_subject == null) + { + _subject = Mocker.Resolve(); + } + + return _subject; + } + + } + } + public abstract class ObjectDbTest : CoreTest { + private EloqueraDb _db; protected EloqueraDb Db { @@ -32,6 +59,7 @@ protected void WithObjectDb(bool memory = true) } Mocker.SetConstant(Db); + Mocker.SetConstant(Db.Db); } [TearDown] diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 473d5a61e..9fe4fd2ec 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -146,6 +146,7 @@ + diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.ncrunchproject b/NzbDrone.Core.Test/NzbDrone.Core.Test.ncrunchproject index ab51924af..925803202 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.ncrunchproject +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.ncrunchproject @@ -30,6 +30,30 @@ NzbDrone\.Core\.Test\.Integeration\.ServiceIntegerationFixture\..* + + NzbDrone\.Core\.Test\.QualityProfileTest\..* + + + NzbDrone\.Core\.Test\.ProviderTests\.TvDbProviderTest\..* + + + NzbDrone\.Core\.Test\.ProviderTests\.MediaFileProviderTest\..* + + + NzbDrone\.Core\.Test\.ProviderTests\.EpisodeProviderTests\.EpisodeProviderTest_DeleteInvalidEpisodes\..* + + + NzbDrone\.Core\.Test\.ProviderTests\.DecisionEngineTests\.UpgradeHistorySpecificationFixture\..* + + + NzbDrone\.Core\.Test\.ProviderTests\.DecisionEngineTests\.QualityAllowedByProfileSpecificationFixture\..* + + + NzbDrone\.Core\.Test\.ParserTests\.QualityParserFixture\..* + + + NzbDrone\.Core\.Test\.ParserTests\.ParserFixture\..* + ..\NzbDrone.Core\bin\Debug\Eloquera.Server.exe PostBuildEventDisabled diff --git a/NzbDrone.Core/Datastore/BaseRepositoryModel.cs b/NzbDrone.Core/Datastore/BaseRepositoryModel.cs index 80bbf54e2..538239534 100644 --- a/NzbDrone.Core/Datastore/BaseRepositoryModel.cs +++ b/NzbDrone.Core/Datastore/BaseRepositoryModel.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Linq; using Eloquera.Client; namespace NzbDrone.Core.Datastore @@ -9,6 +6,8 @@ namespace NzbDrone.Core.Datastore public abstract class BaseRepositoryModel { [ID] - public long Id; + private long _eqId; + + public int Id { get; set; } } } diff --git a/NzbDrone.Core/Datastore/BasicRepository.cs b/NzbDrone.Core/Datastore/BasicRepository.cs index cf59b867b..ed018fb92 100644 --- a/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/NzbDrone.Core/Datastore/BasicRepository.cs @@ -6,9 +6,9 @@ namespace NzbDrone.Core.Datastore public interface IBasicRepository { List All(); - TModel Get(long rootFolderId); + TModel Get(int rootFolderId); TModel Add(TModel rootFolder); - void Delete(long rootFolderId); + void Delete(int rootFolderId); } public class BasicRepository : IBasicRepository where TModel : BaseRepositoryModel, new() @@ -25,7 +25,7 @@ public List All() return EloqueraDb.AsQueryable().ToList(); } - public TModel Get(long id) + public TModel Get(int id) { return EloqueraDb.AsQueryable().Single(c => c.Id == id); } @@ -35,7 +35,7 @@ public TModel Add(TModel model) return EloqueraDb.Insert(model); } - public void Delete(long id) + public void Delete(int id) { var itemToDelete = Get(id); EloqueraDb.Delete(itemToDelete); diff --git a/NzbDrone.Core/Datastore/EloqueraDb.cs b/NzbDrone.Core/Datastore/EloqueraDb.cs index a71d6e14e..3d8bc7a41 100644 --- a/NzbDrone.Core/Datastore/EloqueraDb.cs +++ b/NzbDrone.Core/Datastore/EloqueraDb.cs @@ -1,49 +1,56 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Eloquera.Client; namespace NzbDrone.Core.Datastore { public class EloqueraDb : IDisposable { - private readonly DB _db; + private readonly IdService _idService; + public DB Db { get; private set; } - public EloqueraDb(DB db) + public EloqueraDb(DB db, IdService idService) { - _db = db; + _idService = idService; + Db = db; } public IEnumerable AsQueryable() { - return _db.Query(); + return Db.Query(); } public T Insert(T obj) where T : BaseRepositoryModel { - obj.Id = _db.Store(obj); + _idService.EnsureIds(obj, new HashSet()); + Db.Store(obj); return obj; } - public IList InsertMany(IEnumerable objects) where T : BaseRepositoryModel + public IList InsertMany(IList objects) where T : BaseRepositoryModel { + _idService.EnsureIds(objects, new HashSet()); return DoMany(objects, Insert); } public T Update(T obj) { - _db.Store(obj); + Db.Store(obj); return obj; } - public IList UpdateMany(IEnumerable objects) + public IList UpdateMany(IList objects) { + _idService.EnsureIds(objects, new HashSet()); return DoMany(objects, Update); } public void Delete(T obj) where T : new() { - _db.Delete(obj); + Db.Delete(obj); } public void DeleteMany(IEnumerable objects) where T : new() @@ -59,9 +66,10 @@ private IList DoMany(IEnumerable objects, Func function) return objects.Select(function).ToList(); } + public void Dispose() { - _db.Dispose(); + Db.Dispose(); } } } diff --git a/NzbDrone.Core/Datastore/EloqueraDbFactory.cs b/NzbDrone.Core/Datastore/EloqueraDbFactory.cs index 1f1c138ba..83ac51ac0 100644 --- a/NzbDrone.Core/Datastore/EloqueraDbFactory.cs +++ b/NzbDrone.Core/Datastore/EloqueraDbFactory.cs @@ -53,18 +53,18 @@ private EloqueraDb InternalCreate(string connectionString, string databaseName) //This seemse to cause Invalid Cast Exceptions... WTF //db.RefreshMode = ObjectRefreshMode.AlwaysReturnUpdatedValues; - + RegisterTypeRules(); RegisterTypes(db); - return new EloqueraDb(db); + return new EloqueraDb(db, new IdService(new IndexProvider(db))); } private void RegisterTypeRules() { RootFolder rootFolder = null; DB.TypeRules - //.SetIDField(() => rootFolder.Id) + //.SetIDField(() => rootFolder.Id) .IgnoreProperty(() => rootFolder.FreeSpace) .IgnoreProperty(() => rootFolder.UnmappedFolders); diff --git a/NzbDrone.Core/Datastore/IdService.cs b/NzbDrone.Core/Datastore/IdService.cs new file mode 100644 index 000000000..77004655d --- /dev/null +++ b/NzbDrone.Core/Datastore/IdService.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace NzbDrone.Core.Datastore +{ + public class IdService + { + private readonly IndexProvider _indexProvider; + + private static readonly ConcurrentDictionary> propertyCache = new ConcurrentDictionary>(); + + public IdService(IndexProvider indexProvider) + { + _indexProvider = indexProvider; + } + + public void EnsureIds(T obj, HashSet context) + { + //context is use to prevent infinite loop if objects are recursively looped. + if (obj == null || context.Contains(obj)) + { + return; + } + + context.Add(obj); + + var modelBase = obj as BaseRepositoryModel; + + if (modelBase != null && modelBase.Id == 0) + { + modelBase.Id = _indexProvider.Next(obj.GetType()); + } + + foreach (var propertyInfo in GetPotentialProperties(obj.GetType())) + { + var propValue = propertyInfo.GetValue(obj, null); + + var list = propValue as IEnumerable; + + if (list != null) + { + foreach (var item in list) + { + EnsureIds(item, context); + } + } + else + { + EnsureIds(propValue, context); + + } + + } + } + + private IList GetPotentialProperties(Type type) + { + IList result; + if (!propertyCache.TryGetValue(type.FullName, out result)) + { + result = type.GetProperties().Where(ShouldCrawl).ToList(); + propertyCache.TryAdd(type.FullName, result); + } + + return result; + } + + private bool ShouldCrawl(PropertyInfo propertyInfo) + { + return propertyInfo.CanRead && ShouldCrawl(propertyInfo.PropertyType); + } + + private bool ShouldCrawl(Type type) + { + if (type.IsGenericType) + { + var genericArg = type.GetGenericArguments()[0]; + + //skip if generic argument type isn't interesting + if (!ShouldCrawl(genericArg)) + { + return false; + } + + var listType = typeof(IList<>).MakeGenericType(genericArg); + return listType.IsAssignableFrom(type); + } + + return type.IsClass && type.FullName.StartsWith("NzbDrone"); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/IndexProvider.cs b/NzbDrone.Core/Datastore/IndexProvider.cs new file mode 100644 index 000000000..9854da8be --- /dev/null +++ b/NzbDrone.Core/Datastore/IndexProvider.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Eloquera.Client; + +namespace NzbDrone.Core.Datastore +{ + public interface IProvideIndex + { + int Next(Type type); + } + + public class IndexProvider : IProvideIndex + { + private readonly DB _db; + + private static object _lock = new object(); + + public IndexProvider(DB db) + { + _db = db; + + if (db.IsTypeRegistered(typeof(IndexList))) + { + db.RegisterType(typeof(IndexList)); + } + + lock (_lock) + { + try + { + _db.Query().Count(); + + } + catch (EloqueraException ex) + { + _db.Store(new IndexList()); + } + } + + } + + public int Next(Type type) + { + if (type == null) + { + throw new ArgumentException(); + } + + var key = type.Name; + + lock (_lock) + { + var indexList = _db.Query().Single(); + + var indexInfo = indexList.SingleOrDefault(c => c.Type == key); + + if (indexInfo == null) + { + indexInfo = new IndexInfo { Type = key }; + indexList.Add(indexInfo); + } + + indexInfo.Index++; + + _db.Store(indexList); + + return indexInfo.Index; + } + } + + public class IndexList : List { } + + public class IndexInfo + { + public string Type { get; set; } + public int Index { get; set; } + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index f140c3221..59632f165 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -236,6 +236,8 @@ + + diff --git a/NzbDrone.Core/RootFolders/RootFolderService.cs b/NzbDrone.Core/RootFolders/RootFolderService.cs index 0319baa2b..bc21e7ab4 100644 --- a/NzbDrone.Core/RootFolders/RootFolderService.cs +++ b/NzbDrone.Core/RootFolders/RootFolderService.cs @@ -13,7 +13,7 @@ public interface IRootFolderService { List All(); RootFolder Add(RootFolder rootDir); - void Remove(long rootDirId); + void Remove(int rootDirId); List GetUnmappedFolders(string path); Dictionary FreeSpaceOnDrives(); } @@ -63,7 +63,7 @@ public virtual RootFolder Add(RootFolder rootFolder) return rootFolder; } - public virtual void Remove(long rootDirId) + public virtual void Remove(int rootDirId) { _rootFolderRepository.Delete(rootDirId); } diff --git a/NzbDrone.Services.Api/NzbDrone.Services.Api.csproj b/NzbDrone.Services.Api/NzbDrone.Services.Api.csproj index f347d90b7..5a8c0aa61 100644 --- a/NzbDrone.Services.Api/NzbDrone.Services.Api.csproj +++ b/NzbDrone.Services.Api/NzbDrone.Services.Api.csproj @@ -150,7 +150,7 @@ False True - 1306 + 28501 / http://localhost:1306/ False diff --git a/NzbDrone.Services/NzbDrone.Services.Service/NzbDrone.Services.Service.csproj b/NzbDrone.Services/NzbDrone.Services.Service/NzbDrone.Services.Service.csproj index 91f000b84..dc17eb56c 100644 --- a/NzbDrone.Services/NzbDrone.Services.Service/NzbDrone.Services.Service.csproj +++ b/NzbDrone.Services/NzbDrone.Services.Service/NzbDrone.Services.Service.csproj @@ -366,7 +366,7 @@ False True - 17584 + 28496 / http://localhost:62182/ False diff --git a/NzbDrone.ncrunchsolution b/NzbDrone.ncrunchsolution index e88d63cdb..13efc6321 100644 --- a/NzbDrone.ncrunchsolution +++ b/NzbDrone.ncrunchsolution @@ -1,6 +1,6 @@ 1 - False + True true true UseDynamicAnalysis