value injector should map lazy loaded values properly.

This commit is contained in:
kay.one 2013-05-31 19:49:15 -07:00
parent c34ae218e8
commit e5cc0c1a93
13 changed files with 163 additions and 267 deletions

View File

@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
namespace Marr.Data
{
public interface ILazyLoaded : ICloneable
{
void Prepare(Func<IDataMapper> dbCreator, object parent);
bool IsLoaded { get; }
void Prepare(Func<IDataMapper> dataMapperFactory, object parent);
void LazyLoad();
}
@ -18,8 +15,7 @@ namespace Marr.Data
/// <typeparam name="TChild"></typeparam>
public class LazyLoaded<TChild> : ILazyLoaded
{
protected TChild _child;
protected bool _isLoaded;
protected TChild _value;
public LazyLoaded()
{
@ -27,8 +23,8 @@ namespace Marr.Data
public LazyLoaded(TChild val)
{
_child = val;
_isLoaded = true;
_value = val;
IsLoaded = true;
}
public TChild Value
@ -36,11 +32,13 @@ namespace Marr.Data
get
{
LazyLoad();
return _child;
return _value;
}
}
public virtual void Prepare(Func<IDataMapper> dbCreator, object parent)
public bool IsLoaded { get; protected set; }
public virtual void Prepare(Func<IDataMapper> dataMapperFactory, object parent)
{ }
public virtual void LazyLoad()
@ -50,7 +48,7 @@ namespace Marr.Data
{
return new LazyLoaded<TChild>(val);
}
public static implicit operator TChild(LazyLoaded<TChild> lazy)
{
return lazy.Value;
@ -58,7 +56,7 @@ namespace Marr.Data
public object Clone()
{
return this.MemberwiseClone();
return MemberwiseClone();
}
}
@ -70,7 +68,7 @@ namespace Marr.Data
internal class LazyLoaded<TParent, TChild> : LazyLoaded<TChild>
{
private TParent _parent;
private Func<IDataMapper> _dbCreator;
private Func<IDataMapper> _dbMapperFactory;
private readonly Func<IDataMapper, TParent, TChild> _query;
private readonly Func<TParent, bool> _condition;
@ -81,46 +79,41 @@ namespace Marr.Data
_condition = condition;
}
public LazyLoaded(TChild val)
public LazyLoaded(TChild val)
: base(val)
{
_child = val;
_isLoaded = true;
_value = val;
IsLoaded = true;
}
/// <summary>
/// The second part of the initialization happens when the entity is being built.
/// </summary>
/// <param name="dbCreator">Knows how to instantiate a new IDataMapper.</param>
/// <param name="dataMapperFactory">Knows how to instantiate a new IDataMapper.</param>
/// <param name="parent">The parent entity.</param>
public override void Prepare(Func<IDataMapper> dbCreator, object parent)
public override void Prepare(Func<IDataMapper> dataMapperFactory, object parent)
{
_dbCreator = dbCreator;
_dbMapperFactory = dataMapperFactory;
_parent = (TParent)parent;
}
public bool IsLoaded
{
get { return _isLoaded; }
}
public override void LazyLoad()
{
if (!_isLoaded)
if (!IsLoaded)
{
if (_condition != null && _condition(_parent))
{
using (IDataMapper db = _dbCreator())
using (IDataMapper db = _dbMapperFactory())
{
_child = _query(db, _parent);
_value = _query(db, _parent);
}
}
else
{
_child = default(TChild);
_value = default(TChild);
}
_isLoaded = true;
IsLoaded = true;
}
}

View File

@ -1,4 +1,6 @@
using System;
using FluentAssertions;
using Marr.Data;
using NUnit.Framework;
using NzbDrone.Api.Config;
using NzbDrone.Api.Episodes;
@ -40,5 +42,58 @@ namespace NzbDrone.Api.Test.MappingTests
MappingValidation.ValidateMapping(modelType, resourceType);
}
[Test]
public void should_map_lay_loaded_values_should_not_be_inject_if_not_loaded()
{
var modelWithLazy = new ModelWithLazy()
{
Guid = new TestLazyLoaded<Guid>()
};
modelWithLazy.InjectTo<ModelWithNoLazy>().Guid.Should().BeEmpty();
modelWithLazy.Guid.IsLoaded.Should().BeFalse();
}
[Test]
public void should_map_lay_loaded_values_should_be_inject_if_loaded()
{
var guid = Guid.NewGuid();
var modelWithLazy = new ModelWithLazy()
{
Guid = new LazyLoaded<Guid>(guid)
};
modelWithLazy.InjectTo<ModelWithNoLazy>().Guid.Should().Be(guid);
modelWithLazy.Guid.IsLoaded.Should().BeTrue();
}
}
public class ModelWithLazy
{
public LazyLoaded<Guid> Guid { get; set; }
}
public class ModelWithNoLazy
{
public Guid Guid { get; set; }
}
public class TestLazyLoaded<T> : LazyLoaded<T>
{
public TestLazyLoaded()
{
}
public override void Prepare(Func<IDataMapper> dataMapperFactory, object parent)
{
throw new InvalidOperationException();
}
}
}

