Merge branch 'develop'

This commit is contained in:
Mark McDowall 2014-07-23 16:44:48 -07:00
commit 87497e3b53
346 changed files with 5979 additions and 1942 deletions

1
.gitignore vendored
View File

@ -84,7 +84,6 @@ Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
# SQL Server files

View File

@ -37,6 +37,7 @@ module.exports = function (grunt) {
'Content/theme.less',
'Content/overrides.less',
'Series/series.less',
'History/history.less',
'AddSeries/addSeries.less',
'Calendar/calendar.less',
'Cells/cells.less',

View File

@ -2,6 +2,4 @@
using System.Reflection;
[assembly: AssemblyVersion("1.1.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.3")]
[assembly: AssemblyVersion("10.0.0.*")]

View File

@ -22,18 +22,8 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a463887e-594f-4733-b227-a79f4ffb2158")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("10.0.0.*")]
[assembly: AssemblyFileVersion("10.0.0.*")]
[assembly: InternalsVisibleTo("Exceptron.Client.Tests")]
[assembly: InternalsVisibleTo("Exceptron.Api.v1.Tests")]
[assembly: InternalsVisibleTo("Exceptron.Rush")]

View File

@ -51,7 +51,7 @@ namespace NzbDrone.Api.Test.MappingTests
}
[Test]
public void should_map_lay_loaded_values_should_not_be_inject_if_not_loaded()
public void should_map_lazy_loaded_values_should_not_be_inject_if_not_loaded()
{
var modelWithLazy = new ModelWithLazy()
{

View File

@ -21,15 +21,4 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("260b2ff9-d3b7-4d8a-b720-a12c93d045e5")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("10.0.0.*")]
[assembly: AssemblyFileVersion("10.0.0.*")]

View File

@ -12,7 +12,7 @@ namespace NzbDrone.Api.Blacklist
{
_blacklistService = blacklistService;
GetResourcePaged = GetBlacklist;
DeleteResource = Delete;
DeleteResource = DeleteBlacklist;
}
private PagingResource<BlacklistResource> GetBlacklist(PagingResource<BlacklistResource> pagingResource)
@ -25,16 +25,10 @@ namespace NzbDrone.Api.Blacklist
SortDirection = pagingResource.SortDirection
};
//This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue
if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase))
{
pagingSpec.SortKey = "series.title";
}
return ApplyToPage(_blacklistService.Paged, pagingSpec);
}
private void Delete(int id)
private void DeleteBlacklist(int id)
{
_blacklistService.Delete(id);
}

View File

@ -1,16 +1,18 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace NzbDrone.Api.ClientSchema
{
public class Field
{
public int Order { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public string HelpText { get; set; }
public string HelpLink { get; set; }
public object Value { get; set; }
public string Type { get; set; }
public Int32 Order { get; set; }
public String Name { get; set; }
public String Label { get; set; }
public String HelpText { get; set; }
public String HelpLink { get; set; }
public Object Value { get; set; }
public String Type { get; set; }
public Boolean Advanced { get; set; }
public List<SelectOption> SelectOptions { get; set; }
}
}

View File

@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NzbDrone.Common;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Reflection;
@ -26,13 +28,14 @@ namespace NzbDrone.Api.ClientSchema
if (fieldAttribute != null)
{
var field = new Field()
var field = new Field
{
Name = propertyInfo.Name,
Label = fieldAttribute.Label,
HelpText = fieldAttribute.HelpText,
HelpLink = fieldAttribute.HelpLink,
Order = fieldAttribute.Order,
Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().ToLowerInvariant()
};
@ -101,6 +104,23 @@ namespace NzbDrone.Api.ClientSchema
propertyInfo.SetValue(target, value, null);
}
else if (propertyInfo.PropertyType == typeof (IEnumerable<Int32>))
{
IEnumerable<Int32> value;
if (field.Value.GetType() == typeof (JArray))
{
value = ((JArray) field.Value).Select(s => s.Value<Int32>());
}
else
{
value = field.Value.ToString().Split(new []{','}, StringSplitOptions.RemoveEmptyEntries).Select(s => Convert.ToInt32(s));
}
propertyInfo.SetValue(target, value, null);
}
else
{
propertyInfo.SetValue(target, field.Value, null);

View File

@ -39,6 +39,7 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 3);
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
}
@ -80,6 +81,7 @@ namespace NzbDrone.Api.Config
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
sampleResource.SingleEpisodeExample = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult) != null
? "Invalid format"
@ -93,6 +95,10 @@ namespace NzbDrone.Api.Config
? "Invalid format"
: dailyEpisodeSampleResult.Filename;
sampleResource.AnimeEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult) != null
? "Invalid format"
: animeEpisodeSampleResult.Filename;
sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"
: _filenameSampleService.GetSeriesFolderSample(nameSpec);

View File

@ -9,6 +9,7 @@ namespace NzbDrone.Api.Config
public Int32 MultiEpisodeStyle { get; set; }
public string StandardEpisodeFormat { get; set; }
public string DailyEpisodeFormat { get; set; }
public string AnimeEpisodeFormat { get; set; }
public string SeriesFolderFormat { get; set; }
public string SeasonFolderFormat { get; set; }
public bool IncludeSeriesTitle { get; set; }

View File

@ -5,6 +5,7 @@
public string SingleEpisodeExample { get; set; }
public string MultiEpisodeExample { get; set; }
public string DailyEpisodeExample { get; set; }
public string AnimeEpisodeExample { get; set; }
public string SeriesFolderExample { get; set; }
public string SeasonFolderExample { get; set; }
}

View File

@ -16,7 +16,6 @@ namespace NzbDrone.Api.Episodes
GetResourceAll = GetEpisodes;
UpdateResource = SetMonitored;
GetResourceById = GetEpisode;
}
private List<EpisodeResource> GetEpisodes()

View File

@ -26,11 +26,15 @@ namespace NzbDrone.Api.Episodes
: base(commandExecutor, resource)
{
_episodeService = episodeService;
GetResourceById = GetEpisode;
}
protected EpisodeResource GetEpisode(int id)
{
return _episodeService.GetEpisode(id).InjectTo<EpisodeResource>();
var episode = _episodeService.GetEpisode(id);
episode.EpisodeFile.LazyLoad();
return episode.InjectTo<EpisodeResource>();
}
public void Handle(EpisodeGrabbedEvent message)

View File

@ -19,6 +19,7 @@ namespace NzbDrone.Api.Episodes
public Boolean HasFile { get; set; }
public Boolean Monitored { get; set; }
public Nullable<Int32> SceneAbsoluteEpisodeNumber { get; set; }
public Int32 SceneEpisodeNumber { get; set; }
public Int32 SceneSeasonNumber { get; set; }
public Int32 TvDbEpisodeId { get; set; }

View File

@ -1,11 +1,18 @@
using Nancy;
using Nancy.Bootstrapper;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Api.Frontend;
namespace NzbDrone.Api.Extensions.Pipelines
{
public class NzbDroneVersionPipeline : IRegisterNancyPipeline
public class CacheHeaderPipeline : IRegisterNancyPipeline
{
private readonly ICacheableSpecification _cacheableSpecification;
public CacheHeaderPipeline(ICacheableSpecification cacheableSpecification)
{
_cacheableSpecification = cacheableSpecification;
}
public void Register(IPipelines pipelines)
{
pipelines.AfterRequest.AddItemToStartOfPipeline(Handle);
@ -13,9 +20,13 @@ namespace NzbDrone.Api.Extensions.Pipelines
private void Handle(NancyContext context)
{
if (!context.Response.Headers.ContainsKey("X-ApplicationVersion"))
if (_cacheableSpecification.IsCacheable(context))
{
context.Response.Headers.Add("X-ApplicationVersion", BuildInfo.Version.ToString());
context.Response.Headers.EnableCache();
}
else
{
context.Response.Headers.DisableCache();
}
}
}

View File

@ -1,18 +1,11 @@
using Nancy;
using Nancy.Bootstrapper;
using NzbDrone.Api.Frontend;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Api.Extensions.Pipelines
{
public class CacheHeaderPipeline : IRegisterNancyPipeline
public class NzbDroneVersionPipeline : IRegisterNancyPipeline
{
private readonly ICacheableSpecification _cacheableSpecification;
public CacheHeaderPipeline(ICacheableSpecification cacheableSpecification)
{
_cacheableSpecification = cacheableSpecification;
}
public void Register(IPipelines pipelines)
{
pipelines.AfterRequest.AddItemToStartOfPipeline(Handle);
@ -20,13 +13,9 @@ namespace NzbDrone.Api.Extensions.Pipelines
private void Handle(NancyContext context)
{
if (_cacheableSpecification.IsCacheable(context))
if (!context.Response.Headers.ContainsKey("X-ApplicationVersion"))
{
context.Response.Headers.EnableCache();
}
else
{
context.Response.Headers.DisableCache();
context.Response.Headers.Add("X-ApplicationVersion", BuildInfo.Version.ToString());
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using Nancy;
using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Api.Frontend
@ -20,7 +21,13 @@ namespace NzbDrone.Api.Frontend
if (context.Request.Query.v == BuildInfo.Version) return true;
if (context.Request.Path.StartsWith("/api", StringComparison.CurrentCultureIgnoreCase)) return false;
if (context.Request.Path.StartsWith("/api", StringComparison.CurrentCultureIgnoreCase))
{
if (context.Request.Path.ContainsIgnoreCase("/MediaCover")) return true;
return false;
}
if (context.Request.Path.StartsWith("/signalr", StringComparison.CurrentCultureIgnoreCase)) return false;
if (context.Request.Path.EndsWith("main.js")) return false;
if (context.Request.Path.StartsWith("/feed", StringComparison.CurrentCultureIgnoreCase)) return false;

View File

@ -0,0 +1,31 @@
using System.IO;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Api.Frontend.Mappers
{
public class BackupFileMapper : StaticResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
public BackupFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
: base(diskProvider, logger)
{
_appFolderInfo = appFolderInfo;
}
protected override string Map(string resourceUrl)
{
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
return Path.Combine(_appFolderInfo.GetBackupFolder(), path);
}
public override bool CanHandle(string resourceUrl)
{
return resourceUrl.StartsWith("/backup/") && resourceUrl.ContainsIgnoreCase("nzbdrone_backup_") && resourceUrl.EndsWith(".zip");
}
}
}

View File

@ -10,12 +10,12 @@ namespace NzbDrone.Api.History
public class HistoryModule : NzbDroneRestModule<HistoryResource>
{
private readonly IHistoryService _historyService;
private readonly IFailedDownloadService _failedDownloadService;
private readonly IDownloadTrackingService _downloadTrackingService;
public HistoryModule(IHistoryService historyService, IFailedDownloadService failedDownloadService)
public HistoryModule(IHistoryService historyService, IDownloadTrackingService downloadTrackingService)
{
_historyService = historyService;
_failedDownloadService = failedDownloadService;
_downloadTrackingService = downloadTrackingService;
GetResourcePaged = GetHistory;
Post["/failed"] = x => MarkAsFailed();
@ -33,12 +33,6 @@ namespace NzbDrone.Api.History
SortDirection = pagingResource.SortDirection
};
//This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue
if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase))
{
pagingSpec.SortKey = "series.title";
}
if (pagingResource.FilterKey == "eventType")
{
var filterValue = (HistoryEventType)Convert.ToInt32(pagingResource.FilterValue);
@ -57,7 +51,7 @@ namespace NzbDrone.Api.History
private Response MarkAsFailed()
{
var id = (int)Request.Form.Id;
_failedDownloadService.MarkAsFailed(id);
_downloadTrackingService.MarkAsFailed(id);
return new Object().AsResponse();
}
}

View File

@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using FluentValidation;
using Nancy;
using NLog;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
@ -13,6 +15,7 @@ using Omu.ValueInjecter;
using System.Linq;
using Nancy.ModelBinding;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Indexers
{
@ -21,20 +24,26 @@ namespace NzbDrone.Api.Indexers
private readonly IFetchAndParseRss _rssFetcherAndParser;
private readonly ISearchForNzb _nzbSearchService;
private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision;
private readonly IDownloadService _downloadService;
private readonly IParsingService _parsingService;
private readonly Logger _logger;
public ReleaseModule(IFetchAndParseRss rssFetcherAndParser,
ISearchForNzb nzbSearchService,
IMakeDownloadDecision downloadDecisionMaker,
IPrioritizeDownloadDecision prioritizeDownloadDecision,
IDownloadService downloadService,
IParsingService parsingService)
IParsingService parsingService,
Logger logger)
{
_rssFetcherAndParser = rssFetcherAndParser;
_nzbSearchService = nzbSearchService;
_downloadDecisionMaker = downloadDecisionMaker;
_prioritizeDownloadDecision = prioritizeDownloadDecision;
_downloadService = downloadService;
_parsingService = parsingService;
_logger = logger;
GetResourceAll = GetReleases;
Post["/"] = x=> DownloadRelease(this.Bind<ReleaseResource>());
@ -43,7 +52,7 @@ namespace NzbDrone.Api.Indexers
private Response DownloadRelease(ReleaseResource release)
{
var remoteEpisode = _parsingService.Map(release.InjectTo<ParsedEpisodeInfo>(), 0);
var remoteEpisode = _parsingService.Map(release.InjectTo<ParsedEpisodeInfo>(), release.TvRageId);
remoteEpisode.Release = release.InjectTo<ReleaseInfo>();
_downloadService.DownloadReport(remoteEpisode);
@ -62,17 +71,28 @@ namespace NzbDrone.Api.Indexers
private List<ReleaseResource> GetEpisodeReleases(int episodeId)
{
var decisions = _nzbSearchService.EpisodeSearch(episodeId);
try
{
var decisions = _nzbSearchService.EpisodeSearch(episodeId);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(decisions);
return MapDecisions(prioritizedDecisions);
}
catch (Exception ex)
{
_logger.ErrorException("Episode search failed: " + ex.Message, ex);
}
return new List<ReleaseResource>();
}
private List<ReleaseResource> GetRss()
{
var reports = _rssFetcherAndParser.Fetch();
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(decisions);
return MapDecisions(prioritizedDecisions);
}
private static List<ReleaseResource> MapDecisions(IEnumerable<DownloadDecision> decisions)
@ -89,6 +109,18 @@ namespace NzbDrone.Api.Indexers
release.Rejections = downloadDecision.Rejections.ToList();
release.DownloadAllowed = downloadDecision.RemoteEpisode.DownloadAllowed;
release.ReleaseWeight = result.Count;
if (downloadDecision.RemoteEpisode.Series != null)
{
release.QualityWeight = downloadDecision.RemoteEpisode.Series.QualityProfile.Value.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 2;
}
if (!release.Quality.Proper)
{
release.QualityWeight++;
}
result.Add(release);
}

View File

@ -10,11 +10,14 @@ namespace NzbDrone.Api.Indexers
public class ReleaseResource : RestResource
{
public QualityModel Quality { get; set; }
public Int32 QualityWeight { get; set; }
public Int32 Age { get; set; }
public Double AgeHours { get; set; }
public Int64 Size { get; set; }
public String Indexer { get; set; }
public String ReleaseGroup { get; set; }
public String SubGroup { get; set; }
public String ReleaseHash { get; set; }
public String Title { get; set; }
public Boolean FullSeason { get; set; }
public Boolean SceneSource { get; set; }
@ -23,14 +26,21 @@ namespace NzbDrone.Api.Indexers
public String AirDate { get; set; }
public String SeriesTitle { get; set; }
public int[] EpisodeNumbers { get; set; }
public int[] AbsoluteEpisodeNumbers { get; set; }
public Boolean Approved { get; set; }
public Int32 TvRageId { get; set; }
public List<string> Rejections { get; set; }
public IEnumerable<String> Rejections { get; set; }
public DateTime PublishDate { get; set; }
public String CommentUrl { get; set; }
public String DownloadUrl { get; set; }
public String InfoUrl { get; set; }
public Boolean DownloadAllowed { get; set; }
public DownloadProtocol DownloadProtocol { get; set; }
public Int32 ReleaseWeight { get; set; }
public Boolean IsDaily { get; set; }
public Boolean IsAbsoluteNumbering { get; set; }
public Boolean IsPossibleSpecialEpisode { get; set; }
public Boolean Special { get; set; }
}
}

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.IO;
using Nancy;
using Nancy.Responses;
using NzbDrone.Common;

View File

@ -122,15 +122,16 @@
<Compile Include="Episodes\EpisodeResource.cs" />
<Compile Include="Episodes\RenameEpisodeModule.cs" />
<Compile Include="Episodes\RenameEpisodeResource.cs" />
<Compile Include="Extensions\Pipelines\CacheHeaderPipeline.cs" />
<Compile Include="Extensions\Pipelines\NzbDroneVersionPipeline.cs" />
<Compile Include="Extensions\Pipelines\CacheHeaderPipeline.cs" />
<Compile Include="Extensions\Pipelines\GZipPipeline.cs" />
<Compile Include="Extensions\Pipelines\IfModifiedPipeline.cs" />
<Compile Include="Extensions\Pipelines\IRegisterNancyPipeline.cs" />
<Compile Include="Extensions\NancyJsonSerializer.cs" />
<Compile Include="Extensions\RequestExtensions.cs" />
<Compile Include="Frontend\IsCacheableSpecification.cs" />
<Compile Include="Frontend\CacheableSpecification.cs" />
<Compile Include="Frontend\Mappers\UpdateLogFileMapper.cs" />
<Compile Include="Frontend\Mappers\BackupFileMapper.cs" />
<Compile Include="Frontend\Mappers\FaviconMapper.cs" />
<Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" />
<Compile Include="Frontend\Mappers\LogFileMapper.cs" />
@ -164,6 +165,10 @@
<Compile Include="Mapping\MappingValidation.cs" />
<Compile Include="Mapping\ResourceMappingException.cs" />
<Compile Include="Mapping\ValueInjectorExtensions.cs" />
<Compile Include="Series\AlternateTitleResource.cs" />
<Compile Include="Series\SeasonResource.cs" />
<Compile Include="System\Backup\BackupModule.cs" />
<Compile Include="System\Backup\BackupResource.cs" />
<Compile Include="Update\UpdateResource.cs" />
<Compile Include="Wanted\CutoffModule.cs" />
<Compile Include="Wanted\LegacyMissingModule.cs" />

View File

@ -7,6 +7,5 @@ using System.Runtime.InteropServices;
[assembly: Guid("4c0922d7-979e-4ff7-b44b-b8ac2100eeb5")]
[assembly: AssemblyVersion("10.0.0.*")]
[assembly: AssemblyFileVersion("10.0.0.*")]
[assembly: InternalsVisibleTo("NzbDrone.Core")]

View File

@ -23,7 +23,10 @@ namespace NzbDrone.Api
: base(resource)
{
_providerFactory = providerFactory;
Get["schema"] = x => GetTemplates();
Post["test"] = x => Test(ReadResourceFromRequest());
GetResourceAll = GetAll;
GetResourceById = GetProviderById;
CreateResource = CreateProvider;
@ -63,16 +66,26 @@ namespace NzbDrone.Api
private int CreateProvider(TProviderResource providerResource)
{
var provider = GetDefinition(providerResource);
provider = _providerFactory.Create(provider);
return provider.Id;
var providerDefinition = GetDefinition(providerResource);
if (providerDefinition.Enable)
{
Test(providerDefinition);
}
providerDefinition = _providerFactory.Create(providerDefinition);
return providerDefinition.Id;
}
private void UpdateProvider(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource);
Validate(providerDefinition);
if (providerDefinition.Enable)
{
Test(providerDefinition);
}
_providerFactory.Update(providerDefinition);
}
@ -133,6 +146,25 @@ namespace NzbDrone.Api
return result.AsResponse();
}
private Response Test(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource);
Test(providerDefinition);
return "{}";
}
private void Test(TProviderDefinition providerDefinition)
{
var result = _providerFactory.Test(providerDefinition);
if (!result.IsValid)
{
throw new ValidationException(result.Errors);
}
}
protected virtual void Validate(TProviderDefinition definition)
{
var validationResult = definition.Settings.Validate();
@ -143,4 +175,4 @@ namespace NzbDrone.Api
}
}
}
}
}

