added support for 0 based sequential ids to our object db.

This commit is contained in:
kay.one 2013-02-15 19:50:22 -08:00
parent a0c7ccfe7c
commit c6fa3cc02b
17 changed files with 375 additions and 29 deletions

View File

@ -18,7 +18,7 @@ namespace NzbDrone.Api.RootFolders
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 @@ namespace NzbDrone.Api.RootFolders
return _rootFolderService.All().AsResponse();
}
private Response DeleteRootFolder(long folderId)
private Response DeleteRootFolder(int folderId)
{
_rootFolderService.Remove(folderId);
return new Response { StatusCode = HttpStatusCode.OK };

View File

@ -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<IndexProvider>
{
[SetUp]
public void Setup()
{
WithObjectDb();
}
[Test]
public void should_be_able_to_get_sequential_numbers()
{
var indexs = new List<int>();
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<int>();
var episodeIndex = new List<int>();
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);
}
}
}

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Eloquera.Client;
using FizzWare.NBuilder;
@ -85,13 +86,40 @@ namespace NzbDrone.Core.Test.Datastore
[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<Series>().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<Series>();
@ -122,5 +150,15 @@ namespace NzbDrone.Core.Test.Datastore
{
public string Field1 { get; set; }
}
public class NestedModel : BaseRepositoryModel
{
public NestedModel()
{
List = new List<NestedModel> { this };
}
public IList<NestedModel> List { get; set; }
}
}

View File

@ -6,8 +6,35 @@ using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Test.Framework
{
public abstract class ObjectDbTest<TSubject> : ObjectDbTest where TSubject : class
{
private TSubject _subject;
[SetUp]
public void CoreTestSetup()
{
_subject = null;
}
protected TSubject Subject
{
get
{
if (_subject == null)
{
_subject = Mocker.Resolve<TSubject>();
}
return _subject;
}
}
}
public abstract class ObjectDbTest : CoreTest
{
private EloqueraDb _db;
protected EloqueraDb Db
{
@ -32,6 +59,7 @@ namespace NzbDrone.Core.Test.Framework
}
Mocker.SetConstant(Db);
Mocker.SetConstant(Db.Db);
}
[TearDown]

View File

@ -146,6 +146,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Datastore\IndexProviderFixture.cs" />
<Compile Include="Datastore\ObjectDatabaseFixture.cs" />
<Compile Include="Framework\CoreTest.cs" />
<Compile Include="Framework\ObjectDbTest.cs" />

View File

@ -30,6 +30,30 @@
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.Integeration\.ServiceIntegerationFixture\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.QualityProfileTest\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.TvDbProviderTest\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.MediaFileProviderTest\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.EpisodeProviderTests\.EpisodeProviderTest_DeleteInvalidEpisodes\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.DecisionEngineTests\.UpgradeHistorySpecificationFixture\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.DecisionEngineTests\.QualityAllowedByProfileSpecificationFixture\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ParserTests\.QualityParserFixture\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ParserTests\.ParserFixture\..*</RegularExpression>
</RegexTestSelector>
</IgnoredTests>
<AdditionalFilesToInclude>..\NzbDrone.Core\bin\Debug\Eloquera.Server.exe</AdditionalFilesToInclude>
<HiddenWarnings>PostBuildEventDisabled</HiddenWarnings>

View File

@ -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; }
}
}

View File