View File

@ -73,11 +73,14 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ClientSchemaTests\SchemaBuilderFixture.cs" />
<Compile Include="MappingTests\ReflectionExtensionFixture.cs" />
<Compile Include="MappingTests\ResourceMappingFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
<Project>{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}</Project>
<Name>Marr.Data</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone.Api\NzbDrone.Api.csproj">
<Project>{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}</Project>
<Name>NzbDrone.Api</Name>

View File

@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Marr.Data;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Mapping
@ -41,22 +42,16 @@ namespace NzbDrone.Api.Mapping
if (conventionInfo.SourceProp.Type.IsGenericType)
{
var genericInterfaces = conventionInfo.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces();
//handle IEnumerable<> also ICollection<> IList<> List<>
if (conventionInfo.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces().Any(d => d == typeof(IEnumerable)))
if (genericInterfaces.Any(d => d == typeof(IEnumerable)))
{
var t = conventionInfo.SourceProp.Type.GetGenericArguments()[0];
if (t.IsValueType || t == typeof(string)) return conventionInfo.SourceProp.Value;
return MapLists(conventionInfo);
}
var tlist = typeof(List<>).MakeGenericType(t);
var list = Activator.CreateInstance(tlist);
var addMethod = tlist.GetMethod("Add");
foreach (var o in (IEnumerable)conventionInfo.SourceProp.Value)
{
var e = Activator.CreateInstance(t).InjectFrom<CloneInjection>(o);
addMethod.Invoke(list, new[] { e }); // in 4.0 you can use dynamic and just do list.Add(e);
}
return list;
if (genericInterfaces.Any(i => i == typeof(ILazyLoaded)))
{
return MapLazy(conventionInfo);
}
//unhandled generic type, you could also return null or throw
@ -67,5 +62,38 @@ namespace NzbDrone.Api.Mapping
return Activator.CreateInstance(conventionInfo.SourceProp.Type)
.InjectFrom<CloneInjection>(conventionInfo.SourceProp.Value);
}
private static object MapLazy(ConventionInfo conventionInfo)
{
var genericArgument = conventionInfo.SourceProp.Type.GetGenericArguments()[0];
dynamic lazy = conventionInfo.SourceProp.Value;
if (lazy.IsLoaded && conventionInfo.TargetProp.Type.IsAssignableFrom(genericArgument))
{
return lazy.Value;
}
return null;
}
private static object MapLists(ConventionInfo conventionInfo)
{
var t = conventionInfo.SourceProp.Type.GetGenericArguments()[0];
if (t.IsValueType || t == typeof(string)) return conventionInfo.SourceProp.Value;
var tlist = typeof(List<>).MakeGenericType(t);
var list = Activator.CreateInstance(tlist);
var addMethod = tlist.GetMethod("Add");
foreach (var o in (IEnumerable)conventionInfo.SourceProp.Value)
{
var e = Activator.CreateInstance(t).InjectFrom<CloneInjection>(o);
addMethod.Invoke(list, new[] { e }); // in 4.0 you can use dynamic and just do list.Add(e);
}
return list;
}
}
}

View File

@ -160,6 +160,10 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
<Project>{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}</Project>
<Name>Marr.Data</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone.Common\NzbDrone.Common.csproj">
<Project>{f2be0fdf-6e47-4827-a420-dd4ef82407f8}</Project>
<Name>NzbDrone.Common</Name>

View File

@ -89,6 +89,7 @@
<Compile Include="DiskProviderFixture.cs" />
<Compile Include="EnviromentProviderTest.cs" />
<Compile Include="ProcessProviderTests.cs" />
<Compile Include="ReflectionTests\ReflectionExtensionFixture.cs" />
<Compile Include="ServiceFactoryFixture.cs" />
<Compile Include="ServiceProviderTests.cs" />
<Compile Include="WebClientTests.cs" />

