diff --git a/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs b/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs new file mode 100644 index 000000000..b5c0c6f46 --- /dev/null +++ b/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs @@ -0,0 +1,54 @@ +using System.Linq; +using System; +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using Db4objects.Db4o.Linq; + +namespace NzbDrone.Core.Test.Datastore +{ + [TestFixture] + public class ObjectDatabaseFixture : CoreTest + { + [SetUp] + public void SetUp() + { + WithObjectDb(); + } + + [Test] + public void should_be_able_to_write_to_database() + { + + var series = Builder.CreateNew().Build(); + + ObjDb.Save(series); + + ObjDb.Ext().Purge(); + + ObjDb.AsQueryable().Should().HaveCount(1); + + } + + [Test] + public void should_not_store_dirty_data_in_cache() + { + var episode = Builder.CreateNew().Build(); + + //Save series without episode attached + ObjDb.Save(episode); + + ObjDb.AsQueryable().Single().Series.Should().BeNull(); + + episode.Series = Builder.CreateNew().Build(); + + ObjDb.AsQueryable().Single().Series.Should().BeNull(); + + } + } +} diff --git a/NzbDrone.Core.Test/Framework/CoreTest.cs b/NzbDrone.Core.Test/Framework/CoreTest.cs index 9093e2fe9..49c76790a 100644 --- a/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using Db4objects.Db4o.IO; using NUnit.Framework; using NzbDrone.Core.Datastore; using NzbDrone.Core.Model.Notification; @@ -9,14 +10,14 @@ namespace NzbDrone.Core.Test.Framework { - public class CoreTest : TestBase + public abstract class CoreTest : TestBase { private string _dbTemplateName; [SetUp] public void CoreTestSetup() { - if(NCrunch.Framework.NCrunchEnvironment.NCrunchIsResident()) + if (NCrunch.Framework.NCrunchEnvironment.NCrunchIsResident()) { _dbTemplateName = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()) + ".sdf"; } @@ -24,6 +25,7 @@ public void CoreTestSetup() { _dbTemplateName = "db_template.sdf"; } + CreateDataBaseTemplate(); } @@ -71,12 +73,32 @@ protected IDatabase Db } } + private IObjectDbSession _objDb; + protected IObjectDbSession ObjDb + { + get + { + if (_objDb == null) + throw new InvalidOperationException("Test object database doesn't exists. Make sure you call WithRealDb() if you intend to use an actual database."); + + return _objDb; + } + } + + + protected void WithRealDb() { _db = GetEmptyDatabase(); Mocker.SetConstant(Db); } + protected void WithObjectDb() + { + _objDb = new ObjectDbSessionFactory().Create(new PagingMemoryStorage()); + Mocker.SetConstant(ObjDb); + } + protected static ProgressNotification MockNotification { get @@ -106,6 +128,11 @@ public void CoreTestTearDown() } catch (IOException) { } } + + if (_objDb != null) + { + _objDb.Dispose(); + } } } } diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index e6403ee35..0771699af 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -65,6 +65,15 @@ ..\packages\AutoMoq.1.6.1\lib\AutoMoq.dll + + ..\packages\db4o-devel.8.1.184.15492\lib\net40\Db4objects.Db4o.dll + + + ..\packages\db4o-devel.8.1.184.15492\lib\net40\Db4objects.Db4o.Data.Services.dll + + + ..\packages\db4o-devel.8.1.184.15492\lib\net40\Db4objects.Db4o.Linq.dll + False ..\Libraries\DeskMetrics\DeskMetrics.NET.dll @@ -88,6 +97,9 @@ ..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.Configuration.dll + + ..\packages\db4o-devel.8.1.184.15492\lib\net40\Mono.Reflection.dll + ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll @@ -134,6 +146,7 @@ + diff --git a/NzbDrone.Core.Test/packages.config b/NzbDrone.Core.Test/packages.config index c9d7a612d..ca7aad22c 100644 --- a/NzbDrone.Core.Test/packages.config +++ b/NzbDrone.Core.Test/packages.config @@ -3,6 +3,7 @@ + diff --git a/NzbDrone.Core/Datastore/DbProviderFactory.cs b/NzbDrone.Core/Datastore/DbProviderFactory.cs index 4e94284d3..697296550 100644 --- a/NzbDrone.Core/Datastore/DbProviderFactory.cs +++ b/NzbDrone.Core/Datastore/DbProviderFactory.cs @@ -1,6 +1,10 @@ -using System; +using System; using System.Data.Common; using System.Data.SqlServerCe; +using Db4objects.Db4o; +using Db4objects.Db4o.IO; +using Db4objects.Db4o.Internal; +using Db4objects.Db4o.Internal.Config; using StackExchange.Profiling; using StackExchange.Profiling.Data; @@ -23,4 +27,23 @@ public override DbConnection CreateConnection() return connection; } } + + + public class ObjectDbSessionFactory + { + public IObjectDbSession Create(IStorage storage = null) + { + if (storage == null) + { + storage = new FileStorage(); + } + + var config = Db4oEmbedded.NewConfiguration(); + config.File.Storage = storage; + + + var objectContainer = Db4oEmbedded.OpenFile(config, "nzbdrone.db4o"); + return new ObjectDbSession((ObjectContainerBase)objectContainer); + } + } } diff --git a/NzbDrone.Core/Datastore/IObjectDbSession.cs b/NzbDrone.Core/Datastore/IObjectDbSession.cs new file mode 100644 index 000000000..e1eace60d --- /dev/null +++ b/NzbDrone.Core/Datastore/IObjectDbSession.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Db4objects.Db4o; +using Db4objects.Db4o.Internal; +using Db4objects.Db4o.Internal.References; + +namespace NzbDrone.Core.Datastore +{ + public interface IObjectDbSession : IObjectContainer + { + void Save(object obj); + void Save(object obj, int depth); + void SaveAll(Transaction transaction, IEnumerator objects); + + void Update(object obj); + void Update(object obj, int depth); + void UpdateAll(Transaction transaction, IEnumerator objects); + } + + + public class ObjectDbSession : ObjectContainerSession, IObjectDbSession + { + private NoCahceRefrenceSystem _noCacheRefSystem; + + public ObjectDbSession(ObjectContainerBase server) + : base(server, server.NewTransaction(server.SystemTransaction(), new NoCahceRefrenceSystem(), false)) + { + _transaction.SetOutSideRepresentation(this); + _noCacheRefSystem = (NoCahceRefrenceSystem)_transaction.ReferenceSystem(); + } + + public override void Store(object obj) + { + throw new InvalidOperationException("Store is not supported. please use Save() or Update()"); + } + + public override void StoreAll(Transaction transaction, IEnumerator objects) + { + throw new InvalidOperationException("Store is not supported. please use Save() or Update()"); + + } + + public override void Store(object obj, int depth) + { + throw new InvalidOperationException("Store is not supported. please use Save() or Update()"); + } + + public void Save(object obj) + { + ValidateSave(obj); + base.Store(obj); + Commit(); + } + + public void Save(object obj, int depth) + { + ValidateSave(obj); + base.Store(obj, depth); + Commit(); + + } + + public void SaveAll(Transaction transaction, IEnumerator objects) + { + var obj = objects.ToIEnumerable().ToList(); + obj.ForEach(c => ValidateSave(c)); + + base.StoreAll(transaction, obj.GetEnumerator()); + Commit(); + + } + + + public void Update(object obj) + { + ValidateUpdate(obj); + base.Store(obj); + Commit(); + } + + public void Update(object obj, int depth) + { + ValidateUpdate(obj); + base.Store(obj, depth); + Commit(); + } + + public void UpdateAll(Transaction transaction, IEnumerator objects) + { + var obj = objects.ToIEnumerable().ToList(); + obj.ForEach(c => ValidateUpdate(c)); + + base.StoreAll(transaction, obj.GetEnumerator()); + Commit(); + } + + public void UpdateAll(Transaction transaction, IEnumerator objects) + { + throw new NotImplementedException(); + } + + public new void Purge() + { + _noCacheRefSystem.Reset(); + } + + private void ValidateSave(object obj) + { + if (IsAttached(obj)) + { + throw new InvalidOperationException("Attempted to save an object that is already attached to database"); + } + } + + private void ValidateUpdate(object obj) + { + if (!IsAttached(obj)) + { + throw new InvalidOperationException("Attempted to update an object that is not attached to database"); + } + } + + private bool IsAttached(object obj) + { + return base.Ext().GetID(obj) > 0; + } + } + + + public static class Ext + { + public static IEnumerable ToIEnumerable(this IEnumerator enumerator) + { + while (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/NoCahceRefrenceSystem.cs b/NzbDrone.Core/Datastore/NoCahceRefrenceSystem.cs new file mode 100644 index 000000000..73e5eac1b --- /dev/null +++ b/NzbDrone.Core/Datastore/NoCahceRefrenceSystem.cs @@ -0,0 +1,116 @@ +using System.Linq; +using Db4objects.Db4o; +using Db4objects.Db4o.Foundation; +using Db4objects.Db4o.Internal; +using Db4objects.Db4o.Internal.References; + +namespace NzbDrone.Core.Datastore +{ + public class NoCahceRefrenceSystem : IReferenceSystem + { + private ObjectReference _hashCodeTree; + private ObjectReference _idTree; + + internal NoCahceRefrenceSystem() + { + + } + + public virtual void AddNewReference(ObjectReference @ref) + { + AddReference(@ref); + } + + public virtual void AddExistingReference(ObjectReference @ref) + { + AddReference(@ref); + } + + public virtual void Commit() + { + Reset(); + } + + + + public virtual ObjectReference ReferenceForId(int id) + { + if (DTrace.enabled) + DTrace.GetYapobject.Log(id); + if (_idTree == null) + return null; + if (!ObjectReference.IsValidId(id)) + return null; + else + return _idTree.Id_find(id); + } + + public virtual ObjectReference ReferenceForObject(object obj) + { + if (_hashCodeTree == null) + return null; + else + return _hashCodeTree.Hc_find(obj); + } + + public virtual void RemoveReference(ObjectReference @ref) + { + if (DTrace.enabled) + DTrace.ReferenceRemoved.Log(@ref.GetID()); + if (_hashCodeTree != null) + _hashCodeTree = _hashCodeTree.Hc_remove(@ref); + if (_idTree == null) + return; + _idTree = _idTree.Id_remove(@ref); + } + + public virtual void Rollback() + { + Reset(); + } + + public virtual void TraverseReferences(IVisitor4 visitor) + { + if (_hashCodeTree == null) + return; + _hashCodeTree.Hc_traverse(visitor); + } + + public virtual void Discarded() + { + } + + + public void Reset() + { + _hashCodeTree = null; + _idTree = null; + } + + private void AddReference(ObjectReference @ref) + { + @ref.Ref_init(); + IdAdd(@ref); + HashCodeAdd(@ref); + } + + private void HashCodeAdd(ObjectReference @ref) + { + if (_hashCodeTree == null) + _hashCodeTree = @ref; + else + _hashCodeTree = _hashCodeTree.Hc_add(@ref); + } + + private void IdAdd(ObjectReference @ref) + { + if (DTrace.enabled) + DTrace.IdTreeAdd.Log(@ref.GetID()); + if (_idTree == null) + _idTree = @ref; + else + _idTree = _idTree.Id_add(@ref); + } + + } +} diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 73ec30106..a63b19dc8 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -133,6 +133,15 @@ False ..\packages\DataTables.Mvc.Core.0.1.0.85\lib\DataTables.Mvc.Core.dll + + ..\packages\db4o-devel.8.1.184.15492\lib\net40\Db4objects.Db4o.dll + + + ..\packages\db4o-devel.8.1.184.15492\lib\net40\Db4objects.Db4o.Data.Services.dll + + + ..\packages\db4o-devel.8.1.184.15492\lib\net40\Db4objects.Db4o.Linq.dll + ..\Libraries\DeskMetrics\DeskMetrics.NET.dll @@ -168,6 +177,9 @@ ..\packages\MiniProfiler.2.0.2\lib\net40\MiniProfiler.dll + + ..\packages\db4o-devel.8.1.184.15492\lib\net40\Mono.Reflection.dll + False ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll @@ -223,6 +235,8 @@ + + diff --git a/NzbDrone.Core/packages.config b/NzbDrone.Core/packages.config index 98f1f8ef1..41f27a466 100644 --- a/NzbDrone.Core/packages.config +++ b/NzbDrone.Core/packages.config @@ -2,6 +2,7 @@ + diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Web.config b/NzbDrone.Services/NzbDrone.Services.Service/Web.config index d93876ae7..64d264fb2 100644 --- a/NzbDrone.Services/NzbDrone.Services.Service/Web.config +++ b/NzbDrone.Services/NzbDrone.Services.Service/Web.config @@ -16,7 +16,7 @@ - + diff --git a/NzbDrone.Test.Common/TestBase.cs b/NzbDrone.Test.Common/TestBase.cs index ff9b64bca..b5968ae5f 100644 --- a/NzbDrone.Test.Common/TestBase.cs +++ b/NzbDrone.Test.Common/TestBase.cs @@ -8,9 +8,8 @@ namespace NzbDrone.Test.Common { - public class TestBase : LoggingTest + public abstract class TestBase : LoggingTest { - protected const string INTEGRATION_TEST = "Integration Test";