View File

@ -17,5 +17,6 @@ namespace NzbDrone.Api.Queue
public Decimal Sizeleft { get; set; }
public TimeSpan? Timeleft { get; set; }
public String Status { get; set; }
public String ErrorMessage { get; set; }
}
}

View File

@ -194,7 +194,7 @@ namespace NzbDrone.Api.REST
var errors = SharedValidator.Validate(resource).Errors.ToList();
if (Request.Method.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
if (Request.Method.Equals("POST", StringComparison.InvariantCultureIgnoreCase) && !Request.Url.Path.EndsWith("/test", StringComparison.InvariantCultureIgnoreCase))
{
errors.AddRange(PostValidator.Validate(resource).Errors);
}

View File

@ -0,0 +1,10 @@
using System;
namespace NzbDrone.Api.Series
{
public class AlternateTitleResource
{
public String Title { get; set; }
public Int32 SeasonNumber { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Api.Series
{
public class SeasonResource
{
public int SeasonNumber { get; set; }
public Boolean Monitored { get; set; }
}
}

View File

@ -15,6 +15,7 @@ using NzbDrone.Api.Mapping;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.Core.DataAugmentation.Scene;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Series
{
@ -78,21 +79,6 @@ namespace NzbDrone.Api.Series
PutValidator.RuleFor(s => s.Path).IsValidPath();
}
private void PopulateAlternativeTitles(List<SeriesResource> resources)
{
foreach (var resource in resources)
{
PopulateAlternativeTitles(resource);
}
}
private void PopulateAlternativeTitles(SeriesResource resource)
{
var mapping = _sceneMappingService.FindByTvdbid(resource.TvdbId);
if (mapping == null) return;
resource.AlternativeTitles = mapping.Select(x => x.Title).Distinct().ToList();
}
private SeriesResource GetSeries(int id)
{
var series = _seriesService.GetSeries(id);
@ -106,7 +92,7 @@ namespace NzbDrone.Api.Series
var resource = series.InjectTo<SeriesResource>();
MapCoversToLocal(resource);
FetchAndLinkSeriesStatistics(resource);
PopulateAlternativeTitles(resource);
PopulateAlternateTitles(resource);
return resource;
}
@ -118,7 +104,7 @@ namespace NzbDrone.Api.Series
MapCoversToLocal(seriesResources.ToArray());
LinkSeriesStatistics(seriesResources, seriesStats);
PopulateAlternativeTitles(seriesResources);
PopulateAlternateTitles(seriesResources);
return seriesResources;
}
@ -177,6 +163,24 @@ namespace NzbDrone.Api.Series
resource.EpisodeCount = seriesStatistics.EpisodeCount;
resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount;
resource.NextAiring = seriesStatistics.NextAiring;
resource.PreviousAiring = seriesStatistics.PreviousAiring;
}
private void PopulateAlternateTitles(List<SeriesResource> resources)
{
foreach (var resource in resources)
{
PopulateAlternateTitles(resource);
}
}
private void PopulateAlternateTitles(SeriesResource resource)
{
var mappings = _sceneMappingService.FindByTvdbid(resource.TvdbId);
if (mappings == null) return;
resource.AlternateTitles = mappings.InjectTo<List<AlternateTitleResource>>();
}
public void Handle(EpisodeImportedEvent message)

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv;
@ -15,7 +16,8 @@ namespace NzbDrone.Api.Series
//View Only
public String Title { get; set; }
public List<String> AlternativeTitles { get; set; }
public List<AlternateTitleResource> AlternateTitles { get; set; }
public String SortTitle { get; set; }
public Int32 SeasonCount
{
@ -33,12 +35,13 @@ namespace NzbDrone.Api.Series
public String QualityProfileName { get; set; }
public String Overview { get; set; }
public DateTime? NextAiring { get; set; }
public DateTime? PreviousAiring { get; set; }
public String Network { get; set; }
public String AirTime { get; set; }
public List<MediaCover> Images { get; set; }
public String RemotePoster { get; set; }
public List<Season> Seasons { get; set; }
public List<SeasonResource> Seasons { get; set; }
public Int32 Year { get; set; }
//View & Edit

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NzbDrone.Core.Backup;
namespace NzbDrone.Api.System.Backup
{
public class BackupModule : NzbDroneRestModule<BackupResource>
{
private readonly IBackupService _backupService;
public BackupModule(IBackupService backupService) : base("system/backup")
{
_backupService = backupService;
GetResourceAll = GetBackupFiles;
}
public List<BackupResource> GetBackupFiles()
{
var backups = _backupService.GetBackups();
return backups.Select(b => new BackupResource
{
Id = b.Path.GetHashCode(),
Name = Path.GetFileName(b.Path),
Path = b.Path,
Type = b.Type,
Time = b.Time
}).ToList();
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using NzbDrone.Api.REST;
using NzbDrone.Core.Backup;
namespace NzbDrone.Api.System.Backup
{
public class BackupResource : RestResource
{
public String Name { get; set; }
public String Path { get; set; }
public BackupType Type { get; set; }
public DateTime Time { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.Datastore;

View File

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.Datastore;

View File

@ -21,15 +21,4 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b47d34ef-05e8-4826-8a57-9dd05106c964")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("10.0.0.*")]
[assembly: AssemblyFileVersion("10.0.0.*")]

View File

@ -21,15 +21,4 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("6b8945f5-f5b5-4729-865d-f958fbd673d9")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("10.0.0.*")]

View File

@ -172,7 +172,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
public void empty_folder_should_return_folder_modified_date()
{
var tempfolder = new DirectoryInfo(TempFolder);
Subject.FolderGetLastWrite(TempFolder).Should().Be(tempfolder.LastWriteTimeUtc);
Subject.FolderGetLastWriteUtc(TempFolder).Should().Be(tempfolder.LastWriteTimeUtc);
}
[Test]
@ -189,8 +189,8 @@ namespace NzbDrone.Common.Test.DiskProviderTests
Subject.WriteAllText(testFile, "Test");
Subject.FolderGetLastWrite(TempFolder).Should().BeOnOrAfter(DateTime.UtcNow.AddMinutes(-1));
Subject.FolderGetLastWrite(TempFolder).Should().BeBefore(DateTime.UtcNow.AddMinutes(1));
Subject.FolderGetLastWriteUtc(TempFolder).Should().BeOnOrAfter(DateTime.UtcNow.AddMinutes(-1));
Subject.FolderGetLastWriteUtc(TempFolder).Should().BeBefore(DateTime.UtcNow.AddMinutes(1));
}
[Test]
@ -238,7 +238,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
[Explicit]
public void check_last_write()
{
Console.WriteLine(Subject.FolderGetLastWrite(GetFilledTempFolder().FullName));
Console.WriteLine(Subject.FolderGetLastWriteUtc(GetFilledTempFolder().FullName));
Console.WriteLine(GetFilledTempFolder().LastWriteTimeUtc);
}

View File

@ -26,14 +26,6 @@ namespace NzbDrone.Common.Test.DiskProviderTests
Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0);
}
[Test]
public void should_get_free_space_for_drive_that_doesnt_exist()
{
WindowsOnly();
Assert.Throws<DirectoryNotFoundException>(() => Subject.GetAvailableSpace("J:\\").Should().NotBe(0));
}
[Test]
public void should_be_able_to_check_space_on_ramdrive()
{

View File

@ -0,0 +1,34 @@
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using NUnit.Framework;
using NzbDrone.Common.Instrumentation;
using FluentAssertions;
namespace NzbDrone.Common.Test.InstrumentationTests
{
[TestFixture]
public class CleanseLogMessageFixture
{
[TestCase(@"http://127.0.0.1:1234/api/call?vv=1&apikey=mySecret")]
[TestCase(@"http://127.0.0.1:1234/api/call?vv=1&ma_username=mySecret&ma_password=mySecret")]
// NzbGet
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]
[TestCase(@"{ ""Name"" : ""Server1.Username"", ""Value"" : ""mySecret"" }, { ""Name"" : ""Server1.Password"", ""Value"" : ""mySecret"" }, ")]
// Sabnzbd
[TestCase(@"""config"":{""newzbin"":{""username"":""mySecret"",""password"":""mySecret""}")]
[TestCase(@"""nzbxxx"":{""username"":""mySecret"",""apikey"":""mySecret""}")]
[TestCase(@"""growl"":{""growl_password"":""mySecret"",""growl_server"":""""}")]
[TestCase(@"""nzbmatrix"":{""username"":""mySecret"",""apikey"":""mySecret""}")]
[TestCase(@"""misc"":{""username"":""mySecret"",""api_key"":""mySecret"",""password"":""mySecret"",""nzb_key"":""mySecret""}")]
[TestCase(@"""servers"":[{""username"":""mySecret"",""password"":""mySecret""}]")]
[TestCase(@"""misc"":{""email_account"":""mySecret"",""email_to"":[],""email_from"":"""",""email_pwd"":""mySecret""}")]
public void should_clean_message(String message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
}
}
}

View File

@ -67,6 +67,7 @@
<Compile Include="EnsureTest\PathExtensionFixture.cs" />
<Compile Include="EnvironmentTests\StartupArgumentsFixture.cs" />
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
<Compile Include="InstrumentationTests\CleanseLogMessageFixture.cs" />
<Compile Include="LevenshteinDistanceFixture.cs" />
<Compile Include="ReflectionExtensions.cs" />
<Compile Include="PathExtensionFixture.cs" />

View File

@ -136,6 +136,23 @@ namespace NzbDrone.Common.Test
parentPath.IsParentPath(childPath).Should().Be(expectedResult);
}
[TestCase(@"C:\Test\mydir", @"C:\Test")]
[TestCase(@"C:\Test\", @"C:")]
[TestCase(@"C:\", null)]
public void path_should_return_parent(string path, string parentPath)
{
path.GetParentPath().Should().Be(parentPath);
}
[Test]
public void path_should_return_parent_for_oversized_path()
{
var path = @"/media/2e168617-f2ae-43fb-b88c-3663af1c8eea/downloads/sabnzbd/nzbdrone/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories";
var parentPath = @"/media/2e168617-f2ae-43fb-b88c-3663af1c8eea/downloads/sabnzbd/nzbdrone/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing";
path.GetParentPath().Should().Be(parentPath);
}
[Test]
[Ignore]
public void should_not_be_parent_when_it_is_grandparent()

View File

@ -12,6 +12,8 @@ namespace NzbDrone.Common
public interface IArchiveService
{
void Extract(string compressedFile, string destination);
void ExtractZip(string compressedFile, string destination);
void CreateZip(string path, params string[] files);
}
public class ArchiveService : IArchiveService
@ -40,7 +42,22 @@ namespace NzbDrone.Common
_logger.Debug("Extraction complete.");
}
private void ExtractZip(string compressedFile, string destination)
public void CreateZip(string path, params string[] files)
{
using (var zipFile = ZipFile.Create(path))
{
zipFile.BeginUpdate();
foreach (var file in files)
{
zipFile.Add(file, Path.GetFileName(file));
}
zipFile.CommitUpdate();
}
}
public void ExtractZip(string compressedFile, string destination)
{
using (var fileStream = File.OpenRead(compressedFile))
{

View File

@ -33,7 +33,7 @@ namespace NzbDrone.Common.Disk
return new DirectoryInfo(path).CreationTimeUtc;
}
public DateTime FolderGetLastWrite(string path)
public DateTime FolderGetLastWriteUtc(string path)
{
CheckFolderExists(path);

View File

@ -12,7 +12,7 @@ namespace NzbDrone.Common.Disk
void SetPermissions(string path, string mask, string user, string group);
long? GetTotalSize(string path);
DateTime FolderGetCreationTimeUtc(string path);
DateTime FolderGetLastWrite(string path);
DateTime FolderGetLastWriteUtc(string path);
DateTime FileGetCreationTimeUtc(string path);
DateTime FileGetLastWrite(string path);
DateTime FileGetLastWriteUtc(string path);

View File

@ -22,5 +22,10 @@ namespace NzbDrone.Common
source.Add(item);
}
public static bool Empty<TSource>(this IEnumerable<TSource> source)
{
return !source.Any();
}
}
}

View File

@ -4,8 +4,21 @@ namespace NzbDrone.Common.Instrumentation
{
public class CleanseLogMessage
{
//TODO: remove password=
private static readonly Regex CleansingRegex = new Regex(@"(?<=apikey=)(\w+?)(?=\W|$|_)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex[] CleansingRules = new[]
{
// Url
new Regex(@"(<=\?|&)apikey=(?<secret>\w+?)(?=\W|$|_)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(<=\?|&)[^=]*?(username|password)=(?<secret>\w+?)(?=\W|$|_)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// NzbGet
new Regex(@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Sabnzbd
new Regex(@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
//private static readonly Regex CleansingRegex = new Regex(@"(?<=apikey=)(\w+?)(?=\W|$|_)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static string Cleanse(string message)
{
@ -14,7 +27,12 @@ namespace NzbDrone.Common.Instrumentation
return message;
}
return CleansingRegex.Replace(message, "<removed>");
foreach (var regex in CleansingRules)
{
message = regex.Replace(message, m => m.Value.Replace(m.Groups["secret"].Index - m.Index, m.Groups["secret"].Length, "<removed>"));
}
return message;
}
}
}

View File

@ -43,6 +43,10 @@
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="RestSharp, Version=104.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\RestSharp.104.4.0\lib\net4\RestSharp.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
@ -59,8 +63,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ArchiveProvider.cs" />
<Compile Include="ConvertBase32.cs" />
<Compile Include="ArchiveService.cs" />
<Compile Include="Cache\Cached.cs" />
<Compile Include="Cache\CacheManager.cs" />
<Compile Include="Cache\ICached.cs" />

View File

@ -11,9 +11,9 @@ namespace NzbDrone.Common
private const string APP_CONFIG_FILE = "config.xml";
private const string NZBDRONE_DB = "nzbdrone.db";
private const string NZBDRONE_LOG_DB = "logs.db";
private const string BACKUP_ZIP_FILE = "NzbDrone_Backup.zip";
private const string NLOG_CONFIG_FILE = "nlog.config";
private const string UPDATE_CLIENT_EXE = "NzbDrone.Update.exe";
private const string BACKUP_FOLDER = "Backups";
private static readonly string UPDATE_SANDBOX_FOLDER_NAME = "nzbdrone_update" + Path.DirectorySeparatorChar;
private static readonly string UPDATE_PACKAGE_FOLDER_NAME = "NzbDrone" + Path.DirectorySeparatorChar;
@ -53,6 +53,22 @@ namespace NzbDrone.Common
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
}
public static string GetParentPath(this string childPath)
{
var parentPath = childPath.TrimEnd('\\', '/');
var index = parentPath.LastIndexOfAny(new[] { '\\', '/' });
if (index != -1)
{
return parentPath.Substring(0, index);
}
else
{
return null;
}
}
public static bool IsParentPath(this string parentPath, string childPath)
{
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
@ -210,9 +226,9 @@ namespace NzbDrone.Common
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE);
}
public static string GetConfigBackupFile(this IAppFolderInfo appFolderInfo)
public static string GetBackupFolder(this IAppFolderInfo appFolderInfo)
{
return Path.Combine(GetAppDataPath(appFolderInfo), BACKUP_ZIP_FILE);
return Path.Combine(GetAppDataPath(appFolderInfo), BACKUP_FOLDER);
}
public static string GetNzbDroneDatabase(this IAppFolderInfo appFolderInfo)

View File

@ -9,8 +9,4 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b6eaa144-e13b-42e5-a738-c60d89c0f728")]
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("10.0.0.*")]
[assembly: AssemblyFileVersion("10.0.0.*")]

View File

@ -15,16 +15,16 @@ namespace NzbDrone.Common.Reflection
return properties.Where(c => c.PropertyType.IsSimpleType()).ToList();
}
public static List<Type> ImplementationsOf<T>(this Assembly assembly)
{
return assembly.GetTypes().Where(c => typeof(T).IsAssignableFrom(c)).ToList();
}
public static bool IsSimpleType(this Type type)
{
if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>) || type.GetGenericTypeDefinition() == typeof(List<>)))
if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>) ||
type.GetGenericTypeDefinition() == typeof(List<>) ||
type.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
type = type.GetGenericArguments()[0];
}

View File

@ -48,12 +48,12 @@ namespace NzbDrone.Common.Serializer
result = Deserialize<T>(json);
return true;
}
catch (JsonReaderException ex)
catch (JsonReaderException)
{
result = default(T);
return false;
}
catch (JsonSerializationException ex)
catch (JsonSerializationException)
{
result = default(T);
return false;

View File

@ -32,6 +32,13 @@ namespace NzbDrone.Common
private static readonly Regex CollapseSpace = new Regex(@"\s+", RegexOptions.Compiled);
public static string Replace(this string text, int index, int length, string replacement)
{
text = text.Remove(index, length);
text = text.Insert(index, replacement);
return text;
}
public static string RemoveAccent(this string text)
{
var normalizedString = text.Normalize(NormalizationForm.FormD);

View File

@ -3,5 +3,6 @@
<package id="loggly-csharp" version="2.3" targetFramework="net40" />
<package id="Newtonsoft.Json" version="5.0.8" targetFramework="net40" />
<package id="NLog" version="2.1.0" targetFramework="net40" />
<package id="RestSharp" version="104.4.0" targetFramework="net40" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
</packages>

View File

@ -8,8 +8,4 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTitle("NzbDrone.Host")]
[assembly: Guid("67AADCD9-89AA-4D95-8281-3193740E70E5")]
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("10.0.0.*")]
[assembly: AssemblyFileVersion("10.0.0.*")]
[assembly: AssemblyVersion("10.0.0.*")]

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FizzWare.NBuilder;
using Moq;
@ -14,9 +16,11 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
public class SceneMappingServiceFixture : CoreTest<SceneMappingService>
{
private List<SceneMapping> _fakeMappings;
private Mock<ISceneMappingProvider> _provider1;
private Mock<ISceneMappingProvider> _provider2;
[SetUp]
public void Setup()
{
@ -33,14 +37,24 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
_fakeMappings[2].ParseTerm = "Can";
_fakeMappings[3].ParseTerm = "Be";
_fakeMappings[4].ParseTerm = "Cleaned";
_provider1 = new Mock<ISceneMappingProvider>();
_provider1.Setup(s => s.GetSceneMappings()).Returns(_fakeMappings);
_provider2 = new Mock<ISceneMappingProvider>();
_provider2.Setup(s => s.GetSceneMappings()).Returns(_fakeMappings);
}
private void GivenProviders(IEnumerable<Mock<ISceneMappingProvider>> providers)
{
Mocker.SetConstant<IEnumerable<ISceneMappingProvider>>(providers.Select(s => s.Object));
}
[Test]
public void UpdateMappings_purge_existing_mapping_and_add_new_ones()
public void should_purge_existing_mapping_and_add_new_ones()
{
Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Returns(_fakeMappings);
GivenProviders(new [] { _provider1 });
Mocker.GetMock<ISceneMappingRepository>().Setup(c => c.All()).Returns(_fakeMappings);
Subject.Execute(new UpdateSceneMappingCommand());
@ -48,27 +62,26 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
AssertMappingUpdated();
}
[Test]
public void UpdateMappings_should_not_delete_if_fetch_fails()
public void should_not_delete_if_fetch_fails()
{
GivenProviders(new[] { _provider1 });
Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Throws(new WebException());
_provider1.Setup(c => c.GetSceneMappings()).Throws(new WebException());
Subject.Execute(new UpdateSceneMappingCommand());
AssertNoUpdate();
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void UpdateMappings_should_not_delete_if_fetch_returns_empty_list()
public void should_not_delete_if_fetch_returns_empty_list()
{
GivenProviders(new[] { _provider1 });
Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Returns(new List<SceneMapping>());
_provider1.Setup(c => c.GetSceneMappings()).Returns(new List<SceneMapping>());
Subject.Execute(new UpdateSceneMappingCommand());
@ -77,28 +90,37 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_get_mappings_for_all_providers()
{
GivenProviders(new[] { _provider1, _provider2 });
Mocker.GetMock<ISceneMappingRepository>().Setup(c => c.All()).Returns(_fakeMappings);
Subject.Execute(new UpdateSceneMappingCommand());
_provider1.Verify(c => c.GetSceneMappings(), Times.Once());
_provider2.Verify(c => c.GetSceneMappings(), Times.Once());
}
private void AssertNoUpdate()
{
Mocker.GetMock<ISceneMappingProxy>().Verify(c => c.Fetch(), Times.Once());
Mocker.GetMock<ISceneMappingRepository>().Verify(c => c.Purge(It.IsAny<bool>()), Times.Never());
_provider1.Verify(c => c.GetSceneMappings(), Times.Once());
Mocker.GetMock<ISceneMappingRepository>().Verify(c => c.Clear(It.IsAny<String>()), Times.Never());
Mocker.GetMock<ISceneMappingRepository>().Verify(c => c.InsertMany(_fakeMappings), Times.Never());
}
private void AssertMappingUpdated()
{
Mocker.GetMock<ISceneMappingProxy>().Verify(c => c.Fetch(), Times.Once());
Mocker.GetMock<ISceneMappingRepository>().Verify(c => c.Purge(It.IsAny<bool>()), Times.Once());
_provider1.Verify(c => c.GetSceneMappings(), Times.Once());
Mocker.GetMock<ISceneMappingRepository>().Verify(c => c.Clear(It.IsAny<String>()), Times.Once());
Mocker.GetMock<ISceneMappingRepository>().Verify(c => c.InsertMany(_fakeMappings), Times.Once());
foreach (var sceneMapping in _fakeMappings)
{
Subject.GetSceneName(sceneMapping.TvdbId).Should().Be(sceneMapping.SearchTerm);
Subject.GetSceneNames(sceneMapping.TvdbId, _fakeMappings.Select(m => m.SeasonNumber)).Should().Contain(sceneMapping.SearchTerm);
Subject.GetTvDbId(sceneMapping.ParseTerm).Should().Be(sceneMapping.TvdbId);
}
}
}
}

View File

@ -54,26 +54,30 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenEmptyQueue()
{
Mocker.GetMock<IQueueService>()
.Setup(s => s.GetQueue())
.Returns(new List<Queue.Queue>());
Mocker.GetMock<IDownloadTrackingService>()
.Setup(s => s.GetQueuedDownloads())
.Returns(new TrackedDownload[0]);
}
private void GivenQueue(IEnumerable<RemoteEpisode> remoteEpisodes)
private void GivenQueue(IEnumerable<RemoteEpisode> remoteEpisodes, TrackedDownloadState state = TrackedDownloadState.Downloading)
{
var queue = new List<Queue.Queue>();
var queue = new List<TrackedDownload>();
foreach (var remoteEpisode in remoteEpisodes)
{
queue.Add(new Queue.Queue
queue.Add(new TrackedDownload
{
RemoteEpisode = remoteEpisode
});
State = state,
DownloadItem = new DownloadClientItem
{
RemoteEpisode = remoteEpisode
}
});
}
Mocker.GetMock<IQueueService>()
.Setup(s => s.GetQueue())
.Returns(queue);
Mocker.GetMock<IDownloadTrackingService>()
.Setup(s => s.GetQueuedDownloads())
.Returns(queue.ToArray());
}
[Test]
@ -95,6 +99,23 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_return_true_when_download_is_failed()
{
var remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Quality = new QualityModel(Quality.DVD)
})
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }, TrackedDownloadState.DownloadFailed);
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_return_true_when_quality_in_queue_is_lower()
{

View File

@ -1,20 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NUnit.Framework;
using FluentAssertions;
using FizzWare.NBuilder;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class DownloadApprovedReportsFixture : CoreTest<DownloadApprovedReports>
public class PrioritizeDownloadDecisionFixture : CoreTest<DownloadDecisionPriorizationService>
{
private Episode GetEpisode(int id)
{
@ -44,16 +45,6 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
return remoteEpisode;
}
[Test]
public void should_return_an_empty_list_when_none_are_appproved()
{
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(null, "Failure!"));
decisions.Add(new DownloadDecision(null, "Failure!"));
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
}
[Test]
public void should_put_propers_before_non_propers()
{
@ -64,7 +55,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
var qualifiedReports = Subject.GetQualifiedReports(decisions);
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Proper.Should().BeTrue();
}
@ -78,7 +69,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
var qualifiedReports = Subject.GetQualifiedReports(decisions);
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.HDTV720p);
}
@ -92,7 +83,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
var qualifiedReports = Subject.GetQualifiedReports(decisions);
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteEpisode.Episodes.First().EpisodeNumber.Should().Be(1);
}
@ -106,7 +97,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
var qualifiedReports = Subject.GetQualifiedReports(decisions);
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteEpisode.Episodes.First().EpisodeNumber.Should().Be(1);
}
@ -125,7 +116,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisodeHdSmallYounge));
decisions.Add(new DownloadDecision(remoteEpisodeHdLargeYounge));
var qualifiedReports = Subject.GetQualifiedReports(decisions);
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteEpisode.Should().Be(remoteEpisodeHdSmallYounge);
}
@ -140,7 +131,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
var qualifiedReports = Subject.GetQualifiedReports(decisions);
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteEpisode.Should().Be(remoteEpisode2);
}
}

View File

@ -12,6 +12,7 @@ using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Test.Common;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Parser.Model;
@ -103,14 +104,20 @@ namespace NzbDrone.Core.Test.Download
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<Core.MediaFiles.EpisodeImport.ImportDecision>() { new Core.MediaFiles.EpisodeImport.ImportDecision(null) });
.Returns(new List<ImportDecision>()
{
new ImportDecision(null)
});
}
private void GivenFailedImport()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<Core.MediaFiles.EpisodeImport.ImportDecision>());
.Returns(new List<ImportDecision>()
{
new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure")
});
}
private void VerifyNoImports()
@ -265,6 +272,8 @@ namespace NzbDrone.Core.Test.Download
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports();
ExceptionVerification.IgnoreErrors();
}
[Test]
@ -289,6 +298,8 @@ namespace NzbDrone.Core.Test.Download
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports();
ExceptionVerification.IgnoreWarns();
}
[Test]
@ -412,6 +423,8 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
ExceptionVerification.IgnoreErrors();
}
[Test]

View File

@ -11,12 +11,21 @@ using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using NzbDrone.Core.DecisionEngine;
namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
{
[TestFixture]
public class DownloadApprovedFixture : CoreTest<DownloadApprovedReports>
{
[SetUp]
public void SetUp()
{
Mocker.GetMock<IPrioritizeDownloadDecision>()
.Setup(v => v.PrioritizeDecisions(It.IsAny<List<DownloadDecision>>()))
.Returns<List<DownloadDecision>>(v => v);
}
private Episode GetEpisode(int id)
{
return Builder<Episode>.CreateNew()
@ -163,5 +172,15 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
Subject.DownloadApproved(decisions).Should().BeEmpty();
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_return_an_empty_list_when_none_are_appproved()
{
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(null, "Failure!"));
decisions.Add(new DownloadDecision(null, "Failure!"));
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
}
}
}

View File

@ -1,17 +1,18 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Test.Common;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
@ -28,7 +29,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new NzbgetSettings
{
Host = "192.168.5.55",
Host = "127.0.0.1",
Port = 2222,
Username = "admin",
Password = "pass",
@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
FileSizeLo = 1000,
Category = "tv",
Name = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
DestDir = "somedirectory",
DestDir = "/remote/mount/tv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
Parameters = new List<NzbgetParameter> { new NzbgetParameter { Name = "drone", Value = "id" } },
ParStatus = "SUCCESS",
UnpackStatus = "NONE",
@ -81,6 +82,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
DownloadRate = 7000000
});
var configItems = new Dictionary<String, String>();
configItems.Add("Category1.Name", "tv");
configItems.Add("Category1.DestDir", @"/remote/mount/tv");
Mocker.GetMock<INzbgetProxy>()
.Setup(v => v.GetConfig(It.IsAny<NzbgetSettings>()))
.Returns(configItems);
}
protected void WithMountPoint(String mountPath)
{
(Subject.Definition.Settings as NzbgetSettings).TvCategoryLocalPath = mountPath;
}
protected void WithFailedDownload()
@ -223,5 +237,40 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
items.Should().BeEmpty();
}
[Test]
public void should_return_status_with_outputdir()
{
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"/remote/mount/tv");
}
[Test]
public void should_return_status_with_mounted_outputdir()
{
WithMountPoint(@"O:\mymount".AsOsAgnostic());
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"O:\mymount".AsOsAgnostic());
}
[Test]
public void should_remap_storage_if_mounted()
{
WithMountPoint(@"O:\mymount".AsOsAgnostic());
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
}
}
}

View File

@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
private SabnzbdQueue _queued;
private SabnzbdHistory _failed;
private SabnzbdHistory _completed;
private SabnzbdConfig _config;
[SetUp]
public void Setup()
@ -30,7 +31,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new SabnzbdSettings
{
Host = "192.168.5.55",
Host = "127.0.0.1",
Port = 2222,
ApiKey = "5c770e3197e4fe763423ee7c392c25d1",
Username = "admin",
@ -40,6 +41,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
};
_queued = new SabnzbdQueue
{
DefaultRootFolder = @"Y:\nzbget\root".AsOsAgnostic(),
Paused = false,
Items = new List<SabnzbdQueueItem>()
{
@ -82,10 +84,31 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
Category = "tv",
Id = "sabnzbd_nzb12345",
Title = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
Storage = "somedirectory"
Storage = "/remote/mount/vv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE"
}
}
};
_config = new SabnzbdConfig
{
Misc = new SabnzbdConfigMisc
{
complete_dir = @"/remote/mount"
},
Categories = new List<SabnzbdCategory>
{
new SabnzbdCategory { Name = "tv", Dir = "vv" }
}
};
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.GetConfig(It.IsAny<SabnzbdSettings>()))
.Returns(_config);
}
protected void WithMountPoint(String mountPath)
{
(Subject.Definition.Settings as SabnzbdSettings).TvCategoryLocalPath = mountPath;
}
protected void WithFailedDownload()
@ -110,7 +133,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{
if (queue == null)
{
queue = new SabnzbdQueue() { Items = new List<SabnzbdQueueItem>() };
queue = new SabnzbdQueue()
{
DefaultRootFolder = _queued.DefaultRootFolder,
Items = new List<SabnzbdQueueItem>()
};
}
Mocker.GetMock<ISabnzbdProxy>()
@ -256,17 +283,34 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
.Verify(v => v.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()), Times.Once());
}
[Test]
public void should_return_path_to_folder_instead_of_file()
[TestCase(@"Droned.S01E01.Pilot.1080p.WEB-DL-DRONE", @"Droned.S01E01_Pilot_1080p_WEB-DL-DRONE.mkv")]
[TestCase(@"Droned.S01E01.Pilot.1080p.WEB-DL-DRONE", @"SubDir\Droned.S01E01_Pilot_1080p_WEB-DL-DRONE.mkv")]
[TestCase(@"Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.mkv", @"SubDir\Droned.S01E01_Pilot_1080p_WEB-DL-DRONE.mkv")]
[TestCase(@"Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.mkv", @"SubDir\SubDir\Droned.S01E01_Pilot_1080p_WEB-DL-DRONE.mkv")]
public void should_return_path_to_jobfolder(String title, String storage)
{
_completed.Items.First().Storage = @"C:\sorted\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE\Droned.S01E01_Pilot_1080p_WEB-DL-DRONE.mkv".AsOsAgnostic();
_completed.Items.First().Title = title;
_completed.Items.First().Storage = (@"C:\sorted\" + title + @"\" + storage).AsOsAgnostic();
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
result.OutputPath.Should().Be(@"C:\sorted\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
result.OutputPath.Should().Be((@"C:\sorted\" + title).AsOsAgnostic());
}
[Test]
public void should_remap_storage_if_mounted()
{
WithMountPoint(@"O:\mymount".AsOsAgnostic());
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
}
[Test]
@ -281,5 +325,51 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
result.OutputPath.Should().Be(@"C:\".AsOsAgnostic());
}
[Test]
public void should_not_blow_up_if_storage_doesnt_have_jobfolder()
{
_completed.Items.First().Storage = @"C:\sorted\somewhere\asdfasdf\asdfasdf.mkv".AsOsAgnostic();
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
result.OutputPath.Should().Be(@"C:\sorted\somewhere\asdfasdf\asdfasdf.mkv".AsOsAgnostic());
}
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads\vv")]
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed\vv")]
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads/vv")]
[TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed/vv")]
public void should_return_status_with_outputdir(String rootFolder, String completeDir, String categoryDir, String expectedDir)
{
_queued.DefaultRootFolder = rootFolder;
_config.Misc.complete_dir = completeDir;
_config.Categories.First().Dir = categoryDir;
WithQueue(null);
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(expectedDir);
}
[Test]
public void should_return_status_with_mounted_outputdir()
{
WithMountPoint(@"O:\mymount".AsOsAgnostic());
WithQueue(null);
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"O:\mymount".AsOsAgnostic());
}
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Processes;
using NzbDrone.Core.HealthCheck.Checks;
@ -78,5 +77,30 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_ok_when_mono_3_6_1()
{
GivenOutput("3.6.1");
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_ok_when_mono_3_6_1_with_custom_output()
{
Mocker.GetMock<IProcessProvider>()
.Setup(s => s.StartAndCapture("mono", "--version"))
.Returns(new ProcessOutput
{
Standard = new List<string>
{
"Mono JIT compiler version 3.6.1 (master/fce3972 Fri Jul 4 01:12:43 CEST 2014)",
"Copyright (C) 2002-2011 Novell, Inc, Xamarin, Inc and Contributors. www.mono-project.com"
}
});
Subject.Check().ShouldBeOk();
}
}
}

View File

@ -1,4 +1,5 @@
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Test.Framework;
using FizzWare.NBuilder;
using System;
@ -29,9 +30,9 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer> { indexer.Object });
Mocker.GetMock<NzbDrone.Core.DecisionEngine.IMakeDownloadDecision>()
Mocker.GetMock<DecisionEngine.IMakeDownloadDecision>()
.Setup(s => s.GetSearchDecision(It.IsAny<List<Parser.Model.ReleaseInfo>>(), It.IsAny<SearchCriteriaBase>()))
.Returns(new List<NzbDrone.Core.DecisionEngine.Specifications.DownloadDecision>());
.Returns(new List<DecisionEngine.Specifications.DownloadDecision>());
_xemSeries = Builder<Series>.CreateNew()
.With(v => v.UseSceneNumbering = true)
@ -46,6 +47,10 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
Mocker.GetMock<IEpisodeService>()
.Setup(v => v.GetEpisodesBySeason(_xemSeries.Id, It.IsAny<int>()))
.Returns<int, int>((i, j) => _xemEpisodes.Where(d => d.SeasonNumber == j).ToList());
Mocker.GetMock<ISceneMappingService>()
.Setup(s => s.GetSceneNames(It.IsAny<Int32>(), It.IsAny<IEnumerable<Int32>>()))
.Returns(new List<String>());
}
private void WithEpisode(int seasonNumber, int episodeNumber, int sceneSeasonNumber, int sceneEpisodeNumber)
@ -90,7 +95,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
private List<SearchCriteriaBase> WatchForSearchCriteria()
{
List<SearchCriteriaBase> result = new List<SearchCriteriaBase>();
var result = new List<SearchCriteriaBase>();
Mocker.GetMock<IFetchFeedFromIndexers>()
.Setup(v => v.Fetch(It.IsAny<IIndexer>(), It.IsAny<SingleEpisodeSearchCriteria>()))
@ -102,6 +107,11 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
.Callback<IIndexer, SeasonSearchCriteria>((i, s) => result.Add(s))
.Returns(new List<Parser.Model.ReleaseInfo>());
Mocker.GetMock<IFetchFeedFromIndexers>()
.Setup(v => v.Fetch(It.IsAny<IIndexer>(), It.IsAny<AnimeEpisodeSearchCriteria>()))
.Callback<IIndexer, AnimeEpisodeSearchCriteria>((i, s) => result.Add(s))
.Returns(new List<Parser.Model.ReleaseInfo>());
return result;
}
@ -186,5 +196,21 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
criteria.Count.Should().Be(1);
criteria[0].SeasonNumber.Should().Be(7);
}
[Test]
public void season_search_for_anime_should_search_for_each_episode()
{
WithEpisodes();
_xemSeries.SeriesType = SeriesTypes.Anime;
var seasonNumber = 1;
var allCriteria = WatchForSearchCriteria();
Subject.SeasonSearch(_xemSeries.Id, seasonNumber);
var criteria = allCriteria.OfType<AnimeEpisodeSearchCriteria>().ToList();
criteria.Count.Should().Be(_xemEpisodes.Count(e => e.SeasonNumber == seasonNumber));
}
}
}

View File

@ -1,3 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@ -12,8 +14,8 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
[TestCase("Franklin & Bash", Result = "Franklin+and+Bash")]
public string should_replace_some_special_characters(string input)
{
Subject.SceneTitle = input;
return Subject.QueryTitle;
Subject.SceneTitles = new List<string> { input };
return Subject.QueryTitles.First();
}
}
}

View File

@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Newznab;
@ -22,7 +20,7 @@ namespace NzbDrone.Core.Test.IndexerTests
{
_indexers = new List<IIndexer>();
_indexers.Add(new Newznab());
_indexers.Add(Mocker.GetMock<Newznab>().Object);
_indexers.Add(new Omgwtfnzbs());
_indexers.Add(new Wombles());

View File

@ -4,7 +4,6 @@ using FizzWare.NBuilder;
using FluentValidation.Results;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch.Definitions;
@ -38,7 +37,7 @@ namespace NzbDrone.Core.Test.IndexerTests
indexer.Setup(s => s.Parser.Process(It.IsAny<String>(), It.IsAny<String>()))
.Returns(results);
indexer.Setup(s => s.GetSeasonSearchUrls(It.IsAny<String>(), It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>()))
indexer.Setup(s => s.GetSeasonSearchUrls(It.IsAny<List<String>>(), It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>()))
.Returns(new List<string> { "http://www.nzbdrone.com" });
indexer.SetupGet(s => s.SupportedPageSize).Returns(paging ? 100 : 0);
@ -56,7 +55,7 @@ namespace NzbDrone.Core.Test.IndexerTests
public void should_not_use_offset_if_result_count_is_less_than_90()
{
var indexer = WithIndexer(true, 25);
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitle = _series.Title });
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitles = new List<string>{_series.Title} });
Mocker.GetMock<IHttpProvider>().Verify(v => v.DownloadString(It.IsAny<String>()), Times.Once());
}
@ -65,7 +64,7 @@ namespace NzbDrone.Core.Test.IndexerTests
public void should_not_use_offset_for_sites_that_do_not_support_it()
{
var indexer = WithIndexer(false, 125);
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitle = _series.Title });
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitles = new List<string> { _series.Title } });
Mocker.GetMock<IHttpProvider>().Verify(v => v.DownloadString(It.IsAny<String>()), Times.Once());
}
@ -74,7 +73,7 @@ namespace NzbDrone.Core.Test.IndexerTests
public void should_not_use_offset_if_its_already_tried_10_times()
{
var indexer = WithIndexer(true, 100);
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitle = _series.Title });
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitles = new List<string> { _series.Title } });
Mocker.GetMock<IHttpProvider>().Verify(v => v.DownloadString(It.IsAny<String>()), Times.Exactly(10));
}

View File

@ -40,6 +40,7 @@ namespace NzbDrone.Core.Test.MetadataSourceTests
[TestCase(75978)]
[TestCase(83462)]
[TestCase(266189)]
public void should_be_able_to_get_series_detail(int tvdbId)
{
var details = Subject.GetSeriesInfo(tvdbId);
@ -56,11 +57,20 @@ namespace NzbDrone.Core.Test.MetadataSourceTests
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_not_have_period_at_start_of_title_slug()
{
var details = Subject.GetSeriesInfo(79099);
details.Item1.TitleSlug.Should().Be("dothack");
}
private void ValidateSeries(Series series)
{
series.Should().NotBeNull();
series.Title.Should().NotBeBlank();
series.CleanTitle.Should().Be(Parser.Parser.CleanSeriesTitle(series.Title));
series.SortTitle.Should().Be(Parser.Parser.NormalizeEpisodeTitle(series.Title));
series.Overview.Should().NotBeBlank();
series.AirTime.Should().NotBeBlank();
series.FirstAired.Should().HaveValue();

View File

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.Tvdb;
using NzbDrone.Core.Rest;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories;
namespace NzbDrone.Core.Test.MetadataSourceTests
{
[TestFixture]
[IntegrationTest]
public class TvdbProxyFixture : CoreTest<TvdbProxy>
{
// [TestCase("The Simpsons", "The Simpsons")]
// [TestCase("South Park", "South Park")]
// [TestCase("Franklin & Bash", "Franklin & Bash")]
// [TestCase("Mr. D", "Mr. D")]
// [TestCase("Rob & Big", "Rob and Big")]
// [TestCase("M*A*S*H", "M*A*S*H")]
// public void successful_search(string title, string expected)
// {
// var result = Subject.SearchForNewSeries(title);
//
// result.Should().NotBeEmpty();
//
// result[0].Title.Should().Be(expected);
// }
//
// [Test]
// public void no_search_result()
// {
// var result = Subject.SearchForNewSeries(Guid.NewGuid().ToString());
// result.Should().BeEmpty();
// }
[TestCase(88031)]
[TestCase(179321)]
public void should_be_able_to_get_series_detail(int tvdbId)
{
var details = Subject.GetSeriesInfo(tvdbId);
//ValidateSeries(details.Item1);
ValidateEpisodes(details.Item2);
}
// [Test]
// public void getting_details_of_invalid_series()
// {
// Assert.Throws<RestException>(() => Subject.GetSeriesInfo(Int32.MaxValue));
//
// ExceptionVerification.ExpectedWarns(1);
// }
//
// [Test]
// public void should_not_have_period_at_start_of_title_slug()
// {
// var details = Subject.GetSeriesInfo(79099);
//
// details.Item1.TitleSlug.Should().Be("dothack");
// }
private void ValidateSeries(Series series)
{
series.Should().NotBeNull();
series.Title.Should().NotBeBlank();
series.CleanTitle.Should().Be(Parser.Parser.CleanSeriesTitle(series.Title));
series.Overview.Should().NotBeBlank();
series.AirTime.Should().NotBeBlank();
series.FirstAired.Should().HaveValue();
series.FirstAired.Value.Kind.Should().Be(DateTimeKind.Utc);
series.Images.Should().NotBeEmpty();
series.ImdbId.Should().NotBeBlank();
series.Network.Should().NotBeBlank();
series.Runtime.Should().BeGreaterThan(0);
series.TitleSlug.Should().NotBeBlank();
series.TvRageId.Should().BeGreaterThan(0);
series.TvdbId.Should().BeGreaterThan(0);
}
private void ValidateEpisodes(List<Episode> episodes)
{
episodes.Should().NotBeEmpty();
episodes.GroupBy(e => e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000"))
.Max(e => e.Count()).Should().Be(1);
episodes.Should().Contain(c => c.SeasonNumber > 0);
// episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Overview));
foreach (var episode in episodes)
{
ValidateEpisode(episode);
//if atleast one episdoe has title it means parse it working.
// episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Title));
}
}
private void ValidateEpisode(Episode episode)
{
episode.Should().NotBeNull();
//TODO: Is there a better way to validate that episode number or season number is greater than zero?
(episode.EpisodeNumber + episode.SeasonNumber).Should().NotBe(0);
episode.Should().NotBeNull();
// if (episode.AirDateUtc.HasValue)
// {
// episode.AirDateUtc.Value.Kind.Should().Be(DateTimeKind.Utc);
// }
}
}
}

View File

@ -114,7 +114,7 @@
<Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\Search\SeriesSpecificationFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\GetQualifiedReportsFixture.cs" />
<Compile Include="DecisionEngineTests\PrioritizeDownloadDecisionFixture.cs" />
<Compile Include="Download\DownloadClientTests\Blackhole\UsenetBlackholeFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadClientFixtureBase.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetTests\NzbgetFixture.cs" />
@ -173,6 +173,7 @@
<Compile Include="Messaging\Commands\CommandExecutorFixture.cs" />
<Compile Include="Messaging\Commands\CommandFixture.cs" />
<Compile Include="Messaging\Events\EventAggregatorFixture.cs" />
<Compile Include="MetadataSourceTests\TvdbProxyFixture.cs" />
<Compile Include="MetadataSourceTests\TraktProxyFixture.cs" />
<Compile Include="Metadata\Consumers\Roksbox\FindMetadataFileFixture.cs" />
<Compile Include="Metadata\Consumers\Wdtv\FindMetadataFileFixture.cs" />
@ -189,6 +190,7 @@
<Compile Include="OrganizerTests\BuildFilePathFixture.cs" />
<Compile Include="OrganizerTests\GetSeriesFolderFixture.cs" />
<Compile Include="ParserTests\AbsoluteEpisodeNumberParserFixture.cs" />
<Compile Include="ParserTests\AnimeMetadataParserFixture.cs" />
<Compile Include="ParserTests\IsPossibleSpecialEpisodeFixture.cs" />
<Compile Include="ParserTests\ReleaseGroupParserFixture.cs" />
<Compile Include="ParserTests\LanguageParserFixture.cs" />
@ -249,7 +251,7 @@
<Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest_GetEpisodesByParseResult.cs" />
<Compile Include="FluentTest.cs" />
<Compile Include="InstrumentationTests\DatabaseTargetFixture.cs" />
<Compile Include="OrganizerTests\GetNewFilenameFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderFixture.cs" />
<Compile Include="DecisionEngineTests\MonitoredEpisodeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\DownloadDecisionMakerFixture.cs" />
<Compile Include="Qualities\QualityModelComparerFixture.cs" />

View File

@ -42,12 +42,14 @@ namespace NzbDrone.Core.Test.OrganizerTests
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 6)
.With(e => e.AbsoluteEpisodeNumber = 100)
.Build();
_episode2 = Builder<Episode>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 7)
.With(e => e.AbsoluteEpisodeNumber = 101)
.Build();
_episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "DRONE" };
@ -435,5 +437,78 @@ namespace NzbDrone.Core.Test.OrganizerTests
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "Chicago P.D.." }, _episodeFile)
.Should().Be("Chicago.P.D.S06E06.Part.1");
}
[Test]
public void should_not_replace_absolute_numbering_when_series_is_not_anime()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.City.Sushi");
}
[Test]
public void should_replace_standard_and_absolute_numbering_when_series_is_anime()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.100.City.Sushi");
}
[Test]
public void should_replace_standard_numbering_when_series_is_anime()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.City.Sushi");
}
[Test]
public void should_replace_absolute_numbering_when_series_is_anime()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.{absolute:00}.{Episode.Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.100.City.Sushi");
}
[Test]
public void should_use_dash_as_separator_when_multi_episode_style_is_extend_for_anime()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFilename(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - 100-101 - City Sushi");
}
[Test]
public void should_use_standard_naming_when_anime_episode_has_absolute_number_of_zero()
{
_series.SeriesType = SeriesTypes.Anime;
_episode1.AbsoluteEpisodeNumber = 0;
_namingConfig.StandardEpisodeFormat = "{Series Title} - {season:0}x{episode:00} - {Episode Title}";
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFilename(new List<Episode> { _episode1, }, _series, _episodeFile)
.Should().Be("South Park - 15x06 - City Sushi");
}
[Test]
public void should_duplicate_absolute_pattern_when_multi_episode_style_is_duplicate()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.MultiEpisodeStyle = (int)MultiEpisodeStyle.Duplicate;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFilename(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - 100 - 101 - City Sushi");
}
}
}

View File

@ -34,6 +34,38 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[SFW-sage] Bakuman S3 - 12 [720p][D07C91FC]", "Bakuman S3", 12, 0, 0)]
[TestCase("ducktales_e66_time_is_money_part_one_marking_time", "DuckTales", 66, 0, 0)]
[TestCase("[Underwater-FFF] No Game No Life - 01 (720p) [27AAA0A0].mkv", "No Game No Life", 1, 0, 0)]
[TestCase("[FroZen] Miyuki - 23 [DVD][7F6170E6]", "Miyuki", 23, 0, 0)]
[TestCase("[Commie] Yowamushi Pedal - 32 [0BA19D5B]", "Yowamushi Pedal", 32, 0, 0)]
[TestCase("[Doki] Mahouka Koukou no Rettousei - 07 (1280x720 Hi10P AAC) [80AF7DDE]", "Mahouka Koukou no Rettousei", 7, 0, 0)]
[TestCase("[HorribleSubs] Yowamushi Pedal - 32 [480p]", "Yowamushi Pedal", 32, 0, 0)]
[TestCase("[CR] Sailor Moon - 004 [480p][48CE2D0F]", "Sailor Moon", 4, 0, 0)]
[TestCase("[Chibiki] Puchimas!! - 42 [360p][7A4FC77B]", "Puchimas", 42, 0, 0)]
[TestCase("[HorribleSubs] Yowamushi Pedal - 32 [1080p]", "Yowamushi Pedal", 32, 0, 0)]
[TestCase("[HorribleSubs] Love Live! S2 - 07 [720p]", "Love Live! S2", 7, 0, 0)]
[TestCase("[DeadFish] Onee-chan ga Kita - 09v2 [720p][AAC]", "Onee-chan ga Kita", 9, 0, 0)]
[TestCase("[Underwater-FFF] No Game No Life - 01 (720p) [27AAA0A0]", "No Game No Life", 1, 0, 0)]
[TestCase("[S-T-D] Soul Eater Not! - 06 (1280x720 10bit AAC) [59B3F2EA].mkv", "Soul Eater Not!", 6, 0, 0)]
[TestCase("[Underwater-FFF] No Game No Life - 01 (720p) [27AAA0A0].mkv", "No Game No Life", 1, 0, 0)]
[TestCase("No Game No Life - 010 (720p) [27AAA0A0].mkv", "No Game No Life", 10, 0, 0)]
[TestCase("Initial D Fifth Stage - 01 DVD - Central Anime", "Initial D Fifth Stage", 1, 0, 0)]
[TestCase("Initial_D_Fifth_Stage_-_01(DVD)_-_(Central_Anime)[5AF6F1E4].mkv", "Initial D Fifth Stage", 1, 0, 0)]
[TestCase("Initial_D_Fifth_Stage_-_02(DVD)_-_(Central_Anime)[0CA65F00].mkv", "Initial D Fifth Stage", 2, 0, 0)]
[TestCase("Initial D Fifth Stage - 03 DVD - Central Anime", "Initial D Fifth Stage", 3, 0, 0)]
[TestCase("Initial_D_Fifth_Stage_-_03(DVD)_-_(Central_Anime)[629BD592].mkv", "Initial D Fifth Stage", 3, 0, 0)]
[TestCase("Initial D Fifth Stage - 14 DVD - Central Anime", "Initial D Fifth Stage", 14, 0, 0)]
[TestCase("Initial_D_Fifth_Stage_-_14(DVD)_-_(Central_Anime)[0183D922].mkv", "Initial D Fifth Stage", 14, 0, 0)]
// [TestCase("Initial D - 4th Stage Ep 01.mkv", "Initial D - 4th Stage", 1, 0, 0)]
[TestCase("[ChihiroDesuYo].No.Game.No.Life.-.09.1280x720.10bit.AAC.[24CCE81D]", "No.Game.No.Life", 9, 0, 0)]
[TestCase("Fairy Tail - 001 - Fairy Tail", "Fairy Tail", 001, 0, 0)]
[TestCase("Fairy Tail - 049 - The Day of Fated Meeting", "Fairy Tail", 049, 0, 0)]
[TestCase("Fairy Tail - 050 - Special Request Watch Out for the Guy You Like!", "Fairy Tail", 050, 0, 0)]
[TestCase("Fairy Tail - 099 - Natsu vs. Gildarts", "Fairy Tail", 099, 0, 0)]
[TestCase("Fairy Tail - 100 - Mest", "Fairy Tail", 100, 0, 0)]
// [TestCase("Fairy Tail - 101 - Mest", "Fairy Tail", 101, 0, 0)] //This gets caught up in the 'see' numbering
[TestCase("[Exiled-Destiny] Angel Beats Ep01 (D2201EC5).mkv", "Angel Beats!", 1, 0, 0)]
[TestCase("[Commie] Nobunaga the Fool - 23 [5396CA24].mkv", "Nobunaga the Fool", 23, 0, 0)]
[TestCase("[FFF] Seikoku no Dragonar - 01 [1FB538B5].mkv", "Seikoku no Dragonar", 1, 0, 0)]
[TestCase("[Hatsuyuki]Fate_Zero-01[1280x720][122E6EF8]", "Fate/Zero", 1, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
{
var result = Parser.Parser.ParseTitle(postTitle);

View File

@ -0,0 +1,37 @@
using System;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class AnimeMetadataParserFixture : CoreTest
{
[TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "SubDESU", "6B7FD717")]
[TestCase("[Chihiro]_Working!!_-_06_[848x480_H.264_AAC][859EEAFA]", "Chihiro", "859EEAFA")]
[TestCase("[Underwater]_Rinne_no_Lagrange_-_12_(720p)_[5C7BC4F9]", "Underwater", "5C7BC4F9")]
[TestCase("[HorribleSubs]_Hunter_X_Hunter_-_33_[720p]", "HorribleSubs", "")]
[TestCase("[HorribleSubs] Tonari no Kaibutsu-kun - 13 [1080p].mkv", "HorribleSubs", "")]
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].[C65D4B1F].mkv", "Doremi", "C65D4B1F")]
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].[C65D4B1F]", "Doremi", "C65D4B1F")]
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].mkv", "Doremi", "")]
[TestCase("[K-F] One Piece 214", "K-F", "")]
[TestCase("[K-F] One Piece S10E14 214", "K-F", "")]
[TestCase("[K-F] One Piece 10x14 214", "K-F", "")]
[TestCase("[K-F] One Piece 214 10x14", "K-F", "")]
[TestCase("Bleach - 031 - The Resolution to Kill [Lunar].avi", "Lunar", "")]
[TestCase("[ACX]Hack Sign 01 Role Play [Kosaka] [9C57891E].mkv", "ACX", "9C57891E")]
[TestCase("[S-T-D] Soul Eater Not! - 06 (1280x720 10bit AAC) [59B3F2EA].mkv", "S-T-D", "59B3F2EA")]
public void should_parse_absolute_numbers(string postTitle, string subGroup, string hash)
{
var result = Parser.Parser.ParseTitle(postTitle);
result.Should().NotBeNull();
result.ReleaseGroup.Should().Be(subGroup);
result.ReleaseHash.Should().Be(hash);
}
}
}

View File

@ -32,6 +32,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("0e895c37245186812cb08aab1529cf8ee389dd05.mkv")]
[TestCase("08bbc153931ce3ca5fcafe1b92d3297285feb061.mkv")]
[TestCase("185d86a343e39f3341e35c4dad3ff159")]
[TestCase("ah63jka93jf0jh26ahjas961.mkv")]
public void should_not_parse_crap(string title)
{
Parser.Parser.ParseTitle(title).Should().BeNull();

View File

@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.ParserTests
SeriesTitle = ""
};
parsedEpisodeInfo.IsPossibleSpecialEpisode().Should().BeFalse();
parsedEpisodeInfo.IsPossibleSpecialEpisode.Should().BeFalse();
}
[Test]
@ -33,7 +33,15 @@ namespace NzbDrone.Core.Test.ParserTests
SeriesTitle = ""
};
parsedEpisodeInfo.IsPossibleSpecialEpisode().Should().BeTrue();
parsedEpisodeInfo.IsPossibleSpecialEpisode.Should().BeTrue();
}
[TestCase("Under.the.Dome.S02.Special-Inside.Chesters.Mill.HDTV.x264-BAJSKORV")]
[TestCase("Under.the.Dome.S02.Special-Inside.Chesters.Mill.720p.HDTV.x264-BAJSKORV")]
[TestCase("Rookie.Blue.Behind.the.Badge.S05.Special.HDTV.x264-2HD")]
public void IsPossibleSpecialEpisode_should_be_true(string title)
{
Parser.Parser.ParseTitle(title).IsPossibleSpecialEpisode.Should().BeTrue();
}
}
}

View File

@ -52,6 +52,9 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Sonny.With.a.Chance.S02E15.divx", false)]
[TestCase("The.Girls.Next.Door.S03E06.HDTV-WiDE", false)]
[TestCase("Degrassi.S10E27.WS.DSR.XviD-2HD", false)]
[TestCase("[HorribleSubs] Yowamushi Pedal - 32 [480p]", false)]
[TestCase("[CR] Sailor Moon - 004 [480p][48CE2D0F]", false)]
[TestCase("[Hatsuyuki] Naruto Shippuuden - 363 [848x480][ADE35E38]", false)]
public void should_parse_sdtv_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.SDTV, proper);
@ -69,8 +72,10 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("The.Girls.Next.Door.S03E06.DVD.Rip.XviD-WiDE", false)]
[TestCase("the.shield.1x13.circles.ws.xvidvd-tns", false)]
[TestCase("the_x-files.9x18.sunshine_days.ac3.ws_dvdrip_xvid-fov.avi", false)]
[TestCase("[FroZen] Miyuki - 23 [DVD][7F6170E6]", false)]
[TestCase("Hannibal.S01E05.576p.BluRay.DD5.1.x264-HiSD", false)]
[TestCase("Hannibal.S01E05.480p.BluRay.DD5.1.x264-HiSD", false)]
[TestCase("Heidi Girl of the Alps (BD)(640x480(RAW) (BATCH 1) (1-13)", false)]
public void should_parse_dvd_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.DVD, proper);
@ -96,6 +101,13 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Sonny.With.a.Chance.S02E15.mkv", false)]
[TestCase(@"E:\Downloads\tv\The.Big.Bang.Theory.S01E01.720p.HDTV\ajifajjjeaeaeqwer_eppj.avi", false)]
[TestCase("Gem.Hunt.S01E08.Tourmaline.Nepal.720p.HDTV.x264-DHD", false)]
[TestCase("[Underwater-FFF] No Game No Life - 01 (720p) [27AAA0A0]", false)]
[TestCase("[Doki] Mahouka Koukou no Rettousei - 07 (1280x720 Hi10P AAC) [80AF7DDE]", false)]
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].[C65D4B1F].mkv", false)]
[TestCase("[HorribleSubs]_Fairy_Tail_-_145_[720p]", false)]
[TestCase("[Eveyuu] No Game No Life - 10 [Hi10P 1280x720 H264][10B23BD8]", false)]
[TestCase("Hells.Kitchen.US.S12E17.HR.WS.PDTV.X264-DIMENSION", false)]
[TestCase("Survivorman.The.Lost.Pilots.Summer.HR.WS.PDTV.x264-DHD", false)]
public void should_parse_hdtv720p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.HDTV720p, proper);
@ -106,6 +118,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("DEXTER.S07E01.ARE.YOU.1080P.HDTV.x264-QCF", false)]
[TestCase("DEXTER.S07E01.ARE.YOU.1080P.HDTV.proper.X264-QCF", true)]
[TestCase("Dexter - S01E01 - Title [HDTV-1080p]", false)]
[TestCase("[HorribleSubs] Yowamushi Pedal - 32 [1080p]", false)]
public void should_parse_hdtv1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.HDTV1080p, proper);
@ -144,6 +157,12 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Chuck - S01E03 - Come Fly With Me - 720p BluRay.mkv", false)]
[TestCase("The Big Bang Theory.S03E01.The Electric Can Opener Fluctuation.m2ts", false)]
[TestCase("Revolution.S01E02.Chained.Heat.[Bluray720p].mkv", false)]
[TestCase("[FFF] DATE A LIVE - 01 [BD][720p-AAC][0601BED4]", false)]
[TestCase("[coldhell] Pupa v3 [BD720p][03192D4C]", false)]
[TestCase("[RandomRemux] Nobunagun - 01 [720p BD][043EA407].mkv", false)]
[TestCase("[Kaylith] Isshuukan Friends Specials - 01 [BD 720p AAC][B7EEE164].mkv", false)]
[TestCase("WEEDS.S03E01-06.DUAL.Blu-ray.AC3.-HELLYWOOD.avi", false)]
[TestCase("WEEDS.S03E01-06.DUAL.720p.Blu-ray.AC3.-HELLYWOOD.avi", false)]
public void should_parse_bluray720p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.Bluray720p, proper);
@ -152,6 +171,12 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Chuck - S01E03 - Come Fly With Me - 1080p BluRay.mkv", false)]
[TestCase("Sons.Of.Anarchy.S02E13.1080p.BluRay.x264-AVCDVD", false)]
[TestCase("Revolution.S01E02.Chained.Heat.[Bluray1080p].mkv", false)]
[TestCase("[FFF] Namiuchigiwa no Muromi-san - 10 [BD][1080p-FLAC][0C4091AF]", false)]
[TestCase("[coldhell] Pupa v2 [BD1080p][5A45EABE].mkv", false)]
[TestCase("[Kaylith] Isshuukan Friends Specials - 01 [BD 1080p FLAC][429FD8C7].mkv", false)]
[TestCase("[Zurako] Log Horizon - 01 - The Apocalypse (BD 1080p AAC) [7AE12174].mkv", false)]
[TestCase("WEEDS.S03E01-06.DUAL.1080p.Blu-ray.AC3.-HELLYWOOD.avi", false)]
[TestCase("[Coalgirls]_Durarara!!_01_(1920x1080_Blu-ray_FLAC)_[8370CB8F].mkv", false)]
public void should_parse_bluray1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.Bluray1080p, proper);

View File

@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Eureka S 01 720p WEB DL DD 5 1 h264 TjHD", "Eureka", 1)]
[TestCase("Doctor Who Confidential Season 3", "Doctor Who Confidential", 3)]
[TestCase("Fleming.S01.720p.WEBDL.DD5.1.H.264-NTb", "Fleming", 1)]
public void should_parsefull_season_release(string postTitle, string title, int season)
public void should_parse_full_season_release(string postTitle, string title, int season)
{
var result = Parser.Parser.ParseTitle(postTitle);
result.SeasonNumber.Should().Be(season);

View File

@ -25,18 +25,6 @@ using System.Runtime.InteropServices;
[assembly: Guid("699aed1b-015e-4f0d-9c81-d5557b05d260")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("10.0.0.*")]
[assembly: AssemblyFileVersion("10.0.0.*")]
[assembly: InternalsVisibleTo("NzbDrone.Core")]

View File

@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
private void WithExpired()
{
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderGetLastWrite(It.IsAny<String>()))
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderGetLastWriteUtc(It.IsAny<String>()))
.Returns(DateTime.UtcNow.AddDays(-10));
Mocker.GetMock<IDiskProvider>().Setup(s => s.FileGetLastWriteUtc(It.IsAny<String>()))
@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
private void WithNonExpired()
{
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderGetLastWrite(It.IsAny<String>()))
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderGetLastWriteUtc(It.IsAny<String>()))
.Returns(DateTime.UtcNow.AddDays(-3));
Mocker.GetMock<IDiskProvider>().Setup(s => s.FileGetLastWriteUtc(It.IsAny<String>()))

View File

@ -23,13 +23,10 @@ namespace NzbDrone.Core.Test.Providers
{
var ids = Subject.GetXemSeriesIds();
ids.Should().NotBeEmpty();
ids.Should().Contain(i => i == 73141);
}
[Test]
[Ignore("XEM's data is not clean")]
public void get_mapping_for_all_series()
@ -45,7 +42,7 @@ namespace NzbDrone.Core.Test.Providers
}
[TestCase(12345, Description = "invalid id")]
[TestCase(267440, Description = "no single connection")]
[TestCase(279042, Description = "no single connection")]
public void should_return_empty_when_known_error(int id)
{
Subject.GetSceneTvdbMappings(id).Should().BeEmpty();
@ -62,7 +59,6 @@ namespace NzbDrone.Core.Test.Providers
result.Should().OnlyContain(c => c.Tvdb != null);
}
[TestCase(78916)]
public void should_filter_out_episodes_without_scene_mapping(int seriesId)
{

View File

@ -39,6 +39,11 @@ namespace NzbDrone.Core.Test.SeriesStatsTests
_episode.EpisodeFileId = 1;
}
private void GivenOldEpisode()
{
_episode.AirDateUtc = DateTime.Now.AddSeconds(-10);
}
private void GivenMonitoredEpisode()
{
_episode.Monitored = true;
@ -59,6 +64,7 @@ namespace NzbDrone.Core.Test.SeriesStatsTests
stats.Should().HaveCount(1);
stats.First().NextAiring.Should().Be(_episode.AirDateUtc);
stats.First().PreviousAiring.Should().NotHaveValue();
}
[Test]
@ -73,6 +79,47 @@ namespace NzbDrone.Core.Test.SeriesStatsTests
stats.First().NextAiring.Should().NotHaveValue();
}
[Test]
public void should_have_previous_airing_for_old_episode_with_file()
{
GivenEpisodeWithFile();
GivenOldEpisode();
GivenFile();
var stats = Subject.SeriesStatistics();
stats.Should().HaveCount(1);
stats.First().NextAiring.Should().NotHaveValue();
stats.First().PreviousAiring.Should().Be(_episode.AirDateUtc);
}
[Test]
public void should_have_previous_airing_for_old_episode_without_file_monitored()
{
GivenMonitoredEpisode();
GivenOldEpisode();
GivenFile();
var stats = Subject.SeriesStatistics();
stats.Should().HaveCount(1);
stats.First().NextAiring.Should().NotHaveValue();
stats.First().PreviousAiring.Should().Be(_episode.AirDateUtc);
}
[Test]
public void should_not_have_previous_airing_for_old_episode_without_file_unmonitored()
{
GivenOldEpisode();
GivenFile();
var stats = Subject.SeriesStatistics();
stats.Should().HaveCount(1);
stats.First().NextAiring.Should().NotHaveValue();
stats.First().PreviousAiring.Should().NotHaveValue();
}
[Test]
public void should_not_include_unmonitored_episode_in_episode_count()
{

View File

@ -6,6 +6,7 @@ using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.Tvdb;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
@ -42,6 +43,15 @@ namespace NzbDrone.Core.Test.TvTests
return series;
}
private Series GetAnimeSeries()
{
var series = Builder<Series>.CreateNew().Build();
series.SeriesType = SeriesTypes.Anime;
series.Seasons = new List<Season>();
return series;
}
[SetUp]
public void Setup()
{
@ -61,11 +71,18 @@ namespace NzbDrone.Core.Test.TvTests
.Callback<List<Episode>>(e => _deletedEpisodes = e);
}
private void GivenAnimeEpisodes(List<Episode> episodes)
{
Mocker.GetMock<ITvdbProxy>()
.Setup(s => s.GetEpisodeInfo(It.IsAny<Int32>()))
.Returns(episodes);
}
[Test]
public void should_create_all_when_no_existing_episodes()
{
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(new List<Episode>());
Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes());
@ -78,7 +95,7 @@ namespace NzbDrone.Core.Test.TvTests
[Test]
public void should_update_all_when_all_existing_episodes()
{
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(GetEpisodes());
Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes());
@ -91,7 +108,7 @@ namespace NzbDrone.Core.Test.TvTests
[Test]
public void should_delete_all_when_all_existing_episodes_are_gone_from_trakt()
{
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(GetEpisodes());
Subject.RefreshEpisodeInfo(GetSeries(), new List<Episode>());
@ -106,7 +123,7 @@ namespace NzbDrone.Core.Test.TvTests
{
var duplicateEpisodes = GetEpisodes().Skip(5).Take(2).ToList();
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(GetEpisodes().Union(duplicateEpisodes).ToList());
Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes());
@ -127,7 +144,7 @@ namespace NzbDrone.Core.Test.TvTests
episodes.ForEach(e => e.Monitored = true);
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(episodes);
Subject.RefreshEpisodeInfo(series, GetEpisodes());
@ -139,7 +156,7 @@ namespace NzbDrone.Core.Test.TvTests
[Test]
public void should_remove_duplicate_remote_episodes_before_processing()
{
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(new List<Episode>());
var episodes = Builder<Episode>.CreateListOfSize(5)
@ -157,21 +174,137 @@ namespace NzbDrone.Core.Test.TvTests
}
[Test]
public void should_set_absolute_episode_number()
public void should_not_set_absolute_episode_number_for_non_anime()
{
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
.Returns(new List<Episode>());
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(new List<Episode>());
Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes());
var season1 = _insertedEpisodes.Where(e => e.SeasonNumber == 1 && e.EpisodeNumber > 0);
var season2episode1 = _insertedEpisodes.Single(e => e.SeasonNumber == 2 && e.EpisodeNumber == 1);
_insertedEpisodes.All(e => e.AbsoluteEpisodeNumber == 0 || !e.AbsoluteEpisodeNumber.HasValue).Should().BeTrue();
}
season2episode1.AbsoluteEpisodeNumber.Should().Be(season1.Count() + 1);
[Test]
public void should_set_absolute_episode_number_for_anime()
{
var episodes = Builder<Episode>.CreateListOfSize(3).Build().ToList();
GivenAnimeEpisodes(episodes);
_insertedEpisodes.Where(e => e.SeasonNumber > 0 && e.EpisodeNumber > 0).All(e => e.AbsoluteEpisodeNumber > 0).Should().BeTrue();
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(new List<Episode>());
Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes);
_insertedEpisodes.All(e => e.AbsoluteEpisodeNumber > 0).Should().BeTrue();
_updatedEpisodes.Should().BeEmpty();
_deletedEpisodes.Should().BeEmpty();
}
[Test]
public void should_set_absolute_episode_number_even_if_not_previously_set_for_anime()
{
var episodes = Builder<Episode>.CreateListOfSize(3).Build().ToList();
GivenAnimeEpisodes(episodes);
var existingEpisodes = episodes.JsonClone();
existingEpisodes.ForEach(e => e.AbsoluteEpisodeNumber = 0);
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(existingEpisodes);
Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes);
_insertedEpisodes.Should().BeEmpty();
_updatedEpisodes.All(e => e.AbsoluteEpisodeNumber > 0).Should().BeTrue();
_deletedEpisodes.Should().BeEmpty();
}
[Test]
public void should_get_new_season_and_episode_numbers_when_absolute_episode_number_match_found()
{
const Int32 expectedSeasonNumber = 10;
const Int32 expectedEpisodeNumber = 5;
const Int32 expectedAbsoluteNumber = 3;
var episode = Builder<Episode>.CreateNew()
.With(e => e.SeasonNumber = expectedSeasonNumber)
.With(e => e.EpisodeNumber = expectedEpisodeNumber)
.With(e => e.AbsoluteEpisodeNumber = expectedAbsoluteNumber)
.Build();
GivenAnimeEpisodes(new List<Episode> { episode });
var existingEpisode = episode.JsonClone();
existingEpisode.SeasonNumber = 1;
existingEpisode.EpisodeNumber = 1;
existingEpisode.AbsoluteEpisodeNumber = expectedAbsoluteNumber;
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(new List<Episode>{ existingEpisode });
Subject.RefreshEpisodeInfo(GetAnimeSeries(), new List<Episode> { episode });
_insertedEpisodes.Should().BeEmpty();
_deletedEpisodes.Should().BeEmpty();
_updatedEpisodes.First().SeasonNumber.Should().Be(expectedSeasonNumber);
_updatedEpisodes.First().EpisodeNumber.Should().Be(expectedEpisodeNumber);
_updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(expectedAbsoluteNumber);
}
[Test]
public void should_prefer_absolute_match_over_season_and_epsiode_match()
{
var episodes = Builder<Episode>.CreateListOfSize(2)
.Build()
.ToList();
episodes[0].AbsoluteEpisodeNumber = 0;
episodes[0].SeasonNumber.Should().NotBe(episodes[1].SeasonNumber);
episodes[0].EpisodeNumber.Should().NotBe(episodes[1].EpisodeNumber);
episodes[0].AbsoluteEpisodeNumber.Should().NotBe(episodes[1].AbsoluteEpisodeNumber);
GivenAnimeEpisodes(episodes);
var existingEpisode = new Episode
{
SeasonNumber = episodes[0].SeasonNumber,
EpisodeNumber = episodes[0].EpisodeNumber,
AbsoluteEpisodeNumber = episodes[1].AbsoluteEpisodeNumber
};
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(new List<Episode> { existingEpisode });
Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes);
_updatedEpisodes.First().SeasonNumber.Should().Be(episodes[1].SeasonNumber);
_updatedEpisodes.First().EpisodeNumber.Should().Be(episodes[1].EpisodeNumber);
_updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(episodes[1].AbsoluteEpisodeNumber);
}
[Test]
public void should_ignore_episodes_with_absolute_episode_of_zero_in_distinct_by_absolute()
{
var episodes = Builder<Episode>.CreateListOfSize(10)
.Build()
.ToList();
episodes[0].AbsoluteEpisodeNumber = 0;
episodes[1].AbsoluteEpisodeNumber = 0;
episodes[2].AbsoluteEpisodeNumber = 0;
episodes[3].AbsoluteEpisodeNumber = 0;
episodes[4].AbsoluteEpisodeNumber = 0;
GivenAnimeEpisodes(episodes);
Mocker.GetMock<IEpisodeService>().Setup(c => c.GetEpisodeBySeries(It.IsAny<Int32>()))
.Returns(new List<Episode>());
Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes);
_insertedEpisodes.Should().HaveCount(episodes.Count);
}
}
}

View File

@ -49,12 +49,12 @@ namespace NzbDrone.Core.Test.TvTests
[Test]
public void should_monitor_new_seasons_automatically()
{
var series = _series.JsonClone();
series.Seasons.Add(Builder<Season>.CreateNew()
var newSeriesInfo = _series.JsonClone();
newSeriesInfo.Seasons.Add(Builder<Season>.CreateNew()
.With(s => s.SeasonNumber = 2)
.Build());
GivenNewSeriesInfo(series);
GivenNewSeriesInfo(newSeriesInfo);
Subject.Execute(new RefreshSeriesCommand(_series.Id));
@ -77,5 +77,19 @@ namespace NzbDrone.Core.Test.TvTests
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.Is<Series>(s => s.Seasons.Count == 2 && s.Seasons.Single(season => season.SeasonNumber == 0).Monitored == false)));
}
[Test]
public void should_update_tvrage_id_if_changed()
{
var newSeriesInfo = _series.JsonClone();
newSeriesInfo.TvRageId = _series.TvRageId + 1;
GivenNewSeriesInfo(newSeriesInfo);
Subject.Execute(new RefreshSeriesCommand(_series.Id));
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.Is<Series>(s => s.TvRageId == newSeriesInfo.TvRageId)));
}
}
}

View File

@ -10,11 +10,12 @@ namespace NzbDrone.Core.Annotations
Order = order;
}
public int Order { get; private set; }
public string Label { get; set; }
public string HelpText { get; set; }
public string HelpLink { get; set; }
public Int32 Order { get; private set; }
public String Label { get; set; }
public String HelpText { get; set; }
public String HelpLink { get; set; }
public FieldType Type { get; set; }
public Boolean Advanced { get; set; }
public Type SelectOptions { get; set; }
}

View File

@ -0,0 +1,11 @@
using System;
namespace NzbDrone.Core.Backup
{
public class Backup
{
public String Path { get; set; }
public BackupType Type { get; set; }
public DateTime Time { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Backup
{
public class BackupCommand : Command
{
public BackupType Type { get; set; }
public override bool SendUpdatesToClient
{
get
{
return true;
}
}
}
public enum BackupType
{
Scheduled = 0 ,
Manual = 1,
Update = 2
}
}

View File

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Marr.Data;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Instrumentation.Extensions;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Backup
{
public interface IBackupService
{
void Backup(BackupType backupType);
List<Backup> GetBackups();
}
public class BackupService : IBackupService, IExecute<BackupCommand>
{
private readonly IDatabase _maindDb;
private readonly IDiskProvider _diskProvider;
private readonly IAppFolderInfo _appFolderInfo;
private readonly IArchiveService _archiveService;
private readonly Logger _logger;
private string _backupTempFolder;
private static readonly Regex BackupFileRegex = new Regex(@"nzbdrone_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public BackupService(IDatabase maindDb,
IDiskProvider diskProvider,
IAppFolderInfo appFolderInfo,
IArchiveService archiveService,
Logger logger)
{
_maindDb = maindDb;
_diskProvider = diskProvider;
_appFolderInfo = appFolderInfo;
_archiveService = archiveService;
_logger = logger;
_backupTempFolder = Path.Combine(_appFolderInfo.TempFolder, "nzbdrone_backup");
}
public void Backup(BackupType backupType)
{
_logger.ProgressInfo("Starting Backup");
_diskProvider.EnsureFolder(_backupTempFolder);
_diskProvider.EnsureFolder(GetBackupFolder(backupType));
var backupFilename = String.Format("nzbdrone_backup_{0:yyyy.MM.dd_HH.mm.ss}.zip", DateTime.Now);
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
Cleanup();
if (backupType != BackupType.Manual)
{
CleanupOldBackups(backupType);
}
BackupConfigFile();
BackupDatabase();
_logger.ProgressDebug("Creating backup zip");
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
_logger.ProgressDebug("Backup zip created");
}
public List<Backup> GetBackups()
{
var backups = new List<Backup>();
foreach (var backupType in Enum.GetValues(typeof(BackupType)).Cast<BackupType>())
{
var folder = GetBackupFolder(backupType);
if (_diskProvider.FolderExists(folder))
{
backups.AddRange(GetBackupFiles(folder).Select(b => new Backup
{
Path = Path.GetFileName(b),
Type = backupType,
Time = _diskProvider.FileGetLastWriteUtc(b)
}));
}
}
return backups;
}
private void Cleanup()
{
if (_diskProvider.FolderExists(_backupTempFolder))
{
_diskProvider.EmptyFolder(_backupTempFolder);
}
}
private void BackupDatabase()
{
_logger.ProgressDebug("Backing up database");
using (var unitOfWork = new UnitOfWork(() => _maindDb.GetDataMapper()))
{
unitOfWork.BeginTransaction();
var databaseFile = _appFolderInfo.GetNzbDroneDatabase();
var tempDatabaseFile = Path.Combine(_backupTempFolder, Path.GetFileName(databaseFile));
_diskProvider.CopyFile(databaseFile, tempDatabaseFile, true);
unitOfWork.Commit();
}
}
private void BackupConfigFile()
{
_logger.ProgressDebug("Backing up config.xml");
var configFile = _appFolderInfo.GetConfigPath();
var tempConfigFile = Path.Combine(_backupTempFolder, Path.GetFileName(configFile));
_diskProvider.CopyFile(configFile, tempConfigFile, true);
}
private void CleanupOldBackups(BackupType backupType)
{
_logger.Debug("Cleaning up old backup files");
var files = GetBackupFiles(GetBackupFolder(backupType));
foreach (var file in files)
{
var lastWriteTime = _diskProvider.FileGetLastWriteUtc(file);
if (lastWriteTime.AddDays(28) < DateTime.UtcNow)
{
_logger.Debug("Deleting old backup file: {0}", file);
_diskProvider.DeleteFile(file);
}
}
_logger.Debug("Finished cleaning up old backup files");
}
private String GetBackupFolder(BackupType backupType)
{
return Path.Combine(_appFolderInfo.GetBackupFolder(), backupType.ToString().ToLower());
}
private IEnumerable<String> GetBackupFiles(String path)
{
var files = _diskProvider.GetFiles(path, SearchOption.TopDirectoryOnly);
return files.Where(f => BackupFileRegex.IsMatch(f));
}
public void Execute(BackupCommand message)
{
Backup(message.Type);
}
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace NzbDrone.Core.DataAugmentation.Scene
{
public interface ISceneMappingProvider
{
List<SceneMapping> GetSceneMappings();
}
}

View File

@ -16,5 +16,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene
[JsonProperty("season")]
public int SeasonNumber { get; set; }
public string Type { get; set; }
}
}

View File

@ -1,3 +1,4 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using System.Collections.Generic;
@ -8,6 +9,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene
public interface ISceneMappingRepository : IBasicRepository<SceneMapping>
{
List<SceneMapping> FindByTvdbid(int tvdbId);
void Clear(string type);
}
public class SceneMappingRepository : BasicRepository<SceneMapping>, ISceneMappingRepository
@ -21,5 +23,10 @@ namespace NzbDrone.Core.DataAugmentation.Scene
{
return Query.Where(x => x.TvdbId == tvdbId);
}
public void Clear(string type)
{
Delete(s => s.Type == type);
}
}
}

View File

@ -1,56 +1,66 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using System.Collections.Generic;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.DataAugmentation.Scene
{
public interface ISceneMappingService
{
string GetSceneName(int tvdbId);
Nullable<int> GetTvDbId(string cleanName);
List<String> GetSceneNames(int tvdbId, IEnumerable<Int32> seasonNumbers);
Nullable<int> GetTvDbId(string title);
List<SceneMapping> FindByTvdbid(int tvdbId);
Nullable<Int32> GetSeasonNumber(string title);
}
public class SceneMappingService : ISceneMappingService,
IHandleAsync<ApplicationStartedEvent>,
IExecute<UpdateSceneMappingCommand>
IHandleAsync<ApplicationStartedEvent>,
IHandle<SeriesRefreshStartingEvent>,
IExecute<UpdateSceneMappingCommand>
{
private readonly ISceneMappingRepository _repository;
private readonly ISceneMappingProxy _sceneMappingProxy;
private readonly IEnumerable<ISceneMappingProvider> _sceneMappingProviders;
private readonly Logger _logger;
private readonly ICached<SceneMapping> _getSceneNameCache;
private readonly ICached<SceneMapping> _gettvdbIdCache;
private readonly ICached<List<SceneMapping>> _findbytvdbIdCache;
public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, ICacheManager cacheManager, Logger logger)
public SceneMappingService(ISceneMappingRepository repository,
ICacheManager cacheManager,
IEnumerable<ISceneMappingProvider> sceneMappingProviders,
Logger logger)
{
_repository = repository;
_sceneMappingProxy = sceneMappingProxy;
_sceneMappingProviders = sceneMappingProviders;
_getSceneNameCache = cacheManager.GetCache<SceneMapping>(GetType(), "scene_name");
_gettvdbIdCache = cacheManager.GetCache<SceneMapping>(GetType(), "tvdb_id");
_findbytvdbIdCache = cacheManager.GetCache<List<SceneMapping>>(GetType(), "find_tvdb_id");
_logger = logger;
}
public string GetSceneName(int tvdbId)
public List<String> GetSceneNames(int tvdbId, IEnumerable<Int32> seasonNumbers)
{
var mapping = _getSceneNameCache.Find(tvdbId.ToString());
var names = _findbytvdbIdCache.Find(tvdbId.ToString());
if (mapping == null) return null;
if (names == null)
{
return new List<String>();
}
return mapping.SearchTerm;
return FilterNonEnglish(names.Where(s => seasonNumbers.Contains(s.SeasonNumber) ||
s.SeasonNumber == -1)
.Select(m => m.SearchTerm).Distinct().ToList());
}
public Nullable<Int32> GetTvDbId(string cleanName)
public Nullable<Int32> GetTvDbId(string title)
{
var mapping = _gettvdbIdCache.Find(cleanName.CleanSeriesTitle());
var mapping = _gettvdbIdCache.Find(title.CleanSeriesTitle());
if (mapping == null)
return null;
@ -60,66 +70,95 @@ namespace NzbDrone.Core.DataAugmentation.Scene
public List<SceneMapping> FindByTvdbid(int tvdbId)
{
return _findbytvdbIdCache.Find(tvdbId.ToString());
var mappings = _findbytvdbIdCache.Find(tvdbId.ToString());
if (mappings == null)
{
return new List<SceneMapping>();
}
return mappings;
}
public Nullable<Int32> GetSeasonNumber(string title)
{
var mapping = _gettvdbIdCache.Find(title.CleanSeriesTitle());
if (mapping == null)
return null;
return mapping.SeasonNumber;
}
private void UpdateMappings()
{
_logger.Info("Updating Scene mapping");
_logger.Info("Updating Scene mappings");
try
foreach (var sceneMappingProvider in _sceneMappingProviders)
{
var mappings = _sceneMappingProxy.Fetch();
if (mappings.Any())
try
{
_repository.Purge();
var mappings = sceneMappingProvider.GetSceneMappings();
foreach (var sceneMapping in mappings)
if (mappings.Any())
{
sceneMapping.ParseTerm = sceneMapping.Title.CleanSeriesTitle();
_repository.Clear(sceneMappingProvider.GetType().Name);
foreach (var sceneMapping in mappings)
{
sceneMapping.ParseTerm = sceneMapping.Title.CleanSeriesTitle();
sceneMapping.Type = sceneMappingProvider.GetType().Name;
}
_repository.InsertMany(mappings.DistinctBy(s => s.ParseTerm).ToList());
}
else
{
_logger.Warn("Received empty list of mapping. will not update.");
}
_repository.InsertMany(mappings);
}
else
catch (Exception ex)
{
_logger.Warn("Received empty list of mapping. will not update.");
_logger.ErrorException("Failed to Update Scene Mappings:", ex);
}
}
catch (Exception ex)
{
_logger.ErrorException("Failed to Update Scene Mappings:", ex);
}
RefreshCache();
}
private void RefreshCache()
{
var mappings = _repository.All();
var mappings = _repository.All().ToList();
_gettvdbIdCache.Clear();
_getSceneNameCache.Clear();
_findbytvdbIdCache.Clear();
foreach (var sceneMapping in mappings)
{
_getSceneNameCache.Set(sceneMapping.TvdbId.ToString(), sceneMapping);
_gettvdbIdCache.Set(sceneMapping.ParseTerm.CleanSeriesTitle(), sceneMapping);
}
foreach (var sceneMapping in mappings.GroupBy(x => x.TvdbId))
{
_findbytvdbIdCache.Set(sceneMapping.Key.ToString(), sceneMapping.ToList());
}
}
private List<String> FilterNonEnglish(List<String> titles)
{
return titles.Where(title => title.All(c => c <= 255)).ToList();
}
public void HandleAsync(ApplicationStartedEvent message)
{
UpdateMappings();
}
public void Handle(SeriesRefreshStartingEvent message)
{
UpdateMappings();
}
public void Execute(UpdateSceneMappingCommand message)
{
UpdateMappings();

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.DataAugmentation.Scene
{
public class ServicesProvider : ISceneMappingProvider
{
private readonly ISceneMappingProxy _sceneMappingProxy;
public ServicesProvider(ISceneMappingProxy sceneMappingProxy)
{
_sceneMappingProxy = sceneMappingProxy;
}
public List<SceneMapping> GetSceneMappings()
{
return _sceneMappingProxy.Fetch();
}
}
}

View File

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.DataAugmentation.Xem.Model;
using NzbDrone.Core.Rest;
using RestSharp;
@ -12,6 +14,7 @@ namespace NzbDrone.Core.DataAugmentation.Xem
{
List<int> GetXemSeriesIds();
List<XemSceneTvdbMapping> GetSceneTvdbMappings(int id);
List<SceneMapping> GetSceneTvdbNames();
}
public class XemProxy : IXemProxy
@ -40,7 +43,7 @@ namespace NzbDrone.Core.DataAugmentation.Xem
{
_logger.Debug("Fetching Series IDs from");
var restClient = new RestClient(XEM_BASE_URL);
var restClient = RestClientFactory.BuildClient(XEM_BASE_URL);
var request = BuildRequest("havemap");
@ -54,7 +57,7 @@ namespace NzbDrone.Core.DataAugmentation.Xem
{
_logger.Debug("Fetching Mappings for: {0}", id);
var restClient = new RestClient(XEM_BASE_URL);
var restClient = RestClientFactory.BuildClient(XEM_BASE_URL);
var request = BuildRequest("all");
request.AddParameter("id", id);
@ -65,6 +68,52 @@ namespace NzbDrone.Core.DataAugmentation.Xem
return response.Data.Where(c => c.Scene != null).ToList();
}
public List<SceneMapping> GetSceneTvdbNames()
{
_logger.Debug("Fetching alternate names");
var restClient = RestClientFactory.BuildClient(XEM_BASE_URL);
var request = BuildRequest("allNames");
request.AddParameter("origin", "tvdb");
request.AddParameter("seasonNumbers", true);
var response = restClient.ExecuteAndValidate<XemResult<Dictionary<Int32, List<JObject>>>>(request);
CheckForFailureResult(response);
var result = new List<SceneMapping>();
foreach (var series in response.Data)
{
foreach (var name in series.Value)
{
foreach (var n in name)
{
int seasonNumber;
if (!Int32.TryParse(n.Value.ToString(), out seasonNumber))
{
continue;
}
//hack to deal with Fate/Zero
if (series.Key == 79151 && seasonNumber > 1)
{
continue;
}
result.Add(new SceneMapping
{
Title = n.Key,
SearchTerm = n.Key,
SeasonNumber = seasonNumber,
TvdbId = series.Key
});
}
}
}
return result;
}
private static void CheckForFailureResult<T>(XemResult<T> response)
{
if (response.Result.Equals("failure", StringComparison.InvariantCultureIgnoreCase) &&
@ -73,7 +122,5 @@ namespace NzbDrone.Core.DataAugmentation.Xem
throw new Exception("Error response received from Xem: " + response.Message);
}
}
}
}

View File

@ -1,14 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.DataAugmentation.Xem
{
public class XemService : IHandle<SeriesUpdatedEvent>, IHandle<SeriesRefreshStartingEvent>
public class XemService : ISceneMappingProvider, IHandle<SeriesUpdatedEvent>, IHandle<SeriesRefreshStartingEvent>
{
private readonly IEpisodeService _episodeService;
private readonly IXemProxy _xemProxy;
@ -47,7 +49,7 @@ namespace NzbDrone.Core.DataAugmentation.Xem
foreach (var episode in episodes)
{
episode.AbsoluteEpisodeNumber = 0;
episode.SceneAbsoluteEpisodeNumber = 0;
episode.SceneSeasonNumber = 0;
episode.SceneEpisodeNumber = 0;
}
@ -64,7 +66,7 @@ namespace NzbDrone.Core.DataAugmentation.Xem
continue;
}
episode.AbsoluteEpisodeNumber = mapping.Scene.Absolute;
episode.SceneAbsoluteEpisodeNumber = mapping.Scene.Absolute;
episode.SceneSeasonNumber = mapping.Scene.Season;
episode.SceneEpisodeNumber = mapping.Scene.Episode;
}
@ -96,6 +98,24 @@ namespace NzbDrone.Core.DataAugmentation.Xem
}
}
public List<SceneMapping> GetSceneMappings()
{
var mappings = _xemProxy.GetSceneTvdbNames();
return mappings.Where(m =>
{
int id;
if (Int32.TryParse(m.Title, out id))
{
_logger.Debug("Skipping all numeric name: {0} for {1}", m.Title, m.TvdbId);
return false;
}
return true;
}).ToList();
}
public void Handle(SeriesUpdatedEvent message)
{
if (_cache.Count == 0)

View File

@ -1,8 +1,4 @@
using NzbDrone.Core.Datastore.Migration.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentMigrator;
namespace NzbDrone.Core.Datastore.Migration

View File

@ -0,0 +1,20 @@
using NzbDrone.Core.Datastore.Migration.Framework;
using FluentMigrator;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(52)]
public class add_columns_for_anime : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
//Support XEM names
Alter.Table("SceneMappings").AddColumn("Type").AsString().Nullable();
Execute.Sql("DELETE FROM SceneMappings");
//Add AnimeEpisodeFormat (set to Stardard Episode format for now)
Alter.Table("NamingConfig").AddColumn("AnimeEpisodeFormat").AsString().Nullable();
Execute.Sql("UPDATE NamingConfig SET AnimeEpisodeFormat = StandardEpisodeFormat");
}
}
}

View File

@ -0,0 +1,46 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(53)]
public class add_series_sorttitle : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Create.Column("SortTitle").OnTable("Series").AsString().Nullable();
Execute.WithConnection(SetSortTitles);
}
private void SetSortTitles(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand getSeriesCmd = conn.CreateCommand())
{
getSeriesCmd.Transaction = tran;
getSeriesCmd.CommandText = @"SELECT Id, Title FROM Series";
using (IDataReader seriesReader = getSeriesCmd.ExecuteReader())
{
while (seriesReader.Read())
{
var id = seriesReader.GetInt32(0);
var title = seriesReader.GetString(1);
var sortTitle = Parser.Parser.NormalizeEpisodeTitle(title).ToLower();
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE Series SET SortTitle = ? WHERE Id = ?";
updateCmd.AddParameter(sortTitle);
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}

View File

@ -2,12 +2,14 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Instrumentation.Extensions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine
{
@ -63,16 +65,17 @@ namespace NzbDrone.Core.DecisionEngine
{
var parsedEpisodeInfo = Parser.Parser.ParseTitle(report.Title);
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode())
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
{
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(report.Title, report.TvRageId, searchCriteria);
if (specialEpisodeInfo != null)
{
parsedEpisodeInfo = specialEpisodeInfo;
}
}
if (parsedEpisodeInfo != null && !string.IsNullOrWhiteSpace(parsedEpisodeInfo.SeriesTitle))
if (parsedEpisodeInfo != null && !parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace())
{
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvRageId, searchCriteria);
remoteEpisode.Release = report;

View File

@ -0,0 +1,31 @@
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.DecisionEngine.Specifications;
namespace NzbDrone.Core.DecisionEngine
{
public interface IPrioritizeDownloadDecision
{
List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions);
}
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
{
public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions)
{
return decisions
.Where(c => c.RemoteEpisode.Series != null)
.GroupBy(c => c.RemoteEpisode.Series.Id, (i, s) => s
.OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.QualityProfile))
.ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault())
.ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count)
.ThenBy(c => c.RemoteEpisode.Release.Age))
.SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteEpisode.Series == null))
.ToList();
}
}
}

Some files were not shown because too many files have changed in this diff Show More