View File

@ -1,31 +1,31 @@
using System.Reflection;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Datastore;
using NzbDrone.Test.Common;
namespace NzbDrone.Api.Test.MappingTests
{
public class ReflectionExtensionFixture : TestBase
{
[Test]
public void should_get_properties_from_models()
{
var models = Assembly.Load("NzbDrone.Core").ImplementationsOf<ModelBase>();
foreach (var model in models)
{
model.GetSimpleProperties().Should().NotBeEmpty();
}
}
[Test]
public void should_be_able_to_get_implementations()
{
var models = Assembly.Load("NzbDrone.Core").ImplementationsOf<ModelBase>();
models.Should().NotBeEmpty();
}
}
using System.Reflection;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Datastore;
using NzbDrone.Test.Common;
namespace NzbDrone.Common.Test.ReflectionTests
{
public class ReflectionExtensionFixture : TestBase
{
[Test]
public void should_get_properties_from_models()
{
var models = Assembly.Load("NzbDrone.Core").ImplementationsOf<ModelBase>();
foreach (var model in models)
{
model.GetSimpleProperties().Should().NotBeEmpty();
}
}
[Test]
public void should_be_able_to_get_implementations()
{
var models = Assembly.Load("NzbDrone.Core").ImplementationsOf<ModelBase>();
models.Should().NotBeEmpty();
}
}
}

View File

@ -404,12 +404,6 @@
<Compile Include="Providers\BackupProvider.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Providers\Converting\AtomicParsleyProvider.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Providers\Converting\HandbrakeProvider.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Configuration\ConfigService.cs">
<SubType>Code</SubType>
</Compile>

View File

@ -1,75 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Providers.Converting
{
public class AtomicParsleyProvider
{
private readonly IConfigService _configService;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public AtomicParsleyProvider(IConfigService configService)
{
_configService = configService;
}
public AtomicParsleyProvider()
{
}
public virtual bool RunAtomicParsley(Episode episode, string outputFile)
{
throw new NotImplementedException();
var atomicParsleyLocation = _configService.GetValue("AtomicParsleyLocation", "");
var atomicParsleyTitleType = (AtomicParsleyTitleType) Convert.ToInt32(_configService.GetValue("AtomicParsley", 0));
var atomicParsleyCommand = String.Format("\"{0}\" --overWrite --title \"{1}\" --genre \"TV Shows\" --stik \"TV Show\" --TVShowName \"{2}\" --TVEpisodeNum \"{3}\" --TVSeason \"{4}\"",
outputFile, episode.Title, episode.Series.Title, episode.EpisodeNumber, episode.SeasonNumber);
//If Episode Number + Name should be in Episode Title (Number - Title)
if (atomicParsleyTitleType == AtomicParsleyTitleType.EpisodeNumber)
{
atomicParsleyCommand = String.Format("\"{0}\" --overWrite --title \"{3} - {1}\" --genre \"TV Shows\" --stik \"TV Show\" --TVShowName \"{2}\" --TVEpisodeNum \"{3}\" --TVSeason \"{4}\"",
outputFile, episode.Title, episode.Series.Title, episode.EpisodeNumber, episode.SeasonNumber);
}
//If Season/Episode Number + Name should be in Episode Title (SeasonNumber'x'EpisodeNumber - Title)
else if (atomicParsleyTitleType == AtomicParsleyTitleType.Both)
{
atomicParsleyCommand = String.Format("\"{0}\" --overWrite --title \"{4}x{3:00} - {1}\" --genre \"TV Shows\" --stik \"TV Show\" --TVShowName \"{2}\" --TVEpisodeNum \"{3}\" --TVSeason \"{4}\"",
outputFile, episode.Title, episode.Series.Title, episode.EpisodeNumber, episode.SeasonNumber);
}
try
{
var process = new Process();
process.StartInfo.FileName = Path.Combine(atomicParsleyLocation, "AtomicParsley.exe");
process.StartInfo.Arguments = atomicParsleyCommand;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
//process.OutputDataReceived += new DataReceivedEventHandler(HandBrakeOutputDataReceived);
process.Start();
//process.BeginOutputReadLine();
process.WaitForExit();
}
catch (Exception ex)
{
Logger.DebugException(ex.Message, ex);
return false;
}
return true;
}
}
}

View File

@ -1,106 +0,0 @@
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Model.Notification;
namespace NzbDrone.Core.Providers.Converting
{
public class HandbrakeProvider
{
//Interacts with Handbrake
private readonly IConfigService _configService;
private ProgressNotification _notification;
private Episode _currentEpisode;
private Regex _processingRegex =
new Regex(@"^(?:Encoding).+?(?:\,\s(?<percent>\d{1,3}\.\d{2})\s\%)(?:.+?ETA\s(?<hours>\d{2})h(?<minutes>\d{2})m(?<seconds>\d{2})s)?",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public HandbrakeProvider(IConfigService configService)
{
_configService = configService;
}
public HandbrakeProvider()
{
}
public virtual string ConvertFile(Episode episode, ProgressNotification notification)
{
_notification = notification;
_currentEpisode = episode;
var outputFile = _configService.GetValue("iPodConvertDir", "");
var handBrakePreset = _configService.GetValue("HandBrakePreset", "iPhone & iPod Touch");
var handBrakeCommand = String.Format("-i \"{0}\" -o \"{1}\" --preset=\"{2}\"", episode.EpisodeFile.Value.Path, outputFile, handBrakePreset);
var handBrakeFile = @"C:\Program Files (x86)\Handbrake\HandBrakeCLI.exe";
try
{
var process = new Process();
process.StartInfo.FileName = handBrakeFile;
process.StartInfo.Arguments = handBrakeCommand;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += new DataReceivedEventHandler(HandBrakeOutputDataReceived);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
}
catch (Exception ex)
{
Logger.DebugException(ex.Message, ex);
return String.Empty;
}
return outputFile;
}
private void HandBrakeOutputDataReceived(object obj, DataReceivedEventArgs args)
{
throw new NotImplementedException();
//args.Data contains the line writen
var match = _processingRegex.Matches(args.Data);
if (match.Count != 1)
return;
var episodeString = String.Format("{0} - {1}x{2:00}",
_currentEpisode.Series.Title,
_currentEpisode.SeasonNumber,
_currentEpisode.EpisodeNumber);
var percent = Convert.ToDecimal(match[0].Groups["percent"].Value);
int hours;
int minutes;
int seconds;
Int32.TryParse(match[0].Groups["hours"].Value, out hours);
Int32.TryParse(match[0].Groups["minutes"].Value, out minutes);
Int32.TryParse(match[0].Groups["seconds"].Value, out seconds);
if (seconds > 0 || minutes > 0 || hours > 0)
{
var eta = DateTime.Now.Add(new TimeSpan(0, hours, minutes, seconds));
_notification.CurrentMessage = String.Format("Converting: {0}, {1}%. ETA: {2}", episodeString, percent, eta);
}
else
_notification.CurrentMessage = String.Format("Converting: {0}, {1}%.", episodeString, percent);
Console.WriteLine(args.Data);
}
}
}

View File

@ -50,8 +50,7 @@ namespace NzbDrone.Core.Providers
CurrentName = currentName,
EpisodeFileId = firstEpisode.EpisodeFileId,
ProperName = properName,
SeriesId = firstEpisode.SeriesId,
SeriesTitle = firstEpisode.Series.Title
SeriesTitle = firstEpisode.Series.Value.Title
});
}
});