@ -6,9 +6,9 @@ namespace NzbDrone.Core.Datastore
public interface IBasicRepository<TModel>
{
List<TModel> All();
TModel Get(long rootFolderId);
TModel Get(int rootFolderId);
TModel Add(TModel rootFolder);
void Delete(long rootFolderId);
void Delete(int rootFolderId);
}
public class BasicRepository<TModel> : IBasicRepository<TModel> where TModel : BaseRepositoryModel, new()
@ -25,7 +25,7 @@ namespace NzbDrone.Core.Datastore
return EloqueraDb.AsQueryable<TModel>().ToList();
}
public TModel Get(long id)
public TModel Get(int id)
{
return EloqueraDb.AsQueryable<TModel>().Single(c => c.Id == id);
}
@ -35,7 +35,7 @@ namespace NzbDrone.Core.Datastore
return EloqueraDb.Insert(model);
}
public void Delete(long id)
public void Delete(int id)
{
var itemToDelete = Get(id);
EloqueraDb.Delete(itemToDelete);

View File

@ -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<T> AsQueryable<T>()
{
return _db.Query<T>();
return Db.Query<T>();
}
public T Insert<T>(T obj) where T : BaseRepositoryModel
{
obj.Id = _db.Store(obj);
_idService.EnsureIds(obj, new HashSet<object>());
Db.Store(obj);
return obj;
}
public IList<T> InsertMany<T>(IEnumerable<T> objects) where T : BaseRepositoryModel
public IList<T> InsertMany<T>(IList<T> objects) where T : BaseRepositoryModel
{
_idService.EnsureIds(objects, new HashSet<object>());
return DoMany(objects, Insert);
}
public T Update<T>(T obj)
{
_db.Store(obj);
Db.Store(obj);
return obj;
}
public IList<T> UpdateMany<T>(IEnumerable<T> objects)
public IList<T> UpdateMany<T>(IList<T> objects)
{
_idService.EnsureIds(objects, new HashSet<object>());
return DoMany(objects, Update);
}
public void Delete<T>(T obj) where T : new()
{
_db.Delete(obj);
Db.Delete(obj);
}
public void DeleteMany<T>(IEnumerable<T> objects) where T : new()
@ -59,9 +66,10 @@ namespace NzbDrone.Core.Datastore
return objects.Select(function).ToList();
}
public void Dispose()
{
_db.Dispose();
Db.Dispose();
}
}
}

View File

@ -53,18 +53,18 @@ namespace NzbDrone.Core.Datastore
//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);

View File

@ -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<string, IList<PropertyInfo>> propertyCache = new ConcurrentDictionary<string, IList<PropertyInfo>>();
public IdService(IndexProvider indexProvider)
{
_indexProvider = indexProvider;
}
public void EnsureIds<T>(T obj, HashSet<object> 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<PropertyInfo> GetPotentialProperties(Type type)
{
IList<PropertyInfo> 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");
}
}
}

View File

@ -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<IndexList>().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<IndexList>().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<IndexInfo> { }
public class IndexInfo
{
public string Type { get; set; }
public int Index { get; set; }
}
}
}

View File

@ -236,6 +236,8 @@
<Compile Include="Datastore\ConnectionFactory.cs" />
<Compile Include="Datastore\EloqueraDb.cs" />
<Compile Include="Datastore\EloqueraDbFactory.cs" />
<Compile Include="Datastore\IdService.cs" />
<Compile Include="Datastore\IndexProvider.cs" />
<Compile Include="Datastore\MigrationLogger.cs" />
<Compile Include="Datastore\MigrationsHelper.cs" />
<Compile Include="Datastore\CustomeMapper.cs" />

View File

@ -13,7 +13,7 @@ namespace NzbDrone.Core.RootFolders
{
List<RootFolder> All();
RootFolder Add(RootFolder rootDir);
void Remove(long rootDirId);
void Remove(int rootDirId);
List<String> GetUnmappedFolders(string path);
Dictionary<string, ulong> FreeSpaceOnDrives();
}
@ -63,7 +63,7 @@ namespace NzbDrone.Core.RootFolders
return rootFolder;
}
public virtual void Remove(long rootDirId)
public virtual void Remove(int rootDirId)
{
_rootFolderRepository.Delete(rootDirId);
}

View File

@ -150,7 +150,7 @@
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>1306</DevelopmentServerPort>
<DevelopmentServerPort>28501</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:1306/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>

View File

@ -366,7 +366,7 @@
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>17584</DevelopmentServerPort>
<DevelopmentServerPort>28496</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:62182/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>

View File

@ -1,6 +1,6 @@
<SolutionConfiguration>
<FileVersion>1</FileVersion>
<AutoEnableOnStartup>False</AutoEnableOnStartup>
<AutoEnableOnStartup>True</AutoEnableOnStartup>
<AllowParallelTestExecution>true</AllowParallelTestExecution>
<AllowTestsToRunInParallelWithThemselves>true</AllowTestsToRunInParallelWithThemselves>
<FrameworkUtilisationTypeForNUnit>UseDynamicAnalysis</FrameworkUtilisationTypeForNUnit>