value injector should map lazy loaded values properly.

pull/4/head
kay.one 11 years ago
parent c34ae218e8
commit e5cc0c1a93

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

@ -1,4 +1,6 @@
using System; using System;
using FluentAssertions;
using Marr.Data;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Api.Config; using NzbDrone.Api.Config;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
@ -40,5 +42,58 @@ namespace NzbDrone.Api.Test.MappingTests
MappingValidation.ValidateMapping(modelType, resourceType); 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();
}
} }
} }

@ -73,11 +73,14 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ClientSchemaTests\SchemaBuilderFixture.cs" /> <Compile Include="ClientSchemaTests\SchemaBuilderFixture.cs" />
<Compile Include="MappingTests\ReflectionExtensionFixture.cs" />
<Compile Include="MappingTests\ResourceMappingFixture.cs" /> <Compile Include="MappingTests\ResourceMappingFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<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"> <ProjectReference Include="..\NzbDrone.Api\NzbDrone.Api.csproj">
<Project>{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}</Project> <Project>{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}</Project>
<Name>NzbDrone.Api</Name> <Name>NzbDrone.Api</Name>

@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Marr.Data;
using Omu.ValueInjecter; using Omu.ValueInjecter;
namespace NzbDrone.Api.Mapping namespace NzbDrone.Api.Mapping
@ -41,22 +42,16 @@ namespace NzbDrone.Api.Mapping
if (conventionInfo.SourceProp.Type.IsGenericType) if (conventionInfo.SourceProp.Type.IsGenericType)
{ {
var genericInterfaces = conventionInfo.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces();
//handle IEnumerable<> also ICollection<> IList<> List<> //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]; return MapLists(conventionInfo);
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"); if (genericInterfaces.Any(i => i == typeof(ILazyLoaded)))
foreach (var o in (IEnumerable)conventionInfo.SourceProp.Value) {
{ return MapLazy(conventionInfo);
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;
} }
//unhandled generic type, you could also return null or throw //unhandled generic type, you could also return null or throw
@ -67,5 +62,38 @@ namespace NzbDrone.Api.Mapping
return Activator.CreateInstance(conventionInfo.SourceProp.Type) return Activator.CreateInstance(conventionInfo.SourceProp.Type)
.InjectFrom<CloneInjection>(conventionInfo.SourceProp.Value); .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;
}
} }
} }

@ -160,6 +160,10 @@
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<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"> <ProjectReference Include="..\NzbDrone.Common\NzbDrone.Common.csproj">
<Project>{f2be0fdf-6e47-4827-a420-dd4ef82407f8}</Project> <Project>{f2be0fdf-6e47-4827-a420-dd4ef82407f8}</Project>
<Name>NzbDrone.Common</Name> <Name>NzbDrone.Common</Name>

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

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

@ -404,12 +404,6 @@
<Compile Include="Providers\BackupProvider.cs"> <Compile Include="Providers\BackupProvider.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </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"> <Compile Include="Configuration\ConfigService.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>

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

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

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

@ -30,15 +30,15 @@ namespace NzbDrone.Core.Tv
public String SeriesTitle { get; private set; } public String SeriesTitle { get; private set; }
public Series Series { get; set; } public LazyLoaded<Series> Series { get; set; }
public LazyLoaded<EpisodeFile> EpisodeFile { get; set; } public LazyLoaded<EpisodeFile> EpisodeFile { get; set; }
public override string ToString() 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} - {1:yyyy-MM-dd}", seriesTitle, AirDate.Value);
return string.Format("{0} - S{1:00}E{2:00}", seriesTitle, SeasonNumber, EpisodeNumber); return string.Format("{0} - S{1:00}E{2:00}", seriesTitle, SeasonNumber, EpisodeNumber);

@ -208,7 +208,7 @@ namespace NzbDrone.Core.Tv
int episodeCount = 0; int episodeCount = 0;
foreach (var episode in group.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber)) 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++; episodeCount++;
} }
} }

Loading…
Cancel
Save