View File

@ -30,15 +30,15 @@ namespace NzbDrone.Core.Tv
public String SeriesTitle { get; private set; }
public Series Series { get; set; }
public LazyLoaded<Series> Series { get; set; }
public LazyLoaded<EpisodeFile> EpisodeFile { get; set; }
public override string ToString()
{
string seriesTitle = Series == null ? "[NULL]" : Series.Title;
string seriesTitle = Series == null ? "[NULL]" : Series.Value.Title;
if (Series != null && Series.SeriesType == SeriesTypes.Daily && AirDate.HasValue)
if (Series != null && Series.Value.SeriesType == SeriesTypes.Daily && AirDate.HasValue)
return string.Format("{0} - {1:yyyy-MM-dd}", seriesTitle, AirDate.Value);
return string.Format("{0} - S{1:00}E{2:00}", seriesTitle, SeasonNumber, EpisodeNumber);

View File

@ -208,7 +208,7 @@ namespace NzbDrone.Core.Tv
int episodeCount = 0;
foreach (var episode in group.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber))
{
episode.AirDate = episode.AirDate.Value.AddMinutes(episode.Series.Runtime * episodeCount);
episode.AirDate = episode.AirDate.Value.AddMinutes(episode.Series.Value.Runtime * episodeCount);
episodeCount++;
}
}