created generic provider factory

This commit is contained in:
kay.one 2013-09-21 22:20:26 -07:00 committed by kayone
parent 0b179a6086
commit 9dbfc6804f
21 changed files with 227 additions and 308 deletions

View File

@ -36,7 +36,7 @@ namespace NzbDrone.Api.Test.MappingTests
[TestCase(typeof(Episode), typeof(EpisodeResource))]
[TestCase(typeof(RootFolder), typeof(RootFolderResource))]
[TestCase(typeof(NamingConfig), typeof(NamingConfigResource))]
[TestCase(typeof(Indexer), typeof(IndexerResource))]
[TestCase(typeof(IndexerDefinition), typeof(IndexerResource))]
[TestCase(typeof(ReleaseInfo), typeof(ReleaseResource))]
[TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))]
[TestCase(typeof(DownloadDecision), typeof(ReleaseResource))]

View File

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.REST;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.ThingiProvider;
using Omu.ValueInjecter;
using FluentValidation;
using NzbDrone.Api.Mapping;
@ -62,47 +60,36 @@ namespace NzbDrone.Api.Indexers
private void UpdateIndexer(IndexerResource indexerResource)
{
var indexer = _indexerService.Get(indexerResource.Id);
indexer.InjectFrom(indexerResource);
indexer.Settings = SchemaDeserializer.DeserializeSchema(indexer.Settings, indexerResource.Fields);
var indexer = GetIndexer(indexerResource);
ValidateIndexer(indexer);
ValidateIndexer(indexer.Settings);
_indexerService.Update(indexer);
}
private static void ValidateIndexer(Indexer indexer)
private static void ValidateIndexer(IProviderConfig config)
{
if (indexer.Enable)
{
var validationResult = indexer.Settings.Validate();
var validationResult = config.Validate();
if (!validationResult.IsValid)
{
throw new ValidationException(validationResult.Errors);
}
if (!validationResult.IsValid)
{
throw new ValidationException(validationResult.Errors);
}
}
private Indexer GetIndexer(IndexerResource indexerResource)
private IndexerDefinition GetIndexer(IndexerResource indexerResource)
{
var indexer = _indexerService.Schema()
.SingleOrDefault(i =>
i.Implementation.Equals(indexerResource.Implementation,
StringComparison.InvariantCultureIgnoreCase));
if (indexer == null)
var definition = new IndexerDefinition();
definition.InjectFrom(indexerResource);
if (indexerResource.Enable)
{
throw new BadRequestException("Invalid Indexer Implementation");
ValidateIndexer(definition.Settings);
}
indexer.InjectFrom(indexerResource);
indexer.Settings = SchemaDeserializer.DeserializeSchema(indexer.Settings, indexerResource.Fields);
ValidateIndexer(indexer);
return indexer;
return definition;
}
private void DeleteIndexer(int id)

View File

@ -18,20 +18,22 @@ namespace NzbDrone.Api.Indexers
private List<IndexerResource> GetSchema()
{
var indexers = _indexerService.Schema();
/* var indexers = _indexerService.Schema();
var result = new List<IndexerResource>(indexers.Count);
var result = new List<IndexerResource>(indexers.Count);
foreach (var indexer in indexers)
{
var indexerResource = new IndexerResource();
indexerResource.InjectFrom(indexer);
indexerResource.Fields = SchemaBuilder.GenerateSchema(indexer.Settings);
foreach (var indexer in indexers)
{
var indexerResource = new IndexerResource();
indexerResource.InjectFrom(indexer);
indexerResource.Fields = SchemaBuilder.GenerateSchema(indexer.Settings);
result.Add(indexerResource);
}
result.Add(indexerResource);
}
return result;
return result;*/
return null;
}
}
}

View File

@ -57,10 +57,8 @@ namespace NzbDrone.Core.Test.IndexerTests
var indexers = Subject.All().ToList();
indexers.Should().NotBeEmpty();
indexers.Should().NotContain(c => c.Settings == null);
indexers.Should().NotContain(c => c.Instance == null);
indexers.Should().NotContain(c => c.Name == null);
indexers.Select(c => c.Name).Should().OnlyHaveUniqueItems();
indexers.Select(c => c.Instance).Should().OnlyHaveUniqueItems();
}

View File

@ -48,14 +48,14 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
public void nzbsorg_rss()
{
var indexer = new Newznab();
indexer.Settings = new NewznabSettings
{
ApiKey = "64d61d3cfd4b75e51d01cbc7c6a78275",
Url = "http://nzbs.org"
};
indexer.InstanceDefinition = new IndexerDefinition();
indexer.InstanceDefinition.Name = "nzbs.org";
indexer.Definition = new IndexerDefinition();
indexer.Definition.Name = "nzbs.org";
indexer.Definition.Settings = new NewznabSettings
{
ApiKey = "64d61d3cfd4b75e51d01cbc7c6a78275",
Url = "http://nzbs.org"
};
var result = Subject.FetchRss(indexer);

View File

@ -9,7 +9,7 @@ using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Test.ThingiProvider
{
public class ProviderRepositoryFixture : DbTest<IndexerProviderRepository, IndexerDefinition>
public class ProviderRepositoryFixture : DbTest<ProviderRepository<IndexerDefinition>, IndexerDefinition>
{
[Test]
public void should_read_write_download_provider()

View File

@ -132,7 +132,7 @@ namespace NzbDrone.Core.IndexerSearch
private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase)
{
var indexers = _indexerService.GetAvailableIndexers().ToList();
var indexers = _indexerService.GetAvailableProviders().ToList();
var reports = new List<ReleaseInfo>();
_logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase);

View File

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Eztv
{
public class Eztv : IndexerBase
public class Eztv : IndexerBase<NullSetting>
{
public override string Name
{

View File

@ -29,7 +29,7 @@ namespace NzbDrone.Core.Indexers
{
var result = new List<ReleaseInfo>();
var indexers = _indexerService.GetAvailableIndexers().ToList();
var indexers = _indexerService.GetAvailableProviders().ToList();
if (!indexers.Any())
{

View File

@ -1,23 +1,15 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public interface IIndexer
public interface IIndexer : IProvider
{
string Name { get; }
bool EnableByDefault { get; }
IEnumerable<IndexerDefinition> DefaultDefinitions { get; }
IndexerDefinition InstanceDefinition { get; set; }
IEnumerable<string> RecentFeed { get; }
IParseFeed Parser { get; }
IndexerKind Kind { get; }
IEnumerable<string> RecentFeed { get; }
IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date);
IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset);

View File

@ -4,30 +4,38 @@ using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public abstract class IndexerBase : IIndexer
public abstract class IndexerBase<TSettings> : IIndexer
{
public abstract string Name { get; }
public abstract IndexerKind Kind { get; }
public virtual bool EnableByDefault { get { return true; } }
public IndexerDefinition InstanceDefinition { get; set; }
public virtual IEnumerable<IndexerDefinition> DefaultDefinitions
public virtual IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
yield return new IndexerDefinition
{
Name = Name,
Enable = EnableByDefault,
Enable = false,
Implementation = GetType().Name,
Settings = NullSetting.Instance
};
}
}
public ProviderDefinition Definition { get; set; }
public abstract IndexerKind Kind { get; }
public virtual bool EnableByDefault { get { return true; } }
protected TSettings Settings
{
get
{
return (TSettings)Definition.Settings;
}
}
public virtual IParseFeed Parser { get; private set; }
public abstract IEnumerable<string> RecentFeed { get; }

View File

@ -1,11 +1,8 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public class IndexerDefinition : ProviderDefinition
{
public Boolean Enable { get; set; }
}
}

View File

@ -1,202 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
using Omu.ValueInjecter;
namespace NzbDrone.Core.Indexers
{
public class Indexer
public interface IIndexerService : IProviderFactory<IIndexer, IndexerDefinition>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enable { get; set; }
public IProviderConfig Settings { get; set; }
public IIndexer Instance { get; set; }
public string Implementation { get; set; }
}
public interface IIndexerService
public class IndexerService : ProviderFactory<IIndexer, IndexerDefinition>
{
List<Indexer> All();
List<IIndexer> GetAvailableIndexers();
Indexer Get(int id);
Indexer Get(string name);
List<Indexer> Schema();
Indexer Create(Indexer indexer);
Indexer Update(Indexer indexer);
void Delete(int id);
}
public class IndexerService : IIndexerService, IHandle<ApplicationStartedEvent>
{
private readonly IIndexerRepository _indexerRepository;
private readonly IConfigFileProvider _configFileProvider;
private readonly INewznabTestService _newznabTestService;
private readonly Logger _logger;
private readonly List<IIndexer> _indexers;
public IndexerService(IIndexerRepository indexerRepository,
IEnumerable<IIndexer> indexers,
IConfigFileProvider configFileProvider,
INewznabTestService newznabTestService,
Logger logger)
public IndexerService(IProviderRepository<IndexerDefinition> providerRepository, IEnumerable<IIndexer> providers, Logger logger)
: base(providerRepository, providers, logger)
{
_indexerRepository = indexerRepository;
_configFileProvider = configFileProvider;
_newznabTestService = newznabTestService;
_logger = logger;
if (!configFileProvider.Torrent)
{
_indexers = indexers.Where(c => c.Kind != IndexerKind.Torrent).ToList();
}
else
{
_indexers = indexers.ToList();
}
}
public List<Indexer> All()
{
return _indexerRepository.All().Select(ToIndexer).ToList();
}
public List<IIndexer> GetAvailableIndexers()
{
return All().Where(c => c.Enable && c.Settings.Validate().IsValid).Select(c => c.Instance).ToList();
}
public Indexer Get(int id)
{
return ToIndexer(_indexerRepository.Get(id));
}
public Indexer Get(string name)
{
return ToIndexer(_indexerRepository.Get(name));
}
public List<Indexer> Schema()
{
var indexers = new List<Indexer>();
var newznab = new Indexer();
newznab.Instance = new Newznab.Newznab();
newznab.Id = 1;
newznab.Name = "Newznab";
newznab.Settings = new NewznabSettings();
newznab.Implementation = "Newznab";
indexers.Add(newznab);
return indexers;
}
public Indexer Create(Indexer indexer)
{
var definition = new IndexerDefinition
{
Name = indexer.Name,
Enable = indexer.Enable,
Implementation = indexer.Implementation,
Settings = indexer.Settings
};
var instance = ToIndexer(definition).Instance;
_newznabTestService.Test(instance);
definition = _indexerRepository.Insert(definition);
indexer.Id = definition.Id;
return indexer;
}
public Indexer Update(Indexer indexer)
{
var definition = _indexerRepository.Get(indexer.Id);
definition.InjectFrom(indexer);
definition.Settings = indexer.Settings;
_indexerRepository.Update(definition);
return indexer;
}
public void Delete(int id)
{
_indexerRepository.Delete(id);
}
private Indexer ToIndexer(IndexerDefinition definition)
{
var indexer = new Indexer();
indexer.Id = definition.Id;
indexer.Enable = definition.Enable;
indexer.Instance = GetInstance(definition);
indexer.Name = definition.Name;
indexer.Implementation = definition.Implementation;
if (indexer.Instance.GetType().GetMethod("ImportSettingsFromJson") != null)
{
indexer.Settings = ((dynamic)indexer.Instance).ImportSettingsFromJson(definition.Settings);
}
else
{
indexer.Settings = NullSetting.Instance;
}
return indexer;
}
private IIndexer GetInstance(IndexerDefinition indexerDefinition)
{
var type = GetImplementation(indexerDefinition);
var instance = (IIndexer)Activator.CreateInstance(type);
instance.InstanceDefinition = indexerDefinition;
return instance;
}
private Type GetImplementation(IndexerDefinition indexerDefinition)
{
return _indexers.Select(c => c.GetType()).SingleOrDefault(c => c.Name.Equals(indexerDefinition.Implementation, StringComparison.InvariantCultureIgnoreCase));
}
public void Handle(ApplicationStartedEvent message)
{
_logger.Debug("Initializing indexers. Count {0}", _indexers.Count);
RemoveMissingImplementations();
var definitions = _indexers.SelectMany(indexer => indexer.DefaultDefinitions);
var currentIndexer = All();
var newIndexers = definitions.Where(def => currentIndexer.All(c => c.Implementation != def.Implementation)).ToList();
if (newIndexers.Any())
{
_indexerRepository.InsertMany(newIndexers);
}
}
private void RemoveMissingImplementations()
{
var storedIndexers = _indexerRepository.All();
foreach (var indexerDefinition in storedIndexers.Where(i => GetImplementation(i) == null))
{
_logger.Debug("Removing Indexer {0} ", indexerDefinition.Name);
_indexerRepository.Delete(indexerDefinition);
}
}
}
}

View File

@ -1,22 +0,0 @@
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public abstract class IndexerWithSetting<TSetting> : IndexerBase where TSetting : class, IProviderConfig, new()
{
public TSetting Settings { get; set; }
public override bool EnableByDefault
{
get { return false; }
}
public TSetting ImportSettingsFromJson(string json)
{
Settings = Json.Deserialize<TSetting>(json) ?? new TSetting();
return Settings;
}
}
}

View File

@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Newznab
{
public class Newznab : IndexerWithSetting<NewznabSettings>
public class Newznab : IndexerBase<NewznabSettings>
{
public override IParseFeed Parser
{
@ -15,7 +16,7 @@ namespace NzbDrone.Core.Indexers.Newznab
}
}
public override IEnumerable<IndexerDefinition> DefaultDefinitions
public override IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
@ -118,7 +119,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{
get
{
return InstanceDefinition.Name;
return Definition.Name;
}
}

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
public class Omgwtfnzbs : IndexerWithSetting<OmgwtfnzbsSettings>
public class Omgwtfnzbs : IndexerBase<OmgwtfnzbsSettings>
{
public override string Name
{

View File

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Wombles
{
public class Wombles : IndexerBase
public class Wombles : IndexerBase<NullSetting>
{
public override string Name
{

View File

@ -241,7 +241,6 @@
<Compile Include="Indexers\IIndexer.cs" />
<Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" />
<Compile Include="Indexers\NewznabTestService.cs" />
<Compile Include="Indexers\IndexerWithSetting.cs" />
<Compile Include="Indexers\IParseFeed.cs" />
<Compile Include="Indexers\Newznab\NewznabException.cs" />
<Compile Include="Indexers\Newznab\NewznabPreProcessor.cs" />
@ -413,13 +412,14 @@
<Compile Include="Parser\Parser.cs" />
<Compile Include="Parser\ParsingService.cs" />
<Compile Include="Parser\QualityParser.cs" />
<Compile Include="ThingiProvider\ProviderBase.cs" />
<Compile Include="ThingiProvider\IProvider.cs" />
<Compile Include="Qualities\QualityProfileInUseException.cs" />
<Compile Include="Qualities\QualitySizeRepository.cs" />
<Compile Include="Qualities\QualityProfileRepository.cs" />
<Compile Include="Rest\RestSharpExtensions.cs" />
<Compile Include="Rest\RestException.cs" />
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
<Compile Include="ThingiProvider\ProviderService.cs" />
<Compile Include="Tv\EpisodeService.cs" />
<Compile Include="Tv\Events\EpisodeInfoDeletedEvent.cs" />
<Compile Include="Tv\Events\EpisodeInfoUpdatedEvent.cs" />
@ -618,4 +618,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@ -1,38 +1,46 @@

using System;
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Notifications;
namespace NzbDrone.Core.ThingiProvider
{
public class NotificationProviderRepository : BasicRepository<NotificationDefinition>
public interface IProviderRepository<TProvider> : IBasicRepository<TProvider> where TProvider : ModelBase, new()
{
public NotificationProviderRepository(IDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
TProvider GetByName(string name);
}
public class IndexerProviderRepository : BasicRepository<IndexerDefinition>
public class ProviderRepository<TProviderDefinition> : BasicRepository<TProviderDefinition>, IProviderRepository<TProviderDefinition>
where TProviderDefinition : ModelBase,
new()
{
public IndexerProviderRepository(IDatabase database, IEventAggregator eventAggregator)
protected ProviderRepository(IDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public TProviderDefinition GetByName(string name)
{
throw new NotImplementedException();
}
}
public abstract class ProviderBase
public interface IProvider
{
public ProviderDefinition Definition { get; set; }
string Name { get; }
IEnumerable<ProviderDefinition> DefaultDefinitions { get; }
ProviderDefinition Definition { get; set; }
}
public abstract class ProviderDefinition : ModelBase
{
public string Name { get; set; }
public string Implementation { get; set; }
public bool Enable { get; set; }
public string ConfigContract
{

View File

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.ThingiProvider
{
public interface IProviderFactory<TProvider, TProviderDefinition>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
{
List<TProviderDefinition> All();
List<TProvider> GetAvailableProviders();
TProviderDefinition Get(int id);
//List<TProvider> Schema();
TProviderDefinition Create(TProviderDefinition indexer);
void Update(TProviderDefinition indexer);
void Delete(int id);
}
public abstract class ProviderFactory<TProvider, TProviderDefinition> : IProviderFactory<TProvider, TProviderDefinition>, IHandle<ApplicationStartedEvent>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
{
private readonly IProviderRepository<TProviderDefinition> _providerRepository;
private readonly Logger _logger;
private readonly List<TProvider> _providers;
protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository, IEnumerable<TProvider> providers, Logger logger)
{
_providerRepository = providerRepository;
_providers = providers.ToList();
_logger = logger;
}
public List<TProviderDefinition> All()
{
return _providerRepository.All().ToList();
}
public List<TProvider> GetAvailableProviders()
{
return All().Where(c => c.Enable && c.Settings.Validate().IsValid)
.Select(GetInstance).ToList();
}
public TProviderDefinition Get(int id)
{
return _providerRepository.Get(id);
}
/* public List<TProvider> Schema()
{
var indexers = new List<Indexer>();
var newznab = new Indexer();
newznab.Instance = new Newznab.Newznab();
newznab.Id = 1;
newznab.Name = "Newznab";
newznab.Settings = new NewznabSettings();
newznab.Implementation = "Newznab";
indexers.Add(newznab);
return indexers;
}*/
public TProviderDefinition Create(TProviderDefinition provider)
{
return _providerRepository.Insert(provider);
}
public void Update(TProviderDefinition definition)
{
_providerRepository.Update(definition);
}
public void Delete(int id)
{
_providerRepository.Delete(id);
}
private TProvider GetInstance(TProviderDefinition definition)
{
var type = GetImplementation(definition);
var instance = (TProvider)Activator.CreateInstance(type);
instance.Definition = definition;
return instance;
}
private Type GetImplementation(TProviderDefinition definition)
{
return _providers.Select(c => c.GetType()).SingleOrDefault(c => c.Name.Equals(definition.Implementation, StringComparison.InvariantCultureIgnoreCase));
}
public void Handle(ApplicationStartedEvent message)
{
_logger.Debug("Initializing Providers. Count {0}", _providers.Count);
RemoveMissingImplementations();
var definitions = _providers.SelectMany(indexer => indexer.DefaultDefinitions);
var currentProviders = All();
var newProviders = definitions.Where(def => currentProviders.All(c => c.Implementation != def.Implementation)).ToList();
if (newProviders.Any())
{
_providerRepository.InsertMany(newProviders.Cast<TProviderDefinition>().ToList());
}
}
private void RemoveMissingImplementations()
{
var storedProvider = _providerRepository.All();
foreach (var providerDefinition in storedProvider.Where(i => GetImplementation(i) == null))
{
_logger.Debug("Removing {0} ", providerDefinition.Name);
_providerRepository.Delete(providerDefinition);
}
}
}
}

View File

@ -7,7 +7,7 @@
<FrameworkUtilisationTypeForGallio>Disabled</FrameworkUtilisationTypeForGallio>
<FrameworkUtilisationTypeForMSpec>Disabled</FrameworkUtilisationTypeForMSpec>
<FrameworkUtilisationTypeForMSTest>Disabled</FrameworkUtilisationTypeForMSTest>
<EngineModes>Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk;Fast:DlN0cnVjdHVyYWxOb2RlBAAAABNEb2VzTm90SGF2ZUNhdGVnb3J5D0ludGVncmF0aW9uVGVzdBNEb2VzTm90SGF2ZUNhdGVnb3J5BkRiVGVzdApJc0ltcGFjdGVkE0RvZXNOb3RIYXZlQ2F0ZWdvcnkORGlza0FjY2Vzc1Rlc3QAAAAAAAAAAAAAAAA=</EngineModes>
<EngineModes>Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk;Fast:DlN0cnVjdHVyYWxOb2RlBQAAABNEb2VzTm90SGF2ZUNhdGVnb3J5D0ludGVncmF0aW9uVGVzdBNEb2VzTm90SGF2ZUNhdGVnb3J5BkRiVGVzdApJc0ltcGFjdGVkE0RvZXNOb3RIYXZlQ2F0ZWdvcnkORGlza0FjY2Vzc1Rlc3QISXNQaW5uZWQAAAAAAAAAAAAAAAABAAAA</EngineModes>
<MetricsExclusionList>
</MetricsExclusionList>
</SolutionConfiguration>