diff --git a/Gruntfile.js b/Gruntfile.js index f78680b09..d78fac4da 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -52,9 +52,10 @@ module.exports = function (grunt) { options:{ dumpLineNumbers : 'false', - compress : false, + compress : true, yuicompress : false, - ieCompat : false + ieCompat : true, + strictImports : true }, bootstrap: { diff --git a/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs b/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs index f68bcb2ef..bf3d72d11 100644 --- a/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs +++ b/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs @@ -28,8 +28,8 @@ namespace NzbDrone.Api.Test.ClientSchemaTests var schema = SchemaBuilder.GenerateSchema(model); - schema.Should().Contain(c => c.Order == 1 && c.Name == "LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && c.Value == "Poop"); - schema.Should().Contain(c => c.Order == 0 && c.Name == "FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && c.Value == "Bob"); + schema.Should().Contain(c => c.Order == 1 && c.Name == "LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string) c.Value == "Poop"); + schema.Should().Contain(c => c.Order == 0 && c.Name == "FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string) c.Value == "Bob"); } } diff --git a/NzbDrone.Api.Test/DirectoryLookupServiceFixture.cs b/NzbDrone.Api.Test/DirectoryLookupServiceFixture.cs index 7399a8f82..849773ddb 100644 --- a/NzbDrone.Api.Test/DirectoryLookupServiceFixture.cs +++ b/NzbDrone.Api.Test/DirectoryLookupServiceFixture.cs @@ -37,9 +37,6 @@ namespace NzbDrone.Api.Test "Windows" }; - Mocker.GetMock<IDiskProvider>() - .SetupGet(s => s.SpecialFolders) - .Returns(new HashSet<string> { "$recycle.bin", "system volume information", "recycler" }); } private void SetupFolders(string root) diff --git a/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs b/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs index 2aaf97b79..82fb9425a 100644 --- a/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs +++ b/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs @@ -4,6 +4,7 @@ using FizzWare.NBuilder; using FluentAssertions; using Marr.Data; using NUnit.Framework; +using NzbDrone.Api.Commands; using NzbDrone.Api.Config; using NzbDrone.Api.Episodes; using NzbDrone.Api.History; @@ -13,16 +14,16 @@ using NzbDrone.Api.Mapping; using NzbDrone.Api.Qualities; using NzbDrone.Api.RootFolders; using NzbDrone.Api.Series; -using NzbDrone.Api.Update; -using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Indexers; using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.RootFolders; using NzbDrone.Core.Tv; -using NzbDrone.Core.Update; +using NzbDrone.Core.Update.Commands; using NzbDrone.Test.Common; using System.Linq; @@ -36,13 +37,13 @@ namespace NzbDrone.Api.Test.MappingTests [TestCase(typeof(RootFolder), typeof(RootFolderResource))] [TestCase(typeof(NamingConfig), typeof(NamingConfigResource))] [TestCase(typeof(Indexer), typeof(IndexerResource))] - [TestCase(typeof(ReportInfo), typeof(ReleaseResource))] + [TestCase(typeof(ReleaseInfo), typeof(ReleaseResource))] [TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))] [TestCase(typeof(DownloadDecision), typeof(ReleaseResource))] [TestCase(typeof(Core.History.History), typeof(HistoryResource))] - [TestCase(typeof(UpdatePackage), typeof(UpdateResource))] [TestCase(typeof(Quality), typeof(QualityResource))] [TestCase(typeof(Log), typeof(LogResource))] + [TestCase(typeof(Command), typeof(CommandResource))] public void matching_fields(Type modelType, Type resourceType) { MappingValidation.ValidateMapping(modelType, resourceType); @@ -116,6 +117,15 @@ namespace NzbDrone.Api.Test.MappingTests profileResource.InjectTo<QualityProfile>(); } + + + + [Test] + public void should_map_tracked_command() + { + var profileResource = new ApplicationUpdateCommand(); + profileResource.InjectTo<CommandResource>(); + } } public class ModelWithLazy diff --git a/NzbDrone.Api/Commands/CommandModule.cs b/NzbDrone.Api/Commands/CommandModule.cs index 371483cc3..231089734 100644 --- a/NzbDrone.Api/Commands/CommandModule.cs +++ b/NzbDrone.Api/Commands/CommandModule.cs @@ -1,37 +1,69 @@ using System; +using System.Collections.Generic; using System.Linq; -using Nancy; using NzbDrone.Api.Extensions; +using NzbDrone.Api.Mapping; +using NzbDrone.Api.Validation; using NzbDrone.Common.Composition; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Commands.Tracking; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.ProgressMessaging; + namespace NzbDrone.Api.Commands { - public class CommandModule : NzbDroneRestModule<CommandResource> + public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, Command>, IHandle<CommandUpdatedEvent> { - private readonly IMessageAggregator _messageAggregator; + private readonly ICommandExecutor _commandExecutor; private readonly IContainer _container; + private readonly ITrackCommands _trackCommands; - public CommandModule(IMessageAggregator messageAggregator, IContainer container) + public CommandModule(ICommandExecutor commandExecutor, IContainer container, ITrackCommands trackCommands) + : base(commandExecutor) { - _messageAggregator = messageAggregator; + _commandExecutor = commandExecutor; _container = container; + _trackCommands = trackCommands; - Post["/"] = x => RunCommand(ReadResourceFromRequest()); + GetResourceById = GetCommand; + CreateResource = StartCommand; + GetResourceAll = GetAllCommands; + PostValidator.RuleFor(c => c.Name).NotBlank(); } - private Response RunCommand(CommandResource resource) + private CommandResource GetCommand(int id) + { + return _trackCommands.GetById(id).InjectTo<CommandResource>(); + } + + private int StartCommand(CommandResource commandResource) { var commandType = - _container.GetImplementations(typeof(ICommand)) - .Single(c => c.Name.Replace("Command", "") - .Equals(resource.Command, StringComparison.InvariantCultureIgnoreCase)); + _container.GetImplementations(typeof(Command)) + .Single(c => c.Name.Replace("Command", "") + .Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase)); dynamic command = Request.Body.FromJson(commandType); - _messageAggregator.PublishCommand(command); - return resource.AsResponse(HttpStatusCode.Created); + var trackedCommand = (Command)_commandExecutor.PublishCommandAsync(command); + return trackedCommand.Id; + } + + private List<CommandResource> GetAllCommands() + { + return ToListResource(_trackCommands.RunningCommands); + } + + public void Handle(CommandUpdatedEvent message) + { + if (message.Command.SendUpdatesToClient) + { + BroadcastResourceChange(ModelAction.Updated, message.Command.Id); + } } } } \ No newline at end of file diff --git a/NzbDrone.Api/Commands/CommandResource.cs b/NzbDrone.Api/Commands/CommandResource.cs index e71a0d08f..86c1f4b15 100644 --- a/NzbDrone.Api/Commands/CommandResource.cs +++ b/NzbDrone.Api/Commands/CommandResource.cs @@ -1,9 +1,16 @@ -using NzbDrone.Api.REST; +using System; +using NzbDrone.Api.REST; +using NzbDrone.Core.Messaging.Commands.Tracking; namespace NzbDrone.Api.Commands { public class CommandResource : RestResource { - public string Command { get; set; } + public String Name { get; set; } + public String Message { get; set; } + public DateTime StartedOn { get; set; } + public DateTime StateChangeTime { get; set; } + public Boolean SendUpdatesToClient { get; set; } + public CommandStatus State { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Api/Episodes/EpisodeConnection.cs b/NzbDrone.Api/Episodes/EpisodeConnection.cs deleted file mode 100644 index 814c6dc84..000000000 --- a/NzbDrone.Api/Episodes/EpisodeConnection.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using Microsoft.AspNet.SignalR; -using Microsoft.AspNet.SignalR.Infrastructure; -using NzbDrone.Api.SignalR; -using NzbDrone.Common.Messaging; -using NzbDrone.Core.Download; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Api.Episodes -{ - public class EpisodeConnection : BasicResourceConnection<Episode>, IHandleAsync<EpisodeGrabbedEvent> - { - public override string Resource - { - get { return "/Episodes"; } - } - - public void HandleAsync(EpisodeGrabbedEvent message) - { - var context = ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); - context.Connection.Broadcast(message); - } - } -} diff --git a/NzbDrone.Api/ErrorManagement/ApiException.cs b/NzbDrone.Api/ErrorManagement/ApiException.cs index 0d5842d19..2a9f2678f 100644 --- a/NzbDrone.Api/ErrorManagement/ApiException.cs +++ b/NzbDrone.Api/ErrorManagement/ApiException.cs @@ -9,7 +9,6 @@ namespace NzbDrone.Api.ErrorManagement { public object Content { get; private set; } - public HttpStatusCode StatusCode { get; private set; } protected ApiException(HttpStatusCode statusCode, object content = null) diff --git a/NzbDrone.Api/ErrorManagement/NzbDroneErrorPipeline.cs b/NzbDrone.Api/ErrorManagement/NzbDroneErrorPipeline.cs index 9db64b37d..47d79c849 100644 --- a/NzbDrone.Api/ErrorManagement/NzbDroneErrorPipeline.cs +++ b/NzbDrone.Api/ErrorManagement/NzbDroneErrorPipeline.cs @@ -3,6 +3,7 @@ using FluentValidation; using NLog; using Nancy; using NzbDrone.Api.Extensions; +using NzbDrone.Core; using HttpStatusCode = Nancy.HttpStatusCode; namespace NzbDrone.Api.ErrorManagement @@ -35,9 +36,20 @@ namespace NzbDrone.Api.ErrorManagement return validationException.Errors.AsResponse(HttpStatusCode.BadRequest); } + var clientException = exception as NzbDroneClientException; + + if (clientException != null) + { + return new ErrorModel + { + Message = exception.Message, + Description = exception.ToString() + }.AsResponse((HttpStatusCode)clientException.StatusCode); + } + _logger.FatalException("Request Failed", exception); - return new ErrorModel() + return new ErrorModel { Message = exception.Message, Description = exception.ToString() diff --git a/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs b/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs index 70f7fc0bf..183326415 100644 --- a/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs +++ b/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs @@ -1,5 +1,4 @@ -using System; -using Nancy; +using Nancy; using Nancy.Bootstrapper; using NzbDrone.Api.Frontend; diff --git a/NzbDrone.Api/Extensions/ReqResExtensions.cs b/NzbDrone.Api/Extensions/ReqResExtensions.cs index fd9980347..1f1d89180 100644 --- a/NzbDrone.Api/Extensions/ReqResExtensions.cs +++ b/NzbDrone.Api/Extensions/ReqResExtensions.cs @@ -12,7 +12,6 @@ namespace NzbDrone.Api.Extensions { private static readonly NancyJsonSerializer NancySerializer = new NancyJsonSerializer(); - public static readonly string LastModified = BuildInfo.BuildDateTime.ToString("r"); public static T FromJson<T>(this Stream body) where T : class, new() @@ -25,7 +24,6 @@ namespace NzbDrone.Api.Extensions return (T)FromJson(body, type); } - public static object FromJson(this Stream body, Type type) { var reader = new StreamReader(body, true); diff --git a/NzbDrone.Api/Indexers/IndexerModule.cs b/NzbDrone.Api/Indexers/IndexerModule.cs index 8a4439b87..02215d3ed 100644 --- a/NzbDrone.Api/Indexers/IndexerModule.cs +++ b/NzbDrone.Api/Indexers/IndexerModule.cs @@ -6,7 +6,6 @@ using NzbDrone.Api.REST; using NzbDrone.Core.Indexers; using Omu.ValueInjecter; using FluentValidation; -using NzbDrone.Api.Extensions; using NzbDrone.Api.Mapping; namespace NzbDrone.Api.Indexers diff --git a/NzbDrone.Api/Indexers/ReleaseModule.cs b/NzbDrone.Api/Indexers/ReleaseModule.cs index aaf452261..f00d30542 100644 --- a/NzbDrone.Api/Indexers/ReleaseModule.cs +++ b/NzbDrone.Api/Indexers/ReleaseModule.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using Nancy; +using NLog; using NzbDrone.Api.Mapping; using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Download; using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Indexers; @@ -23,10 +25,10 @@ namespace NzbDrone.Api.Indexers private readonly IParsingService _parsingService; public ReleaseModule(IFetchAndParseRss rssFetcherAndParser, - ISearchForNzb nzbSearchService, - IMakeDownloadDecision downloadDecisionMaker, - IDownloadService downloadService, - IParsingService parsingService) + ISearchForNzb nzbSearchService, + IMakeDownloadDecision downloadDecisionMaker, + IDownloadService downloadService, + IParsingService parsingService) { _rssFetcherAndParser = rssFetcherAndParser; _nzbSearchService = nzbSearchService; @@ -40,7 +42,7 @@ namespace NzbDrone.Api.Indexers private Response DownloadRelease(ReleaseResource release) { var remoteEpisode = _parsingService.Map(release.InjectTo<ParsedEpisodeInfo>(), 0); - remoteEpisode.Report = release.InjectTo<ReportInfo>(); + remoteEpisode.Release = release.InjectTo<ReleaseInfo>(); _downloadService.DownloadReport(remoteEpisode); @@ -60,6 +62,7 @@ namespace NzbDrone.Api.Indexers private List<ReleaseResource> GetEpisodeReleases(int episodeId) { var decisions = _nzbSearchService.EpisodeSearch(episodeId); + return MapDecisions(decisions); } @@ -79,7 +82,7 @@ namespace NzbDrone.Api.Indexers { var release = new ReleaseResource(); - release.InjectFrom(downloadDecision.RemoteEpisode.Report); + release.InjectFrom(downloadDecision.RemoteEpisode.Release); release.InjectFrom(downloadDecision.RemoteEpisode.ParsedEpisodeInfo); release.InjectFrom(downloadDecision); release.Rejections = downloadDecision.Rejections.ToList(); diff --git a/NzbDrone.Api/Indexers/ReleaseResource.cs b/NzbDrone.Api/Indexers/ReleaseResource.cs index 3db66d39f..3cc4a054a 100644 --- a/NzbDrone.Api/Indexers/ReleaseResource.cs +++ b/NzbDrone.Api/Indexers/ReleaseResource.cs @@ -12,8 +12,6 @@ namespace NzbDrone.Api.Indexers public Int32 Age { get; set; } public Int64 Size { get; set; } public String Indexer { get; set; } - public String NzbInfoUrl { get; set; } - public String NzbUrl { get; set; } public String ReleaseGroup { get; set; } public String Title { get; set; } public Boolean FullSeason { get; set; } @@ -26,5 +24,9 @@ namespace NzbDrone.Api.Indexers public Boolean Approved { get; set; } public Int32 TvRageId { get; set; } public List<string> Rejections { get; set; } + public DateTime PublishDate { get; set; } + public String CommentUrl { get; set; } + public String DownloadUrl { get; set; } + public String InfoUrl { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Api/Logs/LogModule.cs b/NzbDrone.Api/Logs/LogModule.cs index e1c39d92b..59ea4975d 100644 --- a/NzbDrone.Api/Logs/LogModule.cs +++ b/NzbDrone.Api/Logs/LogModule.cs @@ -17,6 +17,12 @@ namespace NzbDrone.Api.Logs private PagingResource<LogResource> GetLogs(PagingResource<LogResource> pagingResource) { var pageSpec = pagingResource.InjectTo<PagingSpec<Log>>(); + + if (pageSpec.SortKey == "time") + { + pageSpec.SortKey = "id"; + } + return ApplyToPage(_logService.Paged, pageSpec); } } diff --git a/NzbDrone.Api/Mapping/CloneInjection.cs b/NzbDrone.Api/Mapping/CloneInjection.cs index 71bbd4bbe..1266ebd0b 100644 --- a/NzbDrone.Api/Mapping/CloneInjection.cs +++ b/NzbDrone.Api/Mapping/CloneInjection.cs @@ -17,9 +17,10 @@ namespace NzbDrone.Api.Mapping protected override object SetValue(ConventionInfo conventionInfo) { - if (conventionInfo.SourceProp.Type.IsValueType || conventionInfo.SourceProp.Type == typeof(string)) + if (conventionInfo.SourceProp.Type == conventionInfo.TargetProp.Type) return conventionInfo.SourceProp.Value; + if (conventionInfo.SourceProp.Type.IsArray) { var array = (Array)conventionInfo.SourceProp.Value; diff --git a/NzbDrone.Api/NancyBootstrapper.cs b/NzbDrone.Api/NancyBootstrapper.cs index 5f65c80e2..263c4f265 100644 --- a/NzbDrone.Api/NancyBootstrapper.cs +++ b/NzbDrone.Api/NancyBootstrapper.cs @@ -1,14 +1,14 @@ -using System; -using NLog; +using NLog; using Nancy.Bootstrapper; using Nancy.Diagnostics; using NzbDrone.Api.Authentication; using NzbDrone.Api.ErrorManagement; -using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions.Pipelines; -using NzbDrone.Common.Messaging; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using TinyIoC; namespace NzbDrone.Api @@ -21,20 +21,18 @@ namespace NzbDrone.Api public NancyBootstrapper(TinyIoCContainer tinyIoCContainer) { _tinyIoCContainer = tinyIoCContainer; - _logger = LogManager.GetCurrentClassLogger(); + _logger = NzbDroneLogger.GetLogger(); } protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { _logger.Info("Starting NzbDrone API"); - RegisterPipelines(pipelines); container.Resolve<DatabaseTarget>().Register(); container.Resolve<IEnableBasicAuthInNancy>().Register(pipelines); - container.Resolve<IMessageAggregator>().PublishEvent(new ApplicationStartedEvent()); - + container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent()); ApplicationPipelines.OnError.AddItemToEndOfPipeline(container.Resolve<NzbDroneErrorPipeline>().HandleException); } @@ -47,10 +45,8 @@ namespace NzbDrone.Api { registerNancyPipeline.Register(pipelines); } - } - protected override TinyIoCContainer GetApplicationContainer() { return _tinyIoCContainer; diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index d0a03bd17..a4fb4ee50 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -91,7 +91,6 @@ <Compile Include="Directories\DirectoryModule.cs" /> <Compile Include="Episodes\EpisodeModule.cs" /> <Compile Include="Episodes\EpisodeResource.cs" /> - <Compile Include="Episodes\EpisodeConnection.cs" /> <Compile Include="Extensions\Pipelines\CacheHeaderPipeline.cs" /> <Compile Include="Extensions\Pipelines\GZipPipeline.cs" /> <Compile Include="Extensions\Pipelines\IfModifiedPipeline.cs" /> @@ -123,6 +122,8 @@ <Compile Include="Mapping\ValueInjectorExtensions.cs" /> <Compile Include="Missing\MissingModule.cs" /> <Compile Include="Config\NamingSampleResource.cs" /> + <Compile Include="NzbDroneRestModuleWithSignalR.cs" /> + <Compile Include="ResourceChangeMessage.cs" /> <Compile Include="Notifications\NotificationSchemaModule.cs" /> <Compile Include="Notifications\NotificationModule.cs" /> <Compile Include="Notifications\NotificationResource.cs" /> @@ -135,10 +136,6 @@ <Compile Include="REST\RestResource.cs" /> <Compile Include="RootFolders\RootFolderModule.cs" /> <Compile Include="RootFolders\RootFolderResource.cs" /> - <Compile Include="RootFolders\RootFolderConnection.cs" /> - <Compile Include="Seasons\SeasonModule.cs" /> - <Compile Include="Seasons\SeasonResource.cs" /> - <Compile Include="Series\SeriesConnection.cs" /> <Compile Include="Series\SeriesResource.cs" /> <Compile Include="Series\SeriesModule.cs" /> <Compile Include="Series\SeriesLookupModule.cs" /> @@ -156,11 +153,6 @@ <Compile Include="Qualities\QualitySizeModule.cs" /> <Compile Include="Extensions\ReqResExtensions.cs" /> <Compile Include="Config\SettingsModule.cs" /> - <Compile Include="SignalR\BasicResourceConnection.cs" /> - <Compile Include="SignalR\NoOpPerformanceCounterManager.cs" /> - <Compile Include="SignalR\Serializer.cs" /> - <Compile Include="SignalR\SignalrDependencyResolver.cs" /> - <Compile Include="SignalR\NzbDronePersistentConnection.cs" /> <Compile Include="System\SystemModule.cs" /> <Compile Include="TinyIoCNancyBootstrapper.cs" /> <Compile Include="Update\UpdateModule.cs" /> @@ -183,6 +175,10 @@ <Project>{ff5ee3b6-913b-47ce-9ceb-11c51b4e1205}</Project> <Name>NzbDrone.Core</Name> </ProjectReference> + <ProjectReference Include="..\NzbDrone.SignalR\NzbDrone.SignalR.csproj"> + <Project>{7c2cc69f-5ca0-4e5c-85cb-983f9f6c3b36}</Project> + <Name>NzbDrone.SignalR</Name> + </ProjectReference> </ItemGroup> <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> @@ -194,4 +190,4 @@ <Target Name="AfterBuild"> </Target> --> -</Project> \ No newline at end of file +</Project> diff --git a/NzbDrone.Api/NzbDrone.Api.ncrunchproject b/NzbDrone.Api/NzbDrone.Api.ncrunchproject index 575717ac8..1a2228e7f 100644 --- a/NzbDrone.Api/NzbDrone.Api.ncrunchproject +++ b/NzbDrone.Api/NzbDrone.Api.ncrunchproject @@ -1,8 +1,8 @@ <ProjectConfiguration> <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> - <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> + <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,9 +12,11 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration /> - <UseBuildPlatform /> - <ProxyProcessPath /> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> + <ProxyProcessPath></ProxyProcessPath> + <UseCPUArchitecture>x86</UseCPUArchitecture> + <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> + <BuildProcessArchitecture>x86</BuildProcessArchitecture> </ProjectConfiguration> \ No newline at end of file diff --git a/NzbDrone.Api/NzbDroneRestModule.cs b/NzbDrone.Api/NzbDroneRestModule.cs index b4b01aee5..5c297e331 100644 --- a/NzbDrone.Api/NzbDroneRestModule.cs +++ b/NzbDrone.Api/NzbDroneRestModule.cs @@ -9,6 +9,8 @@ namespace NzbDrone.Api { public abstract class NzbDroneRestModule<TResource> : RestModule<TResource> where TResource : RestResource, new() { + protected string Resource { get; private set; } + protected NzbDroneRestModule() : this(new TResource().ResourceName) { @@ -17,6 +19,7 @@ namespace NzbDrone.Api protected NzbDroneRestModule(string resource) : base("/api/" + resource.Trim('/')) { + Resource = resource; PostValidator.RuleFor(r => r.Id).IsZero(); PutValidator.RuleFor(r => r.Id).ValidId(); } @@ -28,7 +31,7 @@ namespace NzbDrone.Api return model.Id; } - protected List<TResource> ToListResource<TModel>(Func<IEnumerable<TModel>> function) where TModel : ModelBase, new() + protected List<TResource> ToListResource<TModel>(Func<IEnumerable<TModel>> function) where TModel : class { var modelList = function(); return modelList.InjectTo<List<TResource>>(); diff --git a/NzbDrone.Api/NzbDroneRestModuleWithSignalR.cs b/NzbDrone.Api/NzbDroneRestModuleWithSignalR.cs new file mode 100644 index 000000000..d8350b8ae --- /dev/null +++ b/NzbDrone.Api/NzbDroneRestModuleWithSignalR.cs @@ -0,0 +1,56 @@ +using NzbDrone.Api.REST; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.SignalR; + +namespace NzbDrone.Api +{ + public abstract class NzbDroneRestModuleWithSignalR<TResource, TModel> : NzbDroneRestModule<TResource>, IHandle<ModelEvent<TModel>> + where TResource : RestResource, new() + where TModel : ModelBase + { + private readonly ICommandExecutor _commandExecutor; + + protected NzbDroneRestModuleWithSignalR(ICommandExecutor commandExecutor) + { + _commandExecutor = commandExecutor; + } + + public void Handle(ModelEvent<TModel> message) + { + if (message.Action == ModelAction.Deleted || message.Action == ModelAction.Sync) + { + BroadcastResourceChange(message.Action); + } + + BroadcastResourceChange(message.Action, message.Model.Id); + } + + protected void BroadcastResourceChange(ModelAction action, int id) + { + var resource = GetResourceById(id); + + var signalRMessage = new SignalRMessage + { + Name = Resource, + Body = new ResourceChangeMessage<TResource>(resource, action) + }; + + _commandExecutor.PublishCommand(new BroadcastSignalRMessage(signalRMessage)); + } + + protected void BroadcastResourceChange(ModelAction action) + { + var signalRMessage = new SignalRMessage + { + Name = Resource, + Body = new ResourceChangeMessage<TResource>(action) + }; + + _commandExecutor.PublishCommand(new BroadcastSignalRMessage(signalRMessage)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/REST/RestModule.cs b/NzbDrone.Api/REST/RestModule.cs index 2b19bcc17..9a576d360 100644 --- a/NzbDrone.Api/REST/RestModule.cs +++ b/NzbDrone.Api/REST/RestModule.cs @@ -61,7 +61,7 @@ namespace NzbDrone.Api.REST protected Func<int, TResource> GetResourceById { - private get { return _getResourceById; } + get { return _getResourceById; } set { _getResourceById = value; diff --git a/NzbDrone.Api/ResourceChangeMessage.cs b/NzbDrone.Api/ResourceChangeMessage.cs new file mode 100644 index 000000000..6b7efb50a --- /dev/null +++ b/NzbDrone.Api/ResourceChangeMessage.cs @@ -0,0 +1,29 @@ +using System; +using NzbDrone.Api.REST; +using NzbDrone.Core.Datastore.Events; + +namespace NzbDrone.Api +{ + public class ResourceChangeMessage<TResource> where TResource : RestResource + { + public TResource Resource { get; private set; } + public ModelAction Action { get; private set; } + + public ResourceChangeMessage(ModelAction action) + { + if (action != ModelAction.Deleted || action != ModelAction.Sync) + { + throw new InvalidOperationException("Resource message without a resource needs to have Delete or Sync as action"); + } + + Action = action; + } + + public ResourceChangeMessage(TResource resource, ModelAction action) + { + Resource = resource; + Action = action; + } + } + +} \ No newline at end of file diff --git a/NzbDrone.Api/RootFolders/RootFolderConnection.cs b/NzbDrone.Api/RootFolders/RootFolderConnection.cs deleted file mode 100644 index 0068cc972..000000000 --- a/NzbDrone.Api/RootFolders/RootFolderConnection.cs +++ /dev/null @@ -1,13 +0,0 @@ -using NzbDrone.Api.SignalR; -using NzbDrone.Core.RootFolders; - -namespace NzbDrone.Api.RootFolders -{ - public class RootFolderConnection : BasicResourceConnection<RootFolder> - { - public override string Resource - { - get { return "RootFolder"; } - } - } -} diff --git a/NzbDrone.Api/RootFolders/RootFolderModule.cs b/NzbDrone.Api/RootFolders/RootFolderModule.cs index 7bcc4aa52..dd346d5de 100644 --- a/NzbDrone.Api/RootFolders/RootFolderModule.cs +++ b/NzbDrone.Api/RootFolders/RootFolderModule.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using NzbDrone.Core.RootFolders; using NzbDrone.Api.Mapping; using NzbDrone.Api.Validation; diff --git a/NzbDrone.Api/Seasons/SeasonModule.cs b/NzbDrone.Api/Seasons/SeasonModule.cs deleted file mode 100644 index 0521b8518..000000000 --- a/NzbDrone.Api/Seasons/SeasonModule.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Api.Mapping; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Api.Seasons -{ - public class SeasonModule : NzbDroneRestModule<SeasonResource> - { - private readonly ISeasonService _seasonService; - - public SeasonModule(ISeasonService seasonService) - : base("/season") - { - _seasonService = seasonService; - - GetResourceAll = GetSeasons; - GetResourceById = GetSeason; - UpdateResource = Update; - - Post["/pass"] = x => SetSeasonPass(); - } - - private List<SeasonResource> GetSeasons() - { - var seriesId = Request.Query.SeriesId; - - if (seriesId.HasValue) - { - return ToListResource<Season>(() => _seasonService.GetSeasonsBySeries(seriesId)); - } - - return ToListResource(() => _seasonService.GetAllSeasons()); - } - - private SeasonResource GetSeason(int id) - { - return _seasonService.Get(id).InjectTo<SeasonResource>(); - } - - private void Update(SeasonResource seasonResource) - { - _seasonService.SetMonitored(seasonResource.SeriesId, seasonResource.SeasonNumber, seasonResource.Monitored); - } - - private List<SeasonResource> SetSeasonPass() - { - var seriesId = Request.Form.SeriesId; - var seasonNumber = Request.Form.SeasonNumber; - - return ToListResource<Season>(() => _seasonService.SetSeasonPass(seriesId, seasonNumber)); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Api/Seasons/SeasonResource.cs b/NzbDrone.Api/Seasons/SeasonResource.cs deleted file mode 100644 index 46e14be9d..000000000 --- a/NzbDrone.Api/Seasons/SeasonResource.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using NzbDrone.Api.REST; - -namespace NzbDrone.Api.Seasons -{ - public class SeasonResource : RestResource - { - public int SeriesId { get; set; } - public int SeasonNumber { get; set; } - public Boolean Monitored { get; set; } - } -} diff --git a/NzbDrone.Api/Series/SeriesConnection.cs b/NzbDrone.Api/Series/SeriesConnection.cs deleted file mode 100644 index 4de76e7e6..000000000 --- a/NzbDrone.Api/Series/SeriesConnection.cs +++ /dev/null @@ -1,12 +0,0 @@ -using NzbDrone.Api.SignalR; - -namespace NzbDrone.Api.Series -{ - public class SeriesConnection : BasicResourceConnection<Core.Tv.Series> - { - public override string Resource - { - get { return "/Series"; } - } - } -} diff --git a/NzbDrone.Api/Series/SeriesModule.cs b/NzbDrone.Api/Series/SeriesModule.cs index fe7b820a6..ea01b1a06 100644 --- a/NzbDrone.Api/Series/SeriesModule.cs +++ b/NzbDrone.Api/Series/SeriesModule.cs @@ -117,7 +117,6 @@ namespace NzbDrone.Api.Series { resource.EpisodeCount = seriesStatistics.EpisodeCount; resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount; - resource.SeasonCount = seriesStatistics.SeasonCount; resource.NextAiring = seriesStatistics.NextAiring; } } diff --git a/NzbDrone.Api/Series/SeriesResource.cs b/NzbDrone.Api/Series/SeriesResource.cs index 34f7dcead..170fa8301 100644 --- a/NzbDrone.Api/Series/SeriesResource.cs +++ b/NzbDrone.Api/Series/SeriesResource.cs @@ -14,7 +14,17 @@ namespace NzbDrone.Api.Series //View Only public String Title { get; set; } - public Int32 SeasonCount { get; set; } + + public Int32 SeasonCount + { + get + { + if (Seasons != null) return Seasons.Count; + + return 0; + } + } + public Int32 EpisodeCount { get; set; } public Int32 EpisodeFileCount { get; set; } public SeriesStatusType Status { get; set; } @@ -26,7 +36,8 @@ namespace NzbDrone.Api.Series public List<MediaCover> Images { get; set; } public String RemotePoster { get; set; } - + public List<Season> Seasons { get; set; } + public Int32 Year { get; set; } //View & Edit public String Path { get; set; } diff --git a/NzbDrone.Api/SignalR/BasicResourceConnection.cs b/NzbDrone.Api/SignalR/BasicResourceConnection.cs deleted file mode 100644 index e56ae4fd2..000000000 --- a/NzbDrone.Api/SignalR/BasicResourceConnection.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNet.SignalR; -using Microsoft.AspNet.SignalR.Infrastructure; -using NLog; -using NzbDrone.Common.Messaging; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Datastore.Events; - -namespace NzbDrone.Api.SignalR -{ - public abstract class BasicResourceConnection<T> : - NzbDronePersistentConnection, - IHandleAsync<ModelEvent<T>> - where T : ModelBase - { - private readonly Logger _logger; - - - public BasicResourceConnection() - { - _logger = LogManager.GetCurrentClassLogger(); - } - - protected override Task OnConnected(IRequest request, string connectionId) - { - _logger.Trace("SignalR client connected. ID:{0}", connectionId); - return base.OnConnected(request, connectionId); - } - - public void HandleAsync(ModelEvent<T> message) - { - var context = ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); - context.Connection.Broadcast(message); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Api/SignalR/NzbDronePersistentConnection.cs b/NzbDrone.Api/SignalR/NzbDronePersistentConnection.cs deleted file mode 100644 index 2e4c8444d..000000000 --- a/NzbDrone.Api/SignalR/NzbDronePersistentConnection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.AspNet.SignalR; - -namespace NzbDrone.Api.SignalR -{ - public abstract class NzbDronePersistentConnection : PersistentConnection - { - public abstract string Resource { get; } - } -} \ No newline at end of file diff --git a/NzbDrone.Api/System/SystemModule.cs b/NzbDrone.Api/System/SystemModule.cs index e0e9b9177..a013b15af 100644 --- a/NzbDrone.Api/System/SystemModule.cs +++ b/NzbDrone.Api/System/SystemModule.cs @@ -40,6 +40,7 @@ namespace NzbDrone.Api.System OsVersion = OsInfo.Version.ToString(), IsMono = OsInfo.IsMono, IsLinux = OsInfo.IsLinux, + IsWindows = OsInfo.IsWindows, Branch = _configFileProvider.Branch, Authentication = _configFileProvider.AuthenticationEnabled }.AsResponse(); diff --git a/NzbDrone.Api/Update/UpdateModule.cs b/NzbDrone.Api/Update/UpdateModule.cs index 8a95efa4b..fbe13bd8e 100644 --- a/NzbDrone.Api/Update/UpdateModule.cs +++ b/NzbDrone.Api/Update/UpdateModule.cs @@ -33,8 +33,6 @@ namespace NzbDrone.Api.Update public class UpdateResource : RestResource { - public String Id { get; set; } - [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))] public Version Version { get; set; } diff --git a/NzbDrone.Api/Validation/RuleBuilderExtensions.cs b/NzbDrone.Api/Validation/RuleBuilderExtensions.cs index b142f5a56..a13cbf52a 100644 --- a/NzbDrone.Api/Validation/RuleBuilderExtensions.cs +++ b/NzbDrone.Api/Validation/RuleBuilderExtensions.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq.Expressions; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using FluentValidation; using FluentValidation.Validators; @@ -27,5 +25,10 @@ namespace NzbDrone.Api.Validation { return ruleBuilder.SetValidator(new PathValidator()); } + + public static IRuleBuilderOptions<T, string> NotBlank<T>(this IRuleBuilder<T, string> ruleBuilder) + { + return ruleBuilder.SetValidator(new NotNullValidator()).SetValidator(new NotEmptyValidator("")); + } } } \ No newline at end of file diff --git a/NzbDrone.App.Test/ContainerFixture.cs b/NzbDrone.App.Test/ContainerFixture.cs index f4bf6af6c..244901c0a 100644 --- a/NzbDrone.App.Test/ContainerFixture.cs +++ b/NzbDrone.App.Test/ContainerFixture.cs @@ -2,12 +2,13 @@ using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Messaging; -using NzbDrone.Core.Datastore; using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; using NzbDrone.Core.Jobs; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Host; using NzbDrone.Test.Common; using FluentAssertions; @@ -45,7 +46,6 @@ namespace NzbDrone.App.Test { var genericExecutor = typeof(IExecute<>).MakeGenericType(typeof(RssSyncCommand)); var container = MainAppContainerBuilder.BuildContainer(args); - DbFactory.RegisterDatabase(container); var executor = container.Resolve(genericExecutor); diff --git a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs index 4a1cf9021..1d91558a6 100644 --- a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs +++ b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs @@ -48,6 +48,23 @@ namespace NzbDrone.Common.Test.CacheTests _cachedString.Find("Key").Should().Be("New"); } + + [Test] + public void should_be_able_to_remove_key() + { + _cachedString.Set("Key", "Value"); + + _cachedString.Remove("Key"); + + _cachedString.Find("Key").Should().BeNull(); + } + + [Test] + public void should_be_able_to_remove_non_existing_key() + { + _cachedString.Remove("Key"); + } + [Test] public void should_store_null() { diff --git a/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixture.cs b/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixture.cs index 1bcb330f1..4de222365 100644 --- a/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixture.cs +++ b/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixture.cs @@ -23,6 +23,10 @@ namespace NzbDrone.Common.Test.DiskProviderTests if (_binFolderCopy.Exists) { + foreach (var file in _binFolderCopy.GetFiles("*", SearchOption.AllDirectories)) + { + file.Attributes = FileAttributes.Normal; + } _binFolderCopy.Delete(true); } @@ -83,11 +87,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests [Test] public void CopyFolder_should_copy_folder() { - - Subject.CopyFolder(_binFolder.FullName, _binFolderCopy.FullName); - - VerifyCopy(); } @@ -127,6 +127,22 @@ namespace NzbDrone.Common.Test.DiskProviderTests } + [Test] + public void move_read_only_file() + { + var source = GetTestFilePath(); + var destination = GetTestFilePath(); + + Subject.WriteAllText(source, "SourceFile"); + Subject.WriteAllText(destination, "DestinationFile"); + + File.SetAttributes(source, FileAttributes.ReadOnly); + File.SetAttributes(destination, FileAttributes.ReadOnly); + + Subject.MoveFile(source, destination); + } + + [Test] @@ -139,16 +155,60 @@ namespace NzbDrone.Common.Test.DiskProviderTests [Test] public void folder_should_return_correct_value_for_last_write() { - var testFile = Path.Combine(SandboxFolder, "newfile.txt"); + var testFile = GetTestFilePath(); TestLogger.Info("Path is: {0}", testFile); - Subject.WriteAllText(testFile, ""); + Subject.WriteAllText(testFile, "Test"); Subject.GetLastFolderWrite(SandboxFolder).Should().BeOnOrAfter(DateTime.UtcNow.AddMinutes(-1)); Subject.GetLastFolderWrite(SandboxFolder).Should().BeBefore(DateTime.UtcNow); } + [Test] + public void should_return_false_for_unlocked_file() + { + var testFile = GetTestFilePath(); + Subject.WriteAllText(testFile, new Guid().ToString()); + + Subject.IsFileLocked(testFile).Should().BeFalse(); + } + + [Test] + public void should_return_false_for_unlocked_and_readonly_file() + { + var testFile = GetTestFilePath(); + Subject.WriteAllText(testFile, new Guid().ToString()); + + File.SetAttributes(testFile, FileAttributes.ReadOnly); + + Subject.IsFileLocked(testFile).Should().BeFalse(); + } + + + [Test] + public void should_return_true_for_unlocked_file() + { + var testFile = GetTestFilePath(); + Subject.WriteAllText(testFile, new Guid().ToString()); + + using (var file = File.OpenWrite(testFile)) + { + Subject.IsFileLocked(testFile).Should().BeTrue(); + } + } + + + [Test] + public void should_be_able_to_set_permission_from_parrent() + { + var testFile = GetTestFilePath(); + Subject.WriteAllText(testFile, new Guid().ToString()); + + Subject.InheritFolderPermissions(testFile); + } + + [Test] [Explicit] public void check_last_write() diff --git a/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs b/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs index 8359ad481..f3ede03eb 100644 --- a/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs +++ b/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using FluentAssertions; +using FluentAssertions; using NUnit.Framework; using NzbDrone.Test.Common; diff --git a/NzbDrone.Common.Test/EnvironmentProviderTest.cs b/NzbDrone.Common.Test/EnvironmentProviderTest.cs index 8dfaf87b5..588fb06ad 100644 --- a/NzbDrone.Common.Test/EnvironmentProviderTest.cs +++ b/NzbDrone.Common.Test/EnvironmentProviderTest.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Common.Test [Test] public void IsProduction_should_return_false_when_run_within_nunit() { - RuntimeInfo.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName); + RuntimeInfo.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName + " Folder is " + Directory.GetCurrentDirectory()); } [Test] diff --git a/NzbDrone.Common.Test/EventingTests/MessageAggregatorEventTests.cs b/NzbDrone.Common.Test/MessagingTests/MessageAggregatorEventTests.cs similarity index 94% rename from NzbDrone.Common.Test/EventingTests/MessageAggregatorEventTests.cs rename to NzbDrone.Common.Test/MessagingTests/MessageAggregatorEventTests.cs index b41f81124..30a177c81 100644 --- a/NzbDrone.Common.Test/EventingTests/MessageAggregatorEventTests.cs +++ b/NzbDrone.Common.Test/MessagingTests/MessageAggregatorEventTests.cs @@ -1,16 +1,18 @@ using System; using System.Collections.Generic; using System.Threading; +using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Test.Common; -using FluentAssertions; -namespace NzbDrone.Common.Test.EventingTests +namespace NzbDrone.Common.Test.MessagingTests { [TestFixture] - public class MessageAggregatorEventTests : TestBase<MessageAggregator> + public class MessageAggregatorEventTests : TestBase<EventAggregator> { private Mock<IHandle<EventA>> HandlerA1; private Mock<IHandle<EventA>> HandlerA2; @@ -127,7 +129,7 @@ namespace NzbDrone.Common.Test.EventingTests counter.WaitForAllItems(); - counter.MaxThreads.Should().Be(2); + counter.MaxThreads.Should().Be(3); } } diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index 4c22a7aeb..2347a37bf 100644 --- a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -67,8 +67,7 @@ <Compile Include="EnsureTest\PathExtensionFixture.cs" /> <Compile Include="EnvironmentTests\StartupArgumentsFixture.cs" /> <Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" /> - <Compile Include="EventingTests\MessageAggregatorCommandTests.cs" /> - <Compile Include="EventingTests\MessageAggregatorEventTests.cs" /> + <Compile Include="MessagingTests\MessageAggregatorEventTests.cs" /> <Compile Include="ReflectionExtensions.cs" /> <Compile Include="PathExtensionFixture.cs" /> <Compile Include="DiskProviderTests\DiskProviderFixture.cs" /> diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.ncrunchproject b/NzbDrone.Common.Test/NzbDrone.Common.Test.ncrunchproject index 12a3cf5f9..21e25ddd5 100644 --- a/NzbDrone.Common.Test/NzbDrone.Common.Test.ncrunchproject +++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.ncrunchproject @@ -2,7 +2,7 @@ <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,11 +12,11 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration></UseBuildConfiguration> - <UseBuildPlatform></UseBuildPlatform> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> <ProxyProcessPath></ProxyProcessPath> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <UseCPUArchitecture>x86</UseCPUArchitecture> <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> <BuildProcessArchitecture>x86</BuildProcessArchitecture> <IgnoredTests> @@ -27,10 +27,34 @@ <RegularExpression>NzbDrone\.Common\.Test\.EventingTests\.ServiceNameFixture\..*</RegularExpression> </RegexTestSelector> <RegexTestSelector> - <RegularExpression>NzbDrone\.Common\.Test\.ProcessProviderTests\..*</RegularExpression> + <RegularExpression>NzbDrone\.Common\.Test\.ServiceFactoryFixture\..*</RegularExpression> + </RegexTestSelector> + <NamedTestSelector> + <TestName>NzbDrone.Common.Test.ProcessProviderTests.ToString_on_new_processInfo</TestName> + </NamedTestSelector> + <NamedTestSelector> + <TestName>NzbDrone.Common.Test.ProcessProviderTests.Should_be_able_to_start_process</TestName> + </NamedTestSelector> + <NamedTestSelector> + <TestName>NzbDrone.Common.Test.ProcessProviderTests.kill_all_should_kill_all_process_with_name</TestName> + </NamedTestSelector> + <NamedTestSelector> + <TestName>NzbDrone.Common.Test.ProcessProviderTests.GetProcessById_should_return_null_for_invalid_process(9999)</TestName> + </NamedTestSelector> + <NamedTestSelector> + <TestName>NzbDrone.Common.Test.ProcessProviderTests.GetProcessById_should_return_null_for_invalid_process(-1)</TestName> + </NamedTestSelector> + <NamedTestSelector> + <TestName>NzbDrone.Common.Test.ProcessProviderTests.GetProcessById_should_return_null_for_invalid_process(0)</TestName> + </NamedTestSelector> + <RegexTestSelector> + <RegularExpression>NzbDrone\.Common\.Test\.ServiceProviderTests\..*</RegularExpression> </RegexTestSelector> + <NamedTestSelector> + <TestName>NzbDrone.Common.Test.DiskProviderTests.DiskProviderFixture.folder_should_return_correct_value_for_last_write</TestName> + </NamedTestSelector> <RegexTestSelector> - <RegularExpression>NzbDrone\.Common\.Test\.ServiceFactoryFixture\..*</RegularExpression> + <RegularExpression>NzbDrone\.Common\.Test\.DiskProviderTests\.DiskProviderFixture\..*</RegularExpression> </RegexTestSelector> </IgnoredTests> </ProjectConfiguration> \ No newline at end of file diff --git a/NzbDrone.Common.Test/PathExtensionFixture.cs b/NzbDrone.Common.Test/PathExtensionFixture.cs index 907d13d4c..fbc2fb6b6 100644 --- a/NzbDrone.Common.Test/PathExtensionFixture.cs +++ b/NzbDrone.Common.Test/PathExtensionFixture.cs @@ -126,17 +126,16 @@ namespace NzbDrone.Common.Test } - [Test] public void get_actual_casing_should_return_actual_casing_for_local_dir_in_windows() { WindowsOnly(); - var path = Directory.GetCurrentDirectory(); + var path = Directory.GetCurrentDirectory().Replace("c:\\","C:\\"); + path.ToUpper().GetActualCasing().Should().Be(path); path.ToLower().GetActualCasing().Should().Be(path); } - [Test] public void get_actual_casing_should_return_original_value_in_linux() { diff --git a/NzbDrone.Common.Test/ProcessProviderTests.cs b/NzbDrone.Common.Test/ProcessProviderTests.cs index dab923782..4155ab4d0 100644 --- a/NzbDrone.Common.Test/ProcessProviderTests.cs +++ b/NzbDrone.Common.Test/ProcessProviderTests.cs @@ -73,7 +73,7 @@ namespace NzbDrone.Common.Test var dummy1 = StartDummyProcess(); var dummy2 = StartDummyProcess(); - Subject.KillAll(dummy1.ProcessName); + Subject.KillAll(DummyApp.DUMMY_PROCCESS_NAME); dummy1.HasExited.Should().BeTrue(); dummy2.HasExited.Should().BeTrue(); diff --git a/NzbDrone.Common.Test/ServiceFactoryFixture.cs b/NzbDrone.Common.Test/ServiceFactoryFixture.cs index 4d83d0b3c..7777476ff 100644 --- a/NzbDrone.Common.Test/ServiceFactoryFixture.cs +++ b/NzbDrone.Common.Test/ServiceFactoryFixture.cs @@ -2,8 +2,9 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Host; using NzbDrone.Test.Common; diff --git a/NzbDrone.Common/Cache/CacheManger.cs b/NzbDrone.Common/Cache/CacheManger.cs index d264eeb40..b5da3047c 100644 --- a/NzbDrone.Common/Cache/CacheManger.cs +++ b/NzbDrone.Common/Cache/CacheManger.cs @@ -28,7 +28,6 @@ namespace NzbDrone.Common.Cache return GetCache<T>(host, host.FullName); } - public void Clear() { _cache.Clear(); diff --git a/NzbDrone.Common/Cache/Cached.cs b/NzbDrone.Common/Cache/Cached.cs index eef89f317..7269a9a53 100644 --- a/NzbDrone.Common/Cache/Cached.cs +++ b/NzbDrone.Common/Cache/Cached.cs @@ -61,6 +61,12 @@ namespace NzbDrone.Common.Cache return value.Object; } + public void Remove(string key) + { + CacheItem value; + _store.TryRemove(key, out value); + } + public T Get(string key, Func<T> function, TimeSpan? lifeTime = null) { Ensure.That(() => key).IsNotNullOrWhiteSpace(); @@ -81,7 +87,6 @@ namespace NzbDrone.Common.Cache return value; } - public void Clear() { _store.Clear(); diff --git a/NzbDrone.Common/Cache/ICached.cs b/NzbDrone.Common/Cache/ICached.cs index 3708b72af..1c9e50812 100644 --- a/NzbDrone.Common/Cache/ICached.cs +++ b/NzbDrone.Common/Cache/ICached.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Common.Cache void Set(string key, T value, TimeSpan? lifetime = null); T Get(string key, Func<T> function, TimeSpan? lifeTime = null); T Find(string key); + void Remove(string key); ICollection<T> Values { get; } } diff --git a/NzbDrone.Common/DiskProvider.cs b/NzbDrone.Common/DiskProvider.cs index 851ba4550..56180cdda 100644 --- a/NzbDrone.Common/DiskProvider.cs +++ b/NzbDrone.Common/DiskProvider.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -8,12 +7,12 @@ using System.Security.Principal; using NLog; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Common { public interface IDiskProvider { - HashSet<string> SpecialFolders { get; } DateTime GetLastFolderWrite(string path); DateTime GetLastFileWrite(string path); void EnsureFolder(string path); @@ -24,8 +23,8 @@ namespace NzbDrone.Common string[] GetFiles(string path, SearchOption searchOption); long GetFolderSize(string path); long GetFileSize(string path); - String CreateFolder(string path); - void CopyFolder(string source, string target); + void CreateFolder(string path); + void CopyFolder(string source, string destination); void MoveFolder(string source, string destination); void DeleteFile(string path); void MoveFile(string source, string destination); @@ -36,11 +35,12 @@ namespace NzbDrone.Common void WriteAllText(string filename, string contents); void FileSetLastWriteTimeUtc(string path, DateTime dateTime); void FolderSetLastWriteTimeUtc(string path, DateTime dateTime); - bool IsFileLocked(FileInfo file); + bool IsFileLocked(string path); string GetPathRoot(string path); void SetPermissions(string filename, WellKnownSidType accountSid, FileSystemRights rights, AccessControlType controlType); bool IsParent(string parentPath, string childPath); FileAttributes GetFileAttributes(string path); + void EmptyFolder(string path); } public class DiskProvider : IDiskProvider @@ -58,15 +58,7 @@ namespace NzbDrone.Common out ulong lpTotalNumberOfBytes, out ulong lpTotalNumberOfFreeBytes); - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - public HashSet<string> SpecialFolders - { - get - { - return new HashSet<string> { "$recycle.bin", "system volume information", "recycler" }; - } - } + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public DateTime GetLastFolderWrite(string path) { @@ -93,7 +85,9 @@ namespace NzbDrone.Common Ensure.That(() => path).IsValidPath(); if (!FileExists(path)) + { throw new FileNotFoundException("File doesn't exist: " + path); + } return new FileInfo(path).LastWriteTimeUtc; } @@ -154,25 +148,26 @@ namespace NzbDrone.Common Ensure.That(() => path).IsValidPath(); if (!FileExists(path)) + { throw new FileNotFoundException("File doesn't exist: " + path); + } var fi = new FileInfo(path); return fi.Length; } - public String CreateFolder(string path) + public void CreateFolder(string path) { Ensure.That(() => path).IsValidPath(); - - return Directory.CreateDirectory(path).FullName; + Directory.CreateDirectory(path); } - public void CopyFolder(string source, string target) + public void CopyFolder(string source, string destination) { Ensure.That(() => source).IsValidPath(); - Ensure.That(() => target).IsValidPath(); + Ensure.That(() => destination).IsValidPath(); - TransferFolder(source, target, TransferAction.Copy); + TransferFolder(source, destination, TransferAction.Copy); } public void MoveFolder(string source, string destination) @@ -183,7 +178,7 @@ namespace NzbDrone.Common try { TransferFolder(source, destination, TransferAction.Move); - Directory.Delete(source, true); + DeleteFolder(source, true); } catch (Exception e) { @@ -238,8 +233,10 @@ namespace NzbDrone.Common public void DeleteFile(string path) { Ensure.That(() => path).IsValidPath(); - Logger.Trace("Deleting file: {0}", path); + + RemoveReadOnly(path); + File.Delete(path); } @@ -259,6 +256,7 @@ namespace NzbDrone.Common DeleteFile(destination); } + RemoveReadOnly(source); File.Move(source, destination); } @@ -273,9 +271,19 @@ namespace NzbDrone.Common { Ensure.That(() => filename).IsValidPath(); - var fs = File.GetAccessControl(filename); - fs.SetAccessRuleProtection(false, false); - File.SetAccessControl(filename, fs); + try + { + var fs = File.GetAccessControl(filename); + fs.SetAccessRuleProtection(false, false); + File.SetAccessControl(filename, fs); + } + catch (NotImplementedException) + { + if (!OsInfo.IsLinux) + { + throw; + } + } } public long? GetAvailableSpace(string path) @@ -347,11 +355,10 @@ namespace NzbDrone.Common public void WriteAllText(string filename, string contents) { Ensure.That(() => filename).IsValidPath(); - + RemoveReadOnly(filename); File.WriteAllText(filename, contents); } - public void FileSetLastWriteTimeUtc(string path, DateTime dateTime) { Ensure.That(() => path).IsValidPath(); @@ -366,26 +373,19 @@ namespace NzbDrone.Common Directory.SetLastWriteTimeUtc(path, dateTime); } - public bool IsFileLocked(FileInfo file) + public bool IsFileLocked(string file) { - FileStream stream = null; - try { - stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None); + using (File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None)) + { + return false; + } } catch (IOException) { return true; } - finally - { - if (stream != null) - stream.Close(); - } - - //file is not locked - return false; } public string GetPathRoot(string path) @@ -441,9 +441,34 @@ namespace NzbDrone.Common return false; } + + private static void RemoveReadOnly(string path) + { + if (File.Exists(path)) + { + var newAttributes = File.GetAttributes(path) & ~(FileAttributes.ReadOnly); + File.SetAttributes(path, newAttributes); + } + } + public FileAttributes GetFileAttributes(string path) { return File.GetAttributes(path); } + + public void EmptyFolder(string path) + { + Ensure.That(() => path).IsValidPath(); + + foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly)) + { + DeleteFile(file); + } + + foreach (var directory in GetDirectories(path)) + { + DeleteFolder(directory, true); + } + } } } \ No newline at end of file diff --git a/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs b/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs index 0cd595464..b3b840491 100644 --- a/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs +++ b/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.IO; using System.Text.RegularExpressions; using NzbDrone.Common.EnsureThat.Resources; using NzbDrone.Common.EnvironmentInfo; diff --git a/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs b/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs index e93395756..04f7b29ab 100644 --- a/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs +++ b/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Security.AccessControl; using System.Security.Principal; using NLog; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Common.EnvironmentInfo { @@ -30,7 +31,7 @@ namespace NzbDrone.Common.EnvironmentInfo DATA_SPECIAL_FOLDER = Environment.SpecialFolder.ApplicationData; } - _logger = LogManager.GetCurrentClassLogger(); + _logger = NzbDroneLogger.GetLogger(this); if (startupArguments.Args.ContainsKey(StartupArguments.APPDATA)) { diff --git a/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs b/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs index 7bd6e8d3c..858db57b0 100644 --- a/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs +++ b/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.IO; using System.Security.Principal; -using System.ServiceProcess; using NLog; namespace NzbDrone.Common.EnvironmentInfo @@ -65,7 +64,9 @@ namespace NzbDrone.Common.EnvironmentInfo if (lowerProcessName.Contains("jetbrain")) return false; if (lowerProcessName.Contains("resharper")) return false; - if (Directory.GetCurrentDirectory().ToLower().Contains("teamcity")) return false; + string lowerCurrentDir = Directory.GetCurrentDirectory().ToLower(); + if (lowerCurrentDir.Contains("teamcity")) return false; + if (lowerCurrentDir.StartsWith("/run/")) return false; return true; } diff --git a/NzbDrone.Common/EnvironmentInfo/StartupArguments.cs b/NzbDrone.Common/EnvironmentInfo/StartupArguments.cs index 9075d64e0..51c587bd0 100644 --- a/NzbDrone.Common/EnvironmentInfo/StartupArguments.cs +++ b/NzbDrone.Common/EnvironmentInfo/StartupArguments.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace NzbDrone.Common.EnvironmentInfo { @@ -17,14 +16,6 @@ namespace NzbDrone.Common.EnvironmentInfo public const string UNINSTALL_SERVICE = "u"; public const string HELP = "?"; - static StartupArguments() - { - if (RuntimeInfo.IsProduction) - { - Instance = new StartupArguments(""); - } - } - public StartupArguments(params string[] args) { Flags = new HashSet<string>(); @@ -45,13 +36,9 @@ namespace NzbDrone.Common.EnvironmentInfo Flags.Add(flag); } } - - Instance = this; } public HashSet<string> Flags { get; private set; } public Dictionary<string, string> Args { get; private set; } - - public static IStartupArguments Instance { get; private set; } } } \ No newline at end of file diff --git a/NzbDrone.Common/Exceptions/NzbDroneException.cs b/NzbDrone.Common/Exceptions/NzbDroneException.cs index 09b431fc6..7e0be7312 100644 --- a/NzbDrone.Common/Exceptions/NzbDroneException.cs +++ b/NzbDrone.Common/Exceptions/NzbDroneException.cs @@ -2,8 +2,6 @@ namespace NzbDrone.Common.Exceptions { - - public abstract class NzbDroneException : ApplicationException { protected NzbDroneException(string message, params object[] args) @@ -17,5 +15,16 @@ namespace NzbDrone.Common.Exceptions { } + + protected NzbDroneException(string message, Exception innerException, params object[] args) + : base(string.Format(message, args), innerException) + { + + } + + protected NzbDroneException(string message, Exception innerException) + : base(message, innerException) + { + } } } diff --git a/NzbDrone.Common/HashUtil.cs b/NzbDrone.Common/HashUtil.cs index 0a127ee63..abeb9496d 100644 --- a/NzbDrone.Common/HashUtil.cs +++ b/NzbDrone.Common/HashUtil.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Cryptography; using System.Text; using System.Threading; @@ -34,31 +33,9 @@ namespace NzbDrone.Common return String.Format("{0:x8}", mCrc); } - public static string GenerateUserId() + public static string GenerateCommandId() { - return GenerateId("u"); - } - - public static string GenerateAppId() - { - return GenerateId("a"); - } - - public static string GenerateApiToken() - { - return Guid.NewGuid().ToString().Replace("-", ""); - } - - public static string GenerateSecurityToken(int length) - { - var byteSize = (length / 4) * 3; - - var linkBytes = new byte[byteSize]; - var rngCrypto = new RNGCryptoServiceProvider(); - rngCrypto.GetBytes(linkBytes); - var base64String = Convert.ToBase64String(linkBytes); - - return base64String; + return GenerateId("c"); } private static string GenerateId(string prefix) diff --git a/NzbDrone.Common/Instrumentation/ApplicationLogLayoutRenderer.cs b/NzbDrone.Common/Instrumentation/ApplicationLogLayoutRenderer.cs deleted file mode 100644 index 2a2082d18..000000000 --- a/NzbDrone.Common/Instrumentation/ApplicationLogLayoutRenderer.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.IO; -using System.Text; -using NLog; -using NLog.Config; -using NLog.LayoutRenderers; -using NzbDrone.Common.EnvironmentInfo; - -namespace NzbDrone.Common.Instrumentation -{ - [ThreadAgnostic] - [LayoutRenderer("appLog")] - public class ApplicationLogLayoutRenderer : LayoutRenderer - { - private readonly string _appData; - - public ApplicationLogLayoutRenderer() - { - _appData = Path.Combine(new AppFolderInfo(new DiskProvider(), StartupArguments.Instance ).GetLogFolder(), "nzbdrone.txt"); - } - - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - builder.Append(_appData); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Instrumentation/DirSeparatorLayoutRenderer.cs b/NzbDrone.Common/Instrumentation/DirSeparatorLayoutRenderer.cs deleted file mode 100644 index bb5658326..000000000 --- a/NzbDrone.Common/Instrumentation/DirSeparatorLayoutRenderer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; -using System.Text; -using NLog; -using NLog.Config; -using NLog.LayoutRenderers; - -namespace NzbDrone.Common.Instrumentation -{ - [ThreadAgnostic] - [LayoutRenderer("dirSeparator")] - public class DirSeparatorLayoutRenderer : LayoutRenderer - { - - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - builder.Append(Path.DirectorySeparatorChar); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Instrumentation/ExceptronTarget.cs b/NzbDrone.Common/Instrumentation/ExceptronTarget.cs index 59ff5e918..f824a5ac4 100644 --- a/NzbDrone.Common/Instrumentation/ExceptronTarget.cs +++ b/NzbDrone.Common/Instrumentation/ExceptronTarget.cs @@ -4,7 +4,6 @@ using Exceptron.Client; using Exceptron.Client.Configuration; using NLog; using NLog.Common; -using NLog.Config; using NLog.Layouts; using NLog.Targets; using NzbDrone.Common.EnvironmentInfo; @@ -23,19 +22,6 @@ namespace NzbDrone.Common.Instrumentation /// </summary> public IExceptronClient ExceptronClient { get; internal set; } - - private static ExceptronTarget _instance = new ExceptronTarget(); - - public static void Register() - { - var rule = new LoggingRule("*", LogLevel.Warn, _instance); - - LogManager.Configuration.AddTarget("ExceptronTarget", _instance); - LogManager.Configuration.LoggingRules.Add(rule); - LogManager.ConfigurationReloaded += (sender, args) => Register(); - LogManager.ReconfigExistingLoggers(); - } - protected override void InitializeTarget() { var config = new ExceptronConfiguration diff --git a/NzbDrone.Common/Instrumentation/GlobalExceptionHandlers.cs b/NzbDrone.Common/Instrumentation/GlobalExceptionHandlers.cs index 24fb64c61..ed32997eb 100644 --- a/NzbDrone.Common/Instrumentation/GlobalExceptionHandlers.cs +++ b/NzbDrone.Common/Instrumentation/GlobalExceptionHandlers.cs @@ -6,12 +6,9 @@ namespace NzbDrone.Common.Instrumentation { public static class GlobalExceptionHandlers { - private static readonly Logger Logger = LogManager.GetLogger("Global"); - + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public static void Register() { - ExceptronTarget.Register(); - AppDomain.CurrentDomain.UnhandledException += ((s, e) => AppDomainException(e.ExceptionObject as Exception)); TaskScheduler.UnobservedTaskException += ((s, e) => TaskException(e.Exception)); } @@ -24,6 +21,15 @@ namespace NzbDrone.Common.Instrumentation private static void AppDomainException(Exception exception) { + if (exception == null) return; + + if (exception is NullReferenceException && + exception.ToString().Contains("Microsoft.AspNet.SignalR.Transports.TransportHeartbeat.ProcessServerCommand")) + { + Logger.Warn("SignalR Heartbeat error."); + return; + } + Console.WriteLine("EPIC FAIL: {0}", exception); Logger.FatalException("EPIC FAIL: " + exception.Message, exception); } diff --git a/NzbDrone.Common/Instrumentation/LogEventExtensions.cs b/NzbDrone.Common/Instrumentation/LogEventExtensions.cs index cb2fe44f2..373aa9201 100644 --- a/NzbDrone.Common/Instrumentation/LogEventExtensions.cs +++ b/NzbDrone.Common/Instrumentation/LogEventExtensions.cs @@ -13,7 +13,6 @@ namespace NzbDrone.Common.Instrumentation return HashUtil.CalculateCrc(hashSeed); } - public static string GetFormattedMessage(this LogEventInfo logEvent) { var message = logEvent.FormattedMessage; diff --git a/NzbDrone.Common/Instrumentation/LogTargets.cs b/NzbDrone.Common/Instrumentation/LogTargets.cs new file mode 100644 index 000000000..d63520e80 --- /dev/null +++ b/NzbDrone.Common/Instrumentation/LogTargets.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; +using NLog; +using NLog.Config; +using NLog.Targets; +using NzbDrone.Common.EnvironmentInfo; + +namespace NzbDrone.Common.Instrumentation +{ + public static class LogTargets + { + public static void Register(IStartupArguments startupArguments, bool updateApp, bool inConsole) + { + var appFolderInfo = new AppFolderInfo(new DiskProvider(), startupArguments); + + LogManager.Configuration = new LoggingConfiguration(); + + RegisterExceptron(); + + if (updateApp) + { + RegisterLoggly(); + RegisterUpdateFile(appFolderInfo); + } + else + { + if (inConsole && (OsInfo.IsLinux || new RuntimeInfo(null).IsUserInteractive)) + { + RegisterConsole(); + } + + RegisterAppFile(appFolderInfo); + } + + LogManager.ReconfigExistingLoggers(); + } + + private static void RegisterConsole() + { + var level = LogLevel.Trace; + + if (RuntimeInfo.IsProduction) + { + level = LogLevel.Info; + } + + var coloredConsoleTarget = new ColoredConsoleTarget(); + + coloredConsoleTarget.Name = "consoleLogger"; + coloredConsoleTarget.Layout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"; + + var loggingRule = new LoggingRule("*", level, coloredConsoleTarget); + + LogManager.Configuration.AddTarget("console", coloredConsoleTarget); + LogManager.Configuration.LoggingRules.Add(loggingRule); + } + + + const string FileLogLayout = @"${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"; + + private static void RegisterAppFile(IAppFolderInfo appFolderInfo) + { + var fileTarget = new FileTarget(); + + fileTarget.Name = "rollingFileLogger"; + fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), "nzbdrone.txt"); + fileTarget.AutoFlush = true; + fileTarget.KeepFileOpen = false; + fileTarget.ConcurrentWrites = false; + fileTarget.ConcurrentWriteAttemptDelay = 50; + fileTarget.ConcurrentWriteAttempts = 10; + fileTarget.ArchiveAboveSize = 1024000; + fileTarget.MaxArchiveFiles = 5; + fileTarget.EnableFileDelete = true; + fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling; + fileTarget.Layout = FileLogLayout; + + var loggingRule = new LoggingRule("*", LogLevel.Info, fileTarget); + + LogManager.Configuration.AddTarget("appfile", fileTarget); + LogManager.Configuration.LoggingRules.Add(loggingRule); + } + + + + private static void RegisterUpdateFile(IAppFolderInfo appFolderInfo) + { + var fileTarget = new FileTarget(); + + fileTarget.Name = "updateFileLogger"; + fileTarget.FileName = Path.Combine(appFolderInfo.GetUpdateLogFolder(), DateTime.Now.ToString("yy.MM.d-HH.mm") + ".txt"); + fileTarget.AutoFlush = true; + fileTarget.KeepFileOpen = false; + fileTarget.ConcurrentWrites = false; + fileTarget.ConcurrentWriteAttemptDelay = 50; + fileTarget.ConcurrentWriteAttempts = 100; + fileTarget.Layout = FileLogLayout; + + var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget); + + LogManager.Configuration.AddTarget("updateFile", fileTarget); + LogManager.Configuration.LoggingRules.Add(loggingRule); + } + + private static void RegisterExceptron() + { + + var exceptronTarget = new ExceptronTarget(); + var rule = new LoggingRule("*", LogLevel.Warn, exceptronTarget); + + LogManager.Configuration.AddTarget("ExceptronTarget", exceptronTarget); + LogManager.Configuration.LoggingRules.Add(rule); + } + + + private static void RegisterLoggly() + { + var logglyTarger = new LogglyTarget(); + + var rule = new LoggingRule("*", LogLevel.Trace, logglyTarger); + + LogManager.Configuration.AddTarget("LogglyLogger", logglyTarger); + LogManager.Configuration.LoggingRules.Add(rule); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Instrumentation/LogglyTarget.cs b/NzbDrone.Common/Instrumentation/LogglyTarget.cs index 33d953390..8f2817581 100644 --- a/NzbDrone.Common/Instrumentation/LogglyTarget.cs +++ b/NzbDrone.Common/Instrumentation/LogglyTarget.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using NLog; -using NLog.Config; using NLog.Layouts; using NLog.Targets; using NzbDrone.Common.EnvironmentInfo; @@ -13,16 +12,10 @@ namespace NzbDrone.Common.Instrumentation { private Logger _logger; - public void Register(LogLevel minLevel) + public LogglyTarget() { Layout = new SimpleLayout("${callsite:className=false:fileName=false:includeSourcePath=false:methodName=true}"); - - var rule = new LoggingRule("*", minLevel, this); - - LogManager.Configuration.AddTarget("LogglyLogger", this); - LogManager.Configuration.LoggingRules.Add(rule); - LogManager.ConfigurationReloaded += (sender, args) => Register(minLevel); - LogManager.ReconfigExistingLoggers(); + } protected override void InitializeTarget() diff --git a/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs new file mode 100644 index 000000000..c722d5160 --- /dev/null +++ b/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics; +using NLog; + +namespace NzbDrone.Common.Instrumentation +{ + public static class NzbDroneLogger + { + public static Logger GetLogger() + { + string loggerName; + Type declaringType; + int framesToSkip = 1; + do + { + var frame = new StackFrame(framesToSkip, false); + var method = frame.GetMethod(); + declaringType = method.DeclaringType; + if (declaringType == null) + { + loggerName = method.Name; + break; + } + + framesToSkip++; + loggerName = declaringType.Name; + } while (declaringType.Module.Name.Equals("mscorlib.dll", StringComparison.OrdinalIgnoreCase)); + + return LogManager.GetLogger(loggerName); + } + + public static Logger GetLogger(object obj) + { + return LogManager.GetLogger(obj.GetType().Name); + } + + public static Logger GetLogger<T>() + { + return LogManager.GetLogger(typeof(T).Name); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Instrumentation/UpdateLogLayoutRenderer.cs b/NzbDrone.Common/Instrumentation/UpdateLogLayoutRenderer.cs deleted file mode 100644 index 2f55d36de..000000000 --- a/NzbDrone.Common/Instrumentation/UpdateLogLayoutRenderer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.IO; -using System.Text; -using NLog; -using NLog.Config; -using NLog.LayoutRenderers; -using NzbDrone.Common.EnvironmentInfo; - -namespace NzbDrone.Common.Instrumentation -{ - [ThreadAgnostic] - [LayoutRenderer("updateLog")] - public class UpdateLogLayoutRenderer : LayoutRenderer - { - private readonly string _appData; - - public UpdateLogLayoutRenderer() - { - _appData = Path.Combine(new AppFolderInfo(new DiskProvider(), StartupArguments.Instance).GetUpdateLogFolder(), DateTime.Now.ToString("yy.MM.d-HH.mm") + ".txt"); - - } - - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - builder.Append(_appData); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/CommandCompletedEvent.cs b/NzbDrone.Common/Messaging/CommandCompletedEvent.cs deleted file mode 100644 index 613800ae0..000000000 --- a/NzbDrone.Common/Messaging/CommandCompletedEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NzbDrone.Common.Messaging -{ - public class CommandCompletedEvent : IEvent - { - public ICommand Command { get; private set; } - - public CommandCompletedEvent(ICommand command) - { - Command = command; - } - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/CommandFailedEvent.cs b/NzbDrone.Common/Messaging/CommandFailedEvent.cs deleted file mode 100644 index d33ab79f8..000000000 --- a/NzbDrone.Common/Messaging/CommandFailedEvent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace NzbDrone.Common.Messaging -{ - public class CommandFailedEvent : IEvent - { - public ICommand Command { get; private set; } - public Exception Exception { get; private set; } - - public CommandFailedEvent(ICommand command, Exception exception) - { - Command = command; - Exception = exception; - } - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/CommandStartedEvent.cs b/NzbDrone.Common/Messaging/CommandStartedEvent.cs deleted file mode 100644 index 3cb4e7f55..000000000 --- a/NzbDrone.Common/Messaging/CommandStartedEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NzbDrone.Common.Messaging -{ - public class CommandExecutedEvent : IEvent - { - public ICommand Command { get; private set; } - - public CommandExecutedEvent(ICommand command) - { - Command = command; - } - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/ICommand.cs b/NzbDrone.Common/Messaging/ICommand.cs deleted file mode 100644 index d9f8049ba..000000000 --- a/NzbDrone.Common/Messaging/ICommand.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace NzbDrone.Common.Messaging -{ - public interface ICommand : IMessage - { - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/IEvent.cs b/NzbDrone.Common/Messaging/IEvent.cs index 953709c13..00f40b449 100644 --- a/NzbDrone.Common/Messaging/IEvent.cs +++ b/NzbDrone.Common/Messaging/IEvent.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Common.Messaging +namespace NzbDrone.Common.Messaging { public interface IEvent : IMessage { diff --git a/NzbDrone.Common/Messaging/IMessageAggregator.cs b/NzbDrone.Common/Messaging/IMessageAggregator.cs deleted file mode 100644 index 6de5ac3c8..000000000 --- a/NzbDrone.Common/Messaging/IMessageAggregator.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NzbDrone.Common.Messaging -{ - /// <summary> - /// Enables loosely-coupled publication of events. - /// </summary> - public interface IMessageAggregator - { - void PublishEvent<TEvent>(TEvent @event) where TEvent : class, IEvent; - void PublishCommand<TCommand>(TCommand command) where TCommand : class, ICommand; - void PublishCommand(string commandType); - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/MessageExtensions.cs b/NzbDrone.Common/Messaging/MessageExtensions.cs deleted file mode 100644 index 302aad869..000000000 --- a/NzbDrone.Common/Messaging/MessageExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace NzbDrone.Common.Messaging -{ - public static class MessageExtensions - { - public static string GetExecutorName(this Type commandType) - { - if (!typeof(ICommand).IsAssignableFrom(commandType)) - { - throw new ArgumentException("commandType must implement ICommand"); - } - - return string.Format("I{0}Executor", commandType.Name); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/TestCommand.cs b/NzbDrone.Common/Messaging/TestCommand.cs deleted file mode 100644 index 1a54d5764..000000000 --- a/NzbDrone.Common/Messaging/TestCommand.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NzbDrone.Common.Messaging -{ - public class TestCommand : ICommand - { - public TestCommand() - { - Duration = 4000; - } - - public int Duration { get; set; } - - } -} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/TestCommandExecutor.cs b/NzbDrone.Common/Messaging/TestCommandExecutor.cs deleted file mode 100644 index be0ea4ea4..000000000 --- a/NzbDrone.Common/Messaging/TestCommandExecutor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading; - -namespace NzbDrone.Common.Messaging -{ - public class TestCommandExecutor : IExecute<TestCommand> - { - public void Execute(TestCommand message) - { - Thread.Sleep(message.Duration); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index c1b17236f..d55670a61 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -41,7 +41,7 @@ </Reference> <Reference Include="Loggly, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\loggly-csharp.2.2\lib\Loggly.dll</HintPath> + <HintPath>..\packages\loggly-csharp.2.3\lib\net35\Loggly.dll</HintPath> </Reference> <Reference Include="Microsoft.CSharp" /> <Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> @@ -92,6 +92,11 @@ <Compile Include="IEnumerableExtensions.cs" /> <Compile Include="Instrumentation\GlobalExceptionHandlers.cs" /> <Compile Include="Instrumentation\ExceptronTarget.cs" /> + <Compile Include="Instrumentation\LogEventExtensions.cs" /> + <Compile Include="Instrumentation\NzbDroneLogger.cs" /> + <Compile Include="Instrumentation\LogTargets.cs" /> + <Compile Include="Messaging\IEvent.cs" /> + <Compile Include="Messaging\IMessage.cs" /> <Compile Include="PathEqualityComparer.cs" /> <Compile Include="Services.cs" /> <Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" /> @@ -99,23 +104,8 @@ <Compile Include="StringExtensions.cs" /> <Compile Include="EnsureThat\TypeParam.cs" /> <Compile Include="HashUtil.cs" /> - <Compile Include="Instrumentation\ApplicationLogLayoutRenderer.cs" /> - <Compile Include="Instrumentation\DirSeparatorLayoutRenderer.cs" /> - <Compile Include="Instrumentation\LogEventExtensions.cs" /> <Compile Include="Instrumentation\LogglyTarget.cs" /> - <Compile Include="Instrumentation\UpdateLogLayoutRenderer.cs" /> <Compile Include="Serializer\Json.cs" /> - <Compile Include="Messaging\CommandCompletedEvent.cs" /> - <Compile Include="Messaging\CommandStartedEvent.cs" /> - <Compile Include="Messaging\CommandFailedEvent.cs" /> - <Compile Include="Messaging\IExecute.cs" /> - <Compile Include="Messaging\ICommand.cs" /> - <Compile Include="Messaging\IMessage.cs" /> - <Compile Include="Messaging\IProcessMessage.cs" /> - <Compile Include="Messaging\MessageAggregator.cs" /> - <Compile Include="Messaging\IEvent.cs" /> - <Compile Include="Messaging\IMessageAggregator.cs" /> - <Compile Include="Messaging\IHandle.cs" /> <Compile Include="Expansive\CircularReferenceException.cs" /> <Compile Include="Expansive\Expansive.cs" /> <Compile Include="Expansive\PatternStyle.cs" /> @@ -123,9 +113,6 @@ <Compile Include="Expansive\TreeNode.cs" /> <Compile Include="Expansive\TreeNodeList.cs" /> <Compile Include="Instrumentation\VersionLayoutRenderer.cs" /> - <Compile Include="Messaging\MessageExtensions.cs" /> - <Compile Include="Messaging\TestCommand.cs" /> - <Compile Include="Messaging\TestCommandExecutor.cs" /> <Compile Include="Reflection\ReflectionExtensions.cs" /> <Compile Include="ServiceFactory.cs" /> <Compile Include="HttpProvider.cs" /> diff --git a/NzbDrone.Common/NzbDrone.Common.ncrunchproject b/NzbDrone.Common/NzbDrone.Common.ncrunchproject index 8641d3614..1a2228e7f 100644 --- a/NzbDrone.Common/NzbDrone.Common.ncrunchproject +++ b/NzbDrone.Common/NzbDrone.Common.ncrunchproject @@ -1,8 +1,8 @@ <ProjectConfiguration> <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> - <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> + <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,8 +12,11 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration /> - <ProxyProcessPath /> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> + <ProxyProcessPath></ProxyProcessPath> + <UseCPUArchitecture>x86</UseCPUArchitecture> + <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> + <BuildProcessArchitecture>x86</BuildProcessArchitecture> </ProjectConfiguration> \ No newline at end of file diff --git a/NzbDrone.Common/PathEqualityComparer.cs b/NzbDrone.Common/PathEqualityComparer.cs index 2bf854727..32d6bf07c 100644 --- a/NzbDrone.Common/PathEqualityComparer.cs +++ b/NzbDrone.Common/PathEqualityComparer.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using NzbDrone.Common.EnvironmentInfo; namespace NzbDrone.Common diff --git a/NzbDrone.Common/ProcessProvider.cs b/NzbDrone.Common/ProcessProvider.cs index 8df4198e8..e93edd781 100644 --- a/NzbDrone.Common/ProcessProvider.cs +++ b/NzbDrone.Common/ProcessProvider.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Model; namespace NzbDrone.Common @@ -26,7 +27,7 @@ namespace NzbDrone.Common public class ProcessProvider : IProcessProvider { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public const string NZB_DRONE_PROCESS_NAME = "NzbDrone"; public const string NZB_DRONE_CONSOLE_PROCESS_NAME = "NzbDrone.Console"; @@ -212,7 +213,7 @@ namespace NzbDrone.Common return new ProcessInfo { Id = process.Id, - StartPath = process.MainModule.FileName, + StartPath = GetExeFileName(process), Name = process.ProcessName }; } @@ -224,6 +225,17 @@ namespace NzbDrone.Common return null; } + + private static string GetExeFileName(Process process) + { + if (process.MainModule.FileName != "mono.exe") + { + return process.MainModule.FileName; + } + + return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName; + } + private void Kill(int processId) { var process = Process.GetProcesses().FirstOrDefault(p => p.Id == processId); diff --git a/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs b/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs index 1be964380..809b0fe74 100644 --- a/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs +++ b/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs @@ -3,12 +3,13 @@ using System.Net.Security; using System.Security.Cryptography.X509Certificates; using NLog; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Common.Security { public static class IgnoreCertErrorPolicy { - private static readonly Logger Logger = LogManager.GetLogger("CertPolicy"); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public static void Register() { diff --git a/NzbDrone.Common/ServiceProvider.cs b/NzbDrone.Common/ServiceProvider.cs index 7287e36df..8c2206a63 100644 --- a/NzbDrone.Common/ServiceProvider.cs +++ b/NzbDrone.Common/ServiceProvider.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.ServiceProcess; using NLog; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Common { @@ -25,7 +26,7 @@ namespace NzbDrone.Common { public const string NZBDRONE_SERVICE_NAME = "NzbDrone"; - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public virtual bool ServiceExist(string name) { diff --git a/NzbDrone.Common/TPL/TaskExtensions.cs b/NzbDrone.Common/TPL/TaskExtensions.cs index e655ac86f..855ae225c 100644 --- a/NzbDrone.Common/TPL/TaskExtensions.cs +++ b/NzbDrone.Common/TPL/TaskExtensions.cs @@ -1,11 +1,12 @@ using System.Threading.Tasks; using NLog; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Common.TPL { public static class TaskExtensions { - private static readonly Logger Logger = LogManager.GetLogger("TaskExtensions"); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public static Task LogExceptions(this Task task) { diff --git a/NzbDrone.Common/TinyIoC.cs b/NzbDrone.Common/TinyIoC.cs index 90fff373f..70ba36762 100644 --- a/NzbDrone.Common/TinyIoC.cs +++ b/NzbDrone.Common/TinyIoC.cs @@ -70,7 +70,6 @@ namespace TinyIoC { using System; using System.Collections.Generic; - using System.Collections.ObjectModel; using System.Linq; using System.Reflection; diff --git a/NzbDrone.Common/packages.config b/NzbDrone.Common/packages.config index c1ce89767..7252d18f7 100644 --- a/NzbDrone.Common/packages.config +++ b/NzbDrone.Common/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="loggly-csharp" version="2.2" targetFramework="net40" /> + <package id="loggly-csharp" version="2.3" targetFramework="net40" /> <package id="Newtonsoft.Json" version="5.0.6" targetFramework="net40" /> <package id="NLog" version="2.0.1.2" targetFramework="net40" /> <package id="SharpZipLib" version="0.86.0" targetFramework="net40" /> diff --git a/NzbDrone.Console/ConsoleApp.cs b/NzbDrone.Console/ConsoleApp.cs index 3119cc811..68de86f25 100644 --- a/NzbDrone.Console/ConsoleApp.cs +++ b/NzbDrone.Console/ConsoleApp.cs @@ -2,23 +2,29 @@ using System.Threading; using NLog; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Instrumentation; using NzbDrone.Host; namespace NzbDrone.Console { public static class ConsoleApp { + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); + public static void Main(string[] args) { try { - Bootstrap.Start(new StartupArguments(args), new ConsoleAlerts()); + var startupArgs = new StartupArguments(args); + LogTargets.Register(startupArgs, false, true); + Bootstrap.Start(startupArgs, new ConsoleAlerts()); } catch (TerminateApplicationException) { } catch (Exception e) { + Logger.FatalException("EPIC FAIL!", e); System.Console.ReadLine(); } @@ -28,4 +34,4 @@ namespace NzbDrone.Console } } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs b/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs index 98ad8b504..587e70697 100644 --- a/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs +++ b/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs @@ -3,7 +3,6 @@ using FluentAssertions; using NUnit.Framework; using Newtonsoft.Json; using NzbDrone.Common; -using NzbDrone.Core.Configuration; using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.Test.Framework; @@ -13,7 +12,7 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene public class SceneMappingProxyFixture : CoreTest<SceneMappingProxy> { - private const string SCENE_MAPPING_URL = "http://services.nzbdrone.com/SceneMapping/Active"; + private const string SCENE_MAPPING_URL = "http://services.nzbdrone.com/v1/SceneMapping"; [Test] public void fetch_should_return_list_of_mappings() @@ -31,7 +30,6 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene mappings.Should().NotContain(c => c.TvdbId == 0); } - [Test] public void should_throw_on_server_error() { diff --git a/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperFixture.cs b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperFixture.cs deleted file mode 100644 index c940ac7b2..000000000 --- a/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperFixture.cs +++ /dev/null @@ -1,84 +0,0 @@ -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Datastore.Migration.Framework; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.Datastore -{ - [TestFixture] - public class SQLiteMigrationHelperFixture : DbTest - { - private SQLiteMigrationHelper _subject; - - [SetUp] - public void SetUp() - { - _subject = Mocker.Resolve<SQLiteMigrationHelper>(); - } - - - - [Test] - public void should_parse_existing_columns() - { - var columns = _subject.GetColumns("Series"); - - columns.Should().NotBeEmpty(); - - columns.Values.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name)); - columns.Values.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Schema)); - } - - [Test] - public void should_create_table_from_column_list() - { - var columns = _subject.GetColumns("Series"); - columns.Remove("Title"); - - _subject.CreateTable("Series_New", columns.Values); - - var newColumns = _subject.GetColumns("Series_New"); - - newColumns.Values.Should().HaveSameCount(columns.Values); - newColumns.Should().NotContainKey("Title"); - } - - [Test] - public void should_get_zero_count_on_empty_table() - { - _subject.GetRowCount("Series").Should().Be(0); - } - - - [Test] - public void should_be_able_to_transfer_empty_tables() - { - var columns = _subject.GetColumns("Series"); - columns.Remove("Title"); - - _subject.CreateTable("Series_New", columns.Values); - - - _subject.CopyData("Series", "Series_New", columns.Values); - } - - [Test] - public void should_transfer_table_with_data() - { - var originalEpisodes = Builder<Episode>.CreateListOfSize(10).BuildListOfNew(); - - Mocker.Resolve<EpisodeRepository>().InsertMany(originalEpisodes); - - var columns = _subject.GetColumns("Episodes"); - columns.Remove("Title"); - - _subject.CreateTable("Episodes_New", columns.Values); - - _subject.CopyData("Episodes", "Episodes_New", columns.Values); - - _subject.GetRowCount("Episodes_New").Should().Be(originalEpisodes.Count); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs new file mode 100644 index 000000000..7e1510259 --- /dev/null +++ b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using System.Linq; + +namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests +{ + [TestFixture] + public class AlterFixture : DbTest + { + private SqLiteMigrationHelper _subject; + + [SetUp] + public void SetUp() + { + _subject = Mocker.Resolve<SqLiteMigrationHelper>(); + } + + [Test] + public void should_parse_existing_columns() + { + var columns = _subject.GetColumns("Series"); + + columns.Should().NotBeEmpty(); + + columns.Values.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name)); + columns.Values.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Schema)); + } + + [Test] + public void should_create_table_from_column_list() + { + var columns = _subject.GetColumns("Series"); + columns.Remove("Title"); + + _subject.CreateTable("Series_New", columns.Values, new List<SQLiteIndex>()); + + var newColumns = _subject.GetColumns("Series_New"); + + newColumns.Values.Should().HaveSameCount(columns.Values); + newColumns.Should().NotContainKey("Title"); + } + + + [Test] + public void should_be_able_to_transfer_empty_tables() + { + var columns = _subject.GetColumns("Series"); + var indexes = _subject.GetIndexes("Series"); + columns.Remove("Title"); + + _subject.CreateTable("Series_New", columns.Values, indexes); + + + _subject.CopyData("Series", "Series_New", columns.Values); + } + + [Test] + public void should_transfer_table_with_data() + { + var originalEpisodes = Builder<Episode>.CreateListOfSize(10).BuildListOfNew(); + + Mocker.Resolve<EpisodeRepository>().InsertMany(originalEpisodes); + + var columns = _subject.GetColumns("Episodes"); + var indexes = _subject.GetIndexes("Episodes"); + + columns.Remove("Title"); + + _subject.CreateTable("Episodes_New", columns.Values, indexes); + + _subject.CopyData("Episodes", "Episodes_New", columns.Values); + + _subject.GetRowCount("Episodes_New").Should().Be(originalEpisodes.Count); + } + + [Test] + public void should_read_existing_indexes() + { + var indexes = _subject.GetIndexes("QualitySizes"); + + indexes.Should().NotBeEmpty(); + + indexes.Should().OnlyContain(c => c != null); + indexes.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.Column)); + indexes.Should().OnlyContain(c => c.Table == "QualitySizes"); + indexes.Should().OnlyContain(c => c.Unique); + } + + [Test] + public void should_add_indexes_when_creating_new_table() + { + var columns = _subject.GetColumns("QualitySizes"); + var indexes = _subject.GetIndexes("QualitySizes"); + + + _subject.CreateTable("QualityB", columns.Values, indexes); + + + var newIndexes = _subject.GetIndexes("QualityB"); + + newIndexes.Should().HaveSameCount(indexes); + newIndexes.Select(c=>c.Column).Should().BeEquivalentTo(indexes.Select(c=>c.Column)); + } + + + [Test] + public void should_be_able_to_create_table_with_new_indexes() + { + var columns = _subject.GetColumns("Series"); + columns.Remove("Title"); + + _subject.CreateTable("Series_New", columns.Values, new List<SQLiteIndex>{new SQLiteIndex{Column = "AirTime", Table = "Series_New", Unique = true}}); + + var newColumns = _subject.GetColumns("Series_New"); + var newIndexes = _subject.GetIndexes("Series_New"); + + newColumns.Values.Should().HaveSameCount(columns.Values); + newIndexes.Should().Contain(i=>i.Column == "AirTime"); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs new file mode 100644 index 000000000..d193fb1ec --- /dev/null +++ b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs @@ -0,0 +1,41 @@ +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests +{ + [TestFixture] + public class DuplicateFixture : DbTest + { + private SqLiteMigrationHelper _subject; + + [SetUp] + public void SetUp() + { + _subject = Mocker.Resolve<SqLiteMigrationHelper>(); + } + + + [Test] + public void get_duplicates() + { + var series = Builder<Series>.CreateListOfSize(10) + .Random(3) + .With(c => c.QualityProfileId = 100) + .BuildListOfNew(); + + Db.InsertMany(series); + + var duplicates = _subject.GetDuplicates<int>("series", "QualityProfileId").ToList(); + + + duplicates.Should().HaveCount(1); + duplicates.First().Should().HaveCount(3); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs index 32528beaf..5f511e3dc 100644 --- a/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs +++ b/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs @@ -26,14 +26,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { parseResultMulti = new RemoteEpisode { - Report = new ReportInfo(), + Release = new ReleaseInfo(), ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, true) }, Episodes = new List<Episode> { new Episode(), new Episode() } }; parseResultSingle = new RemoteEpisode { - Report = new ReportInfo(), + Release = new ReleaseInfo(), ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, true) }, Episodes = new List<Episode> { new Episode() } @@ -59,7 +59,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_true_single_episode_not_first_or_last_30_minute() { parseResultSingle.Series = series30minutes; - parseResultSingle.Report.Size = 184572800; + parseResultSingle.Release.Size = 184572800; Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_true_single_episode_not_first_or_last_60_minute() { parseResultSingle.Series = series60minutes; - parseResultSingle.Report.Size = 368572800; + parseResultSingle.Release.Size = 368572800; Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_false_single_episode_not_first_or_last_30_minute() { parseResultSingle.Series = series30minutes; - parseResultSingle.Report.Size = 1.Gigabytes(); + parseResultSingle.Release.Size = 1.Gigabytes(); Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -116,7 +116,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_false_single_episode_not_first_or_last_60_minute() { parseResultSingle.Series = series60minutes; - parseResultSingle.Report.Size = 1.Gigabytes(); + parseResultSingle.Release.Size = 1.Gigabytes(); Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_true_multi_episode_not_first_or_last_30_minute() { parseResultMulti.Series = series30minutes; - parseResultMulti.Report.Size = 184572800; + parseResultMulti.Release.Size = 184572800; Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -152,7 +152,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_true_multi_episode_not_first_or_last_60_minute() { parseResultMulti.Series = series60minutes; - parseResultMulti.Report.Size = 368572800; + parseResultMulti.Release.Size = 368572800; Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -171,7 +171,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_false_multi_episode_not_first_or_last_30_minute() { parseResultMulti.Series = series30minutes; - parseResultMulti.Report.Size = 1.Gigabytes(); + parseResultMulti.Release.Size = 1.Gigabytes(); Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -190,7 +190,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_false_multi_episode_not_first_or_last_60_minute() { parseResultMulti.Series = series60minutes; - parseResultMulti.Report.Size = 10.Gigabytes(); + parseResultMulti.Release.Size = 10.Gigabytes(); Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -209,7 +209,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_true_single_episode_first_30_minute() { parseResultSingle.Series = series30minutes; - parseResultSingle.Report.Size = 184572800; + parseResultSingle.Release.Size = 184572800; Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -228,7 +228,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_true_single_episode_first_60_minute() { parseResultSingle.Series = series60minutes; - parseResultSingle.Report.Size = 368572800; + parseResultSingle.Release.Size = 368572800; Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -247,7 +247,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void IsAcceptableSize_false_single_episode_first_30_minute() { parseResultSingle.Series = series30minutes; - parseResultSingle.Report.Size = 1.Gigabytes(); + parseResultSingle.Release.Size = 1.Gigabytes(); Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -268,7 +268,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests parseResultSingle.Series = series60minutes; - parseResultSingle.Report.Size = 10.Gigabytes(); + parseResultSingle.Release.Size = 10.Gigabytes(); Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -289,7 +289,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests parseResultSingle.Series = series30minutes; - parseResultSingle.Report.Size = 18457280000; + parseResultSingle.Release.Size = 18457280000; qualityType.MaxSize = 0; Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -311,7 +311,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests parseResultSingle.Series = series60minutes; - parseResultSingle.Report.Size = 36857280000; + parseResultSingle.Release.Size = 36857280000; qualityType.MaxSize = 0; Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType); @@ -334,7 +334,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests parseResultSingle.Series = series60minutes; parseResultSingle.Series.SeriesType = SeriesTypes.Daily; - parseResultSingle.Report.Size = 300.Megabytes(); + parseResultSingle.Release.Size = 300.Megabytes(); qualityType.MaxSize = (int)600.Megabytes(); diff --git a/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs new file mode 100644 index 000000000..7ff36a03b --- /dev/null +++ b/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs @@ -0,0 +1,48 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Tv; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class CutoffSpecificationFixture : CoreTest<QualityUpgradableSpecification> + { + [Test] + public void should_return_true_if_current_episode_is_less_than_cutoff() + { + Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.Bluray1080p }, + new QualityModel(Quality.DVD, true)).Should().BeTrue(); + } + + [Test] + public void should_return_false_if_current_episode_is_equal_to_cutoff() + { + Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p }, + new QualityModel(Quality.HDTV720p, true)).Should().BeFalse(); + } + + [Test] + public void should_return_false_if_current_episode_is_greater_than_cutoff() + { + Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p }, + new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse(); + } + + [Test] + public void should_return_true_when_new_episode_is_proper_but_existing_is_not() + { + Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p }, + new QualityModel(Quality.HDTV720p, false), new QualityModel(Quality.HDTV720p, true)).Should().BeTrue(); + } + + [Test] + public void should_return_false_if_cutoff_is_met_and_quality_is_higher() + { + Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p }, + new QualityModel(Quality.HDTV720p, true), new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs index 171387772..d250c4d98 100644 --- a/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs +++ b/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs @@ -4,6 +4,7 @@ using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; @@ -15,7 +16,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [TestFixture] public class DownloadDecisionMakerFixture : CoreTest<DownloadDecisionMaker> { - private List<ReportInfo> _reports; + private List<ReleaseInfo> _reports; private RemoteEpisode _remoteEpisode; private Mock<IDecisionEngineSpecification> _pass1; @@ -56,10 +57,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(false); _fail3.Setup(c => c.RejectionReason).Returns("_fail3"); - _reports = new List<ReportInfo> { new ReportInfo { Title = "The.Office.S03E115.DVDRip.XviD-OSiTV" } }; + _reports = new List<ReleaseInfo> { new ReleaseInfo { Title = "The.Office.S03E115.DVDRip.XviD-OSiTV" } }; _remoteEpisode = new RemoteEpisode { Series = new Series() }; - Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>())) + Mocker.GetMock<IParsingService>() + .Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>())) .Returns(_remoteEpisode); } @@ -130,7 +132,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests var results = Subject.GetRssDecision(_reports).ToList(); - Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>()), Times.Never()); + Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Never()); _pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never()); _pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never()); @@ -146,7 +148,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests var results = Subject.GetRssDecision(_reports).ToList(); - Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>()), Times.Never()); + Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Never()); _pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never()); _pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never()); @@ -174,19 +176,19 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenSpecifications(_pass1); - Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>())) + Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>())) .Throws<TestException>(); - _reports = new List<ReportInfo> + _reports = new List<ReleaseInfo> { - new ReportInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"}, - new ReportInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"}, - new ReportInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"} + new ReleaseInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"}, + new ReleaseInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"}, + new ReleaseInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"} }; Subject.GetRssDecision(_reports); - Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>()), Times.Exactly(_reports.Count)); + Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Exactly(_reports.Count)); ExceptionVerification.ExpectedErrors(3); } diff --git a/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs index f9bcf475e..cdbce8077 100644 --- a/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs +++ b/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { _parseResult = new RemoteEpisode { - Report = new ReportInfo + Release = new ReleaseInfo { Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR" } diff --git a/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradableSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradableSpecificationFixture.cs deleted file mode 100644 index c7eb4bf4b..000000000 --- a/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradableSpecificationFixture.cs +++ /dev/null @@ -1,34 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; -using NzbDrone.Core.DecisionEngine; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.DecisionEngineTests -{ - [TestFixture] - public class QualityUpgradableSpecificationFixture : CoreTest<QualityUpgradableSpecification> - { - [Test] - public void IsUpgradePossible_should_return_true_if_current_episode_is_less_than_cutoff() - { - Subject.IsUpgradable(new QualityProfile { Cutoff = Quality.Bluray1080p }, - new QualityModel(Quality.DVD, true)).Should().BeTrue(); - } - - [Test] - public void IsUpgradePossible_should_return_false_if_current_episode_is_equal_to_cutoff() - { - Subject.IsUpgradable(new QualityProfile { Cutoff = Quality.HDTV720p }, - new QualityModel(Quality.HDTV720p, true)).Should().BeFalse(); - } - - [Test] - public void IsUpgradePossible_should_return_false_if_current_episode_is_greater_than_cutoff() - { - Subject.IsUpgradable(new QualityProfile { Cutoff = Quality.HDTV720p }, - new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse(); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs index 2a489ee8a..cc631b871 100644 --- a/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs +++ b/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs @@ -17,7 +17,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests new object[] { Quality.SDTV, false, Quality.SDTV, true, Quality.SDTV, true }, new object[] { Quality.WEBDL720p, false, Quality.WEBDL720p, true, Quality.WEBDL720p, true }, new object[] { Quality.SDTV, false, Quality.SDTV, false, Quality.SDTV, false }, - new object[] { Quality.SDTV, false, Quality.DVD, true, Quality.SDTV, false }, new object[] { Quality.WEBDL720p, false, Quality.HDTV720p, true, Quality.Bluray720p, false }, new object[] { Quality.WEBDL720p, false, Quality.HDTV720p, true, Quality.WEBDL720p, false }, new object[] { Quality.WEBDL720p, false, Quality.WEBDL720p, false, Quality.WEBDL720p, false }, @@ -37,7 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenAutoDownloadPropers(true); - Subject.IsUpgradable(new QualityProfile() { Cutoff = cutoff }, new QualityModel(current, currentProper), new QualityModel(newQuality, newProper)) + Subject.IsUpgradable(new QualityModel(current, currentProper), new QualityModel(newQuality, newProper)) .Should().Be(expected); } @@ -46,8 +45,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenAutoDownloadPropers(false); - Subject.IsUpgradable(new QualityProfile { Cutoff = Quality.Bluray1080p }, - new QualityModel(Quality.DVD, true), + Subject.IsUpgradable(new QualityModel(Quality.DVD, true), new QualityModel(Quality.DVD, false)).Should().BeFalse(); } } diff --git a/NzbDrone.Core.Test/DecisionEngineTests/RetentionSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/RetentionSpecificationFixture.cs index 7a61aa04a..45db8a529 100644 --- a/NzbDrone.Core.Test/DecisionEngineTests/RetentionSpecificationFixture.cs +++ b/NzbDrone.Core.Test/DecisionEngineTests/RetentionSpecificationFixture.cs @@ -1,4 +1,5 @@ -using FluentAssertions; +using System; +using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Configuration; using NzbDrone.Core.DecisionEngine.Specifications; @@ -19,9 +20,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { parseResult = new RemoteEpisode { - Report = new ReportInfo + Release = new ReleaseInfo { - Age = 100 + PublishDate = DateTime.Now.AddDays(-100) } }; } diff --git a/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs new file mode 100644 index 000000000..b95925ced --- /dev/null +++ b/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.DecisionEngine.Specifications.RssSync; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Tv; +using NzbDrone.Core.DecisionEngine; + +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync +{ + [TestFixture] + + public class ProperSpecificationFixture : CoreTest<ProperSpecification> + { + private RemoteEpisode _parseResultMulti; + private RemoteEpisode _parseResultSingle; + private EpisodeFile _firstFile; + private EpisodeFile _secondFile; + + [SetUp] + public void Setup() + { + Mocker.Resolve<QualityUpgradableSpecification>(); + + _firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, false), DateAdded = DateTime.Now }; + _secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, false), DateAdded = DateTime.Now }; + + var singleEpisodeList = new List<Episode> { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } }; + var doubleEpisodeList = new List<Episode> { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } }; + + var fakeSeries = Builder<Series>.CreateNew() + .With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p }) + .Build(); + + _parseResultMulti = new RemoteEpisode + { + Series = fakeSeries, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) }, + Episodes = doubleEpisodeList + }; + + _parseResultSingle = new RemoteEpisode + { + Series = fakeSeries, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) }, + Episodes = singleEpisodeList + }; + } + + private void WithFirstFileUpgradable() + { + _firstFile.Quality = new QualityModel(Quality.SDTV); + } + + private void GivenAutoDownloadPropers() + { + Mocker.GetMock<IConfigService>() + .Setup(s => s.AutoDownloadPropers) + .Returns(true); + } + + [Test] + public void should_return_false_when_episodeFile_was_added_more_than_7_days_ago() + { + _firstFile.Quality.Quality = Quality.DVD; + + _firstFile.DateAdded = DateTime.Today.AddDays(-30); + Subject.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_first_episodeFile_was_added_more_than_7_days_ago() + { + _firstFile.Quality.Quality = Quality.DVD; + _secondFile.Quality.Quality = Quality.DVD; + + _firstFile.DateAdded = DateTime.Today.AddDays(-30); + Subject.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_second_episodeFile_was_added_more_than_7_days_ago() + { + _firstFile.Quality.Quality = Quality.DVD; + _secondFile.Quality.Quality = Quality.DVD; + + _secondFile.DateAdded = DateTime.Today.AddDays(-30); + Subject.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse(); + } + + [Test] + public void should_return_true_when_episodeFile_was_added_more_than_7_days_ago_but_proper_is_for_better_quality() + { + WithFirstFileUpgradable(); + + _firstFile.DateAdded = DateTime.Today.AddDays(-30); + Subject.IsSatisfiedBy(_parseResultSingle, null).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_episodeFile_was_added_more_than_7_days_ago_but_is_for_search() + { + WithFirstFileUpgradable(); + + _firstFile.DateAdded = DateTime.Today.AddDays(-30); + Subject.IsSatisfiedBy(_parseResultSingle, new SingleEpisodeSearchCriteria()).Should().BeTrue(); + } + + [Test] + public void should_return_false_when_proper_but_auto_download_propers_is_false() + { + _firstFile.Quality.Quality = Quality.DVD; + + _firstFile.DateAdded = DateTime.Today; + Subject.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse(); + } + + [Test] + public void should_return_true_when_episodeFile_was_added_today() + { + GivenAutoDownloadPropers(); + + _firstFile.Quality.Quality = Quality.DVD; + + _firstFile.DateAdded = DateTime.Today; + Subject.IsSatisfiedBy(_parseResultSingle, null).Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs index db083a6b9..eb85c757d 100644 --- a/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs +++ b/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs @@ -4,7 +4,6 @@ using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; @@ -124,52 +123,5 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, false); _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse(); } - - [Test] - public void should_return_false_when_episodeFile_was_added_more_than_7_days_ago() - { - _firstFile.Quality.Quality = Quality.DVD; - - _firstFile.DateAdded = DateTime.Today.AddDays(-30); - _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse(); - } - - [Test] - public void should_return_false_when_first_episodeFile_was_added_more_than_7_days_ago() - { - _firstFile.Quality.Quality = Quality.DVD; - _secondFile.Quality.Quality = Quality.DVD; - - _firstFile.DateAdded = DateTime.Today.AddDays(-30); - _upgradeDisk.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse(); - } - - [Test] - public void should_return_false_when_second_episodeFile_was_added_more_than_7_days_ago() - { - _firstFile.Quality.Quality = Quality.DVD; - _secondFile.Quality.Quality = Quality.DVD; - - _secondFile.DateAdded = DateTime.Today.AddDays(-30); - _upgradeDisk.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse(); - } - - [Test] - public void should_return_true_when_episodeFile_was_added_more_than_7_days_ago_but_proper_is_for_better_quality() - { - WithFirstFileUpgradable(); - - _firstFile.DateAdded = DateTime.Today.AddDays(-30); - _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Should().BeTrue(); - } - - [Test] - public void should_return_true_when_episodeFile_was_added_more_than_7_days_ago_but_is_for_search() - { - WithFirstFileUpgradable(); - - _firstFile.DateAdded = DateTime.Today.AddDays(-30); - _upgradeDisk.IsSatisfiedBy(_parseResultSingle, new SingleEpisodeSearchCriteria()).Should().BeTrue(); - } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs index 8c5c5ecb7..f13c81ccb 100644 --- a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs @@ -4,7 +4,7 @@ using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; -using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Download; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; @@ -34,8 +34,8 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests remoteEpisode.Episodes = new List<Episode>(); remoteEpisode.Episodes.AddRange(episodes); - remoteEpisode.Report = new ReportInfo(); - remoteEpisode.Report.Age = 0; + remoteEpisode.Release = new ReleaseInfo(); + remoteEpisode.Release.PublishDate = DateTime.UtcNow; return remoteEpisode; } diff --git a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs index d1be02648..370142d75 100644 --- a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Download; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; @@ -32,9 +33,9 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests remoteEpisode.Episodes = new List<Episode>(); remoteEpisode.Episodes.AddRange(episodes); - remoteEpisode.Report = new ReportInfo(); - remoteEpisode.Report.Age = Age; - remoteEpisode.Report.Size = size; + remoteEpisode.Release = new ReleaseInfo(); + remoteEpisode.Release.PublishDate = DateTime.Now.AddDays(-Age); + remoteEpisode.Release.Size = size; return remoteEpisode; } @@ -110,9 +111,9 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests public void should_order_by_smallest_rounded_to_200mb_then_age() { var remoteEpisodeSd = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.SDTV), size: 100.Megabytes(), Age: 1); - var remoteEpisodeHdSmallOld = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size:1200.Megabytes(), Age:1000); - var remoteEpisodeHdSmallYounge = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size:1250.Megabytes(), Age:10); - var remoteEpisodeHdLargeYounge = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size:3000.Megabytes(), Age:1); + var remoteEpisodeHdSmallOld = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 1200.Megabytes(), Age: 1000); + var remoteEpisodeHdSmallYounge = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 1250.Megabytes(), Age: 10); + var remoteEpisodeHdLargeYounge = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 3000.Megabytes(), Age: 1); var decisions = new List<DownloadDecision>(); decisions.Add(new DownloadDecision(remoteEpisodeSd)); diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/BlackholeProviderFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/BlackholeProviderFixture.cs index 6229aaf8a..9de3178b9 100644 --- a/NzbDrone.Core.Test/Download/DownloadClientTests/BlackholeProviderFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadClientTests/BlackholeProviderFixture.cs @@ -30,9 +30,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests Mocker.GetMock<IConfigService>().SetupGet(c => c.BlackholeFolder).Returns(_blackHoleFolder); _remoteEpisode = new RemoteEpisode(); - _remoteEpisode.Report = new ReportInfo(); - _remoteEpisode.Report.Title = _title; - _remoteEpisode.Report.NzbUrl = _nzbUrl; + _remoteEpisode.Release = new ReleaseInfo(); + _remoteEpisode.Release.Title = _title; + _remoteEpisode.Release.DownloadUrl = _nzbUrl; } private void WithExistingFile() @@ -58,7 +58,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests { var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]"; var expectedFilename = Path.Combine(_blackHoleFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb"); - _remoteEpisode.Report.Title = illegalTitle; + _remoteEpisode.Release.Title = illegalTitle; Subject.DownloadNzb(_remoteEpisode); diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs index 638613108..c897df251 100644 --- a/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using FizzWare.NBuilder; -using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Common; @@ -31,9 +30,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetProviderTests fakeConfig.SetupGet(c => c.NzbgetRecentTvPriority).Returns(PriorityType.High); _remoteEpisode = new RemoteEpisode(); - _remoteEpisode.Report = new ReportInfo(); - _remoteEpisode.Report.Title = _title; - _remoteEpisode.Report.NzbUrl = _url; + _remoteEpisode.Release = new ReleaseInfo(); + _remoteEpisode.Release.Title = _title; + _remoteEpisode.Release.DownloadUrl = _url; _remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1) .All() diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs index 66b3695cc..cfac3a99a 100644 --- a/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs @@ -34,9 +34,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests Mocker.GetMock<IConfigService>().SetupGet(c => c.DownloadedEpisodesFolder).Returns(_sabDrop); _remoteEpisode = new RemoteEpisode(); - _remoteEpisode.Report = new ReportInfo(); - _remoteEpisode.Report.Title = _title; - _remoteEpisode.Report.NzbUrl = _nzbUrl; + _remoteEpisode.Release = new ReleaseInfo(); + _remoteEpisode.Release.Title = _title; + _remoteEpisode.Release.DownloadUrl = _nzbUrl; _remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); _remoteEpisode.ParsedEpisodeInfo.FullSeason = false; @@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests [Test] public void should_throw_if_full_season_download() { - _remoteEpisode.Report.Title = "30 Rock - Season 1"; + _remoteEpisode.Release.Title = "30 Rock - Season 1"; _remoteEpisode.ParsedEpisodeInfo.FullSeason = true; Assert.Throws<NotImplementedException>(() => Subject.DownloadNzb(_remoteEpisode)); @@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests { var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]"; var expectedFilename = Path.Combine(_pneumaticFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb"); - _remoteEpisode.Report.Title = illegalTitle; + _remoteEpisode.Release.Title = illegalTitle; Subject.DownloadNzb(_remoteEpisode); diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs index 111347602..f23a2b762 100644 --- a/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs @@ -11,7 +11,6 @@ using NzbDrone.Core.Download.Clients.Sabnzbd; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; -using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests { @@ -36,9 +35,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests fakeConfig.SetupGet(c => c.SabTvCategory).Returns("tv"); _remoteEpisode = new RemoteEpisode(); - _remoteEpisode.Report = new ReportInfo(); - _remoteEpisode.Report.Title = TITLE; - _remoteEpisode.Report.NzbUrl = URL; + _remoteEpisode.Release = new ReleaseInfo(); + _remoteEpisode.Release.Title = TITLE; + _remoteEpisode.Release.DownloadUrl = URL; _remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1) .All() diff --git a/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs b/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs index cc0285b78..86413bcc4 100644 --- a/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download _parseResult = Builder<RemoteEpisode>.CreateNew() .With(c => c.Series = Builder<Series>.CreateNew().Build()) - .With(c => c.Report = Builder<ReportInfo>.CreateNew().Build()) + .With(c => c.Release = Builder<ReleaseInfo>.CreateNew().Build()) .With(c => c.Episodes = episodes) .Build(); diff --git a/NzbDrone.Core.Test/Files/Media/H264_sample.mp4 b/NzbDrone.Core.Test/Files/Media/H264_sample.mp4 new file mode 100644 index 000000000..35bc6b353 Binary files /dev/null and b/NzbDrone.Core.Test/Files/Media/H264_sample.mp4 differ diff --git a/NzbDrone.Core.Test/Files/SceneMappings.json b/NzbDrone.Core.Test/Files/SceneMappings.json index a914ecdc9..71e1f5937 100644 --- a/NzbDrone.Core.Test/Files/SceneMappings.json +++ b/NzbDrone.Core.Test/Files/SceneMappings.json @@ -1,27 +1,32 @@ [ { - "CleanTitle": "csinewyork", - "Id": "73696", - "Title": "CSI" + "title": "Adventure Time", + "searchTitle": "Adventure Time", + "season": -1, + "tvdbId": 152831 }, { - "CleanTitle": "csiny", - "Id": "73696", - "Title": "CSI" + "title": "Americas Funniest Home Videos", + "searchTitle": "Americas Funniest Home Videos", + "season": -1, + "tvdbId": 76235 }, { - "CleanTitle": "csi", - "Id": "72546", - "Title": "CSI" + "title": "Antiques Roadshow UK", + "searchTitle": "Antiques Roadshow UK", + "season": -1, + "tvdbId": 83774 }, { - "CleanTitle": "csilasvegas", - "Id": "72546", - "Title": "CSI" + "title": "Aqua Something You Know Whatever", + "searchTitle": "Aqua Something You Know Whatever", + "season": 9, + "tvdbId": 77120 }, { - "CleanTitle": "archer", - "Id": "110381", - "Title": "Archer" + "title": "Aqua Teen Hunger Force", + "searchTitle": "Aqua Teen Hunger Force", + "season": -1, + "tvdbId": 77120 } ] \ No newline at end of file diff --git a/NzbDrone.Core.Test/Framework/DbTest.cs b/NzbDrone.Core.Test/Framework/DbTest.cs index ec94f7418..f8d3a0979 100644 --- a/NzbDrone.Core.Test/Framework/DbTest.cs +++ b/NzbDrone.Core.Test/Framework/DbTest.cs @@ -6,13 +6,15 @@ using FluentMigrator.Runner; using Marr.Data; using Moq; using NUnit.Framework; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Test.Framework { - [Category("DbTest")] + public abstract class DbTest<TSubject, TModel> : DbTest where TSubject : class where TModel : ModelBase, new() @@ -61,7 +63,7 @@ namespace NzbDrone.Core.Test.Framework - + [Category("DbTest")] public abstract class DbTest : CoreTest { private ITestDatabase _db; @@ -93,7 +95,7 @@ namespace NzbDrone.Core.Test.Framework Mocker.SetConstant<IAnnouncer>(Mocker.Resolve<MigrationLogger>()); Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>()); - Mocker.SetConstant<ISQLiteMigrationHelper>(Mocker.Resolve<SQLiteMigrationHelper>()); + Mocker.SetConstant<ISqLiteMigrationHelper>(Mocker.Resolve<SqLiteMigrationHelper>()); Mocker.SetConstant<ISQLiteAlter>(Mocker.Resolve<SQLiteAlter>()); Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>()); @@ -147,27 +149,27 @@ namespace NzbDrone.Core.Test.Framework public class TestDatabase : ITestDatabase { private readonly IDatabase _dbConnection; - private IMessageAggregator _messageAggregator; + private IEventAggregator _eventAggregator; public TestDatabase(IDatabase dbConnection) { - _messageAggregator = new Mock<IMessageAggregator>().Object; + _eventAggregator = new Mock<IEventAggregator>().Object; _dbConnection = dbConnection; } public void InsertMany<T>(IEnumerable<T> items) where T : ModelBase, new() { - new BasicRepository<T>(_dbConnection, _messageAggregator).InsertMany(items.ToList()); + new BasicRepository<T>(_dbConnection, _eventAggregator).InsertMany(items.ToList()); } public T Insert<T>(T item) where T : ModelBase, new() { - return new BasicRepository<T>(_dbConnection, _messageAggregator).Insert(item); + return new BasicRepository<T>(_dbConnection, _eventAggregator).Insert(item); } public List<T> All<T>() where T : ModelBase, new() { - return new BasicRepository<T>(_dbConnection, _messageAggregator).All().ToList(); + return new BasicRepository<T>(_dbConnection, _eventAggregator).All().ToList(); } public T Single<T>() where T : ModelBase, new() @@ -177,12 +179,12 @@ namespace NzbDrone.Core.Test.Framework public void Update<T>(T childModel) where T : ModelBase, new() { - new BasicRepository<T>(_dbConnection, _messageAggregator).Update(childModel); + new BasicRepository<T>(_dbConnection, _eventAggregator).Update(childModel); } public void Delete<T>(T childModel) where T : ModelBase, new() { - new BasicRepository<T>(_dbConnection, _messageAggregator).Delete(childModel); + new BasicRepository<T>(_dbConnection, _eventAggregator).Delete(childModel); } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs b/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs index 4ac080e2a..2eca3586b 100644 --- a/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs +++ b/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs @@ -5,7 +5,7 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.IndexerTests { - public class BasicRssParserFixture : CoreTest<BasicRssParser> + public class BasicRssParserFixture : CoreTest<RssParserBase> { [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", "LOL")] @@ -16,7 +16,7 @@ namespace NzbDrone.Core.Test.IndexerTests [TestCase("The.Office.S03E115.DVDRip.XviD-OSiTV", "OSiTV")] public void parse_releaseGroup(string title, string expected) { - BasicRssParser.ParseReleaseGroup(title).Should().Be(expected); + RssParserBase.ParseReleaseGroup(title).Should().Be(expected); } @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.IndexerTests [TestCase("845 MB", 886046720)] public void parse_size(string sizeString, long expectedSize) { - var result = BasicRssParser.GetReportSize(sizeString); + var result = RssParserBase.ParseSize(sizeString); result.Should().Be(expectedSize); } diff --git a/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs b/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs index 3e6239823..2de52fb5f 100644 --- a/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs +++ b/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs @@ -6,7 +6,6 @@ using Moq; using NUnit.Framework; using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers.Newznab; -using NzbDrone.Core.Indexers.NzbClub; using NzbDrone.Core.Indexers.Omgwtfnzbs; using NzbDrone.Core.Indexers.Wombles; using NzbDrone.Core.Lifecycle; @@ -24,7 +23,6 @@ namespace NzbDrone.Core.Test.IndexerTests _indexers = new List<IIndexer>(); _indexers.Add(new Newznab()); - _indexers.Add(new NzbClub()); _indexers.Add(new Omgwtfnzbs()); _indexers.Add(new Wombles()); diff --git a/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs b/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs index 813c1a45f..1be142824 100644 --- a/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs +++ b/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using FluentAssertions; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Eztv; using NzbDrone.Core.Indexers.Newznab; -using NzbDrone.Core.Indexers.NzbClub; using NzbDrone.Core.Indexers.Wombles; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NUnit.Framework; using NzbDrone.Test.Common.Categories; +using System.Linq; namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests { @@ -22,24 +23,24 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests } [Test] - [Explicit] - public void nzbclub_rss() + public void wombles_rss() { - var indexer = new NzbClub(); + var indexer = new Wombles(); var result = Subject.FetchRss(indexer); - ValidateResult(result); + ValidateResult(result, skipSize: true, skipInfo: true); } + [Test] - public void wombles_rss() + public void extv_rss() { - var indexer = new Wombles(); + var indexer = new Eztv(); var result = Subject.FetchRss(indexer); - ValidateResult(result, skipSize: true, skipInfo: true); + ValidateTorrentResult(result, skipSize: false, skipInfo: true); } @@ -63,15 +64,17 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests - private void ValidateResult(IList<ReportInfo> reports, bool skipSize = false, bool skipInfo = false) + private void ValidateResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false) { reports.Should().NotBeEmpty(); - reports.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.Title)); - reports.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.NzbUrl)); + reports.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Title)); + reports.Should().NotContain(c => string.IsNullOrWhiteSpace(c.DownloadUrl)); + reports.Should().OnlyContain(c => c.PublishDate.Year > 2000); + reports.Should().OnlyContain(c => c.DownloadUrl.StartsWith("http")); if (!skipInfo) { - reports.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.NzbInfoUrl)); + reports.Should().NotContain(c => string.IsNullOrWhiteSpace(c.InfoUrl)); } if (!skipSize) @@ -80,5 +83,18 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests } } + private void ValidateTorrentResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false) + { + + reports.Should().OnlyContain(c => c.GetType() == typeof(TorrentInfo)); + + ValidateResult(reports, skipSize, skipInfo); + + reports.Should().OnlyContain(c => c.DownloadUrl.EndsWith(".torrent")); + + reports.Cast<TorrentInfo>().Should().OnlyContain(c => c.MagnetUrl.StartsWith("magnet:")); + reports.Cast<TorrentInfo>().Should().NotContain(c => string.IsNullOrWhiteSpace(c.InfoHash)); + } + } -} \ No newline at end of file +} diff --git a/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs b/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs index 6bb9644a9..2b88348aa 100644 --- a/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs +++ b/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using FluentAssertions; using NLog; using NUnit.Framework; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Instrumentation; @@ -14,8 +15,6 @@ namespace NzbDrone.Core.Test.InstrumentationTests [TestFixture] public class DatabaseTargetFixture : DbTest<DatabaseTarget, Log> { - string _loggerName; - private static string _uniqueMessage; Logger _logger; @@ -36,8 +35,7 @@ namespace NzbDrone.Core.Test.InstrumentationTests LogManager.ReconfigExistingLoggers(); - _logger = LogManager.GetCurrentClassLogger(); - _loggerName = _logger.Name.Replace("NzbDrone.",""); + _logger = NzbDroneLogger.GetLogger(); _uniqueMessage = "Unique message: " + Guid.NewGuid().ToString(); } @@ -119,7 +117,7 @@ namespace NzbDrone.Core.Test.InstrumentationTests private void VerifyLog(Log logItem, LogLevel level) { logItem.Time.Should().BeWithin(TimeSpan.FromSeconds(2)); - logItem.Logger.Should().Be(_loggerName); + logItem.Logger.Should().Be(this.GetType().Name); logItem.Level.Should().Be(level.Name); logItem.Method.Should().Be(new StackTrace().GetFrame(1).GetMethod().Name); _logger.Name.Should().EndWith(logItem.Logger); diff --git a/NzbDrone.Core.Test/MediaFileTests/DropFolderImportServiceFixture.cs b/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs similarity index 97% rename from NzbDrone.Core.Test/MediaFileTests/DropFolderImportServiceFixture.cs rename to NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs index 870e5025a..09e51ac3a 100644 --- a/NzbDrone.Core.Test/MediaFileTests/DropFolderImportServiceFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs @@ -15,10 +15,10 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests +namespace NzbDrone.Core.Test.MediaFiles { [TestFixture] - public class DropFolderImportServiceFixture : CoreTest<DownloadedEpisodesImportService> + public class DownloadedEpisodesImportServiceFixture : CoreTest<DownloadedEpisodesImportService> { private string[] _subFolders = new[] { "c:\\root\\foldername".AsOsAgnostic() }; private string[] _videoFiles = new[] { "c:\\root\\foldername\\video.ext".AsOsAgnostic() }; @@ -75,7 +75,7 @@ namespace NzbDrone.Core.Test.MediaFileTests [Test] public void should_skip_if_file_is_in_use_by_another_process() { - Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<FileInfo>())) + Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<string>())) .Returns(true); Subject.Execute(new DownloadedEpisodesScanCommand()); diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/ImportDecisionMakerFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs similarity index 97% rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/ImportDecisionMakerFixture.cs rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs index 386c2a9ff..68dfdc5db 100644 --- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/ImportDecisionMakerFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs @@ -13,12 +13,12 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests +namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport { [TestFixture] public class ImportDecisionMakerFixture : CoreTest<ImportDecisionMaker> { - private List<String> _videoFiles; + private List<string> _videoFiles; private LocalEpisode _localEpisode; private Series _series; @@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(false); _fail3.Setup(c => c.RejectionReason).Returns("_fail3"); - _videoFiles = new List<String> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" }; + _videoFiles = new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" }; _series = new Series(); _localEpisode = new LocalEpisode { Series = _series, Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" }; diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecificationFixture.cs similarity index 65% rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecificationFixture.cs index 47804bde0..4f1f545a4 100644 --- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecificationFixture.cs @@ -8,12 +8,11 @@ using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Providers; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests +namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications { [TestFixture] public class FreeSpaceSpecificationFixture : CoreTest<FreeSpaceSpecification> @@ -40,7 +39,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests _localEpisode = new LocalEpisode { - Path = @"C:\Test\30 Rock\30.rock.s01e01.avi", + Path = @"C:\Test\Unsorted\30 Rock\30.rock.s01e01.avi".AsOsAgnostic(), Episodes = episodes, Series = _series }; @@ -51,7 +50,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests _localEpisode.Size = size; } - private void GivenFreeSpace(long size) + private void GivenFreeSpace(long? size) { Mocker.GetMock<IDiskProvider>() .Setup(s => s.GetAvailableSpace(It.IsAny<String>())) @@ -98,5 +97,50 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests Mocker.GetMock<IDiskProvider>() .Verify(v => v.GetAvailableSpace(_rootFolder), Times.Once()); } + + [Test] + public void should_pass_if_free_space_is_null() + { + GivenFileSize(100.Megabytes()); + GivenFreeSpace(null); + + Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); + } + + [Test] + public void should_pass_if_exception_is_thrown() + { + GivenFileSize(100.Megabytes()); + + Mocker.GetMock<IDiskProvider>() + .Setup(s => s.GetAvailableSpace(It.IsAny<String>())) + .Throws(new TestException()); + + Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); + ExceptionVerification.ExpectedErrors(1); + } + + [Test] + public void should_skip_check_for_files_under_series_folder() + { + _localEpisode.ExistingFile = true; + + Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); + + Mocker.GetMock<IDiskProvider>() + .Verify(s => s.GetAvailableSpace(It.IsAny<String>()), Times.Never()); + } + + [Test] + public void should_return_true_if_free_space_is_null() + { + long? freeSpace = null; + + Mocker.GetMock<IDiskProvider>() + .Setup(s => s.GetAvailableSpace(It.IsAny<String>())) + .Returns(freeSpace); + + Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); + } } } diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotInUseSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotInUseSpecificationFixture.cs similarity index 77% rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotInUseSpecificationFixture.cs rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotInUseSpecificationFixture.cs index 98080014b..7887e40aa 100644 --- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotInUseSpecificationFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotInUseSpecificationFixture.cs @@ -1,20 +1,15 @@ -using System.IO; -using System.Linq; -using FizzWare.NBuilder; +using FizzWare.NBuilder; using FluentAssertions; -using Marr.Data; using Moq; -using Newtonsoft.Json.Serialization; using NUnit.Framework; using NzbDrone.Common; -using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests +namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications { [TestFixture] public class NotInUseSpecificationFixture : CoreTest<NotInUseSpecification> @@ -34,9 +29,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests private void GivenChildOfSeries() { - Mocker.GetMock<IDiskProvider>() - .Setup(s => s.IsParent(_localEpisode.Series.Path, _localEpisode.Path)) - .Returns(true); + _localEpisode.ExistingFile = true; } private void GivenNewFile() @@ -62,7 +55,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests Subject.IsSatisfiedBy(_localEpisode); Mocker.GetMock<IDiskProvider>() - .Verify(v => v.IsFileLocked(It.IsAny<FileInfo>()), Times.Never()); + .Verify(v => v.IsFileLocked(It.IsAny<string>()), Times.Never()); } [Test] @@ -71,7 +64,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests GivenNewFile(); Mocker.GetMock<IDiskProvider>() - .Setup(s => s.IsFileLocked(It.IsAny<FileInfo>())) + .Setup(s => s.IsFileLocked(It.IsAny<string>())) .Returns(true); Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); @@ -83,7 +76,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests GivenNewFile(); Mocker.GetMock<IDiskProvider>() - .Setup(s => s.IsFileLocked(It.IsAny<FileInfo>())) + .Setup(s => s.IsFileLocked(It.IsAny<string>())) .Returns(false); Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotSampleSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotSampleSpecificationFixture.cs similarity index 97% rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotSampleSpecificationFixture.cs rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotSampleSpecificationFixture.cs index f89cfa48d..c3f5b0429 100644 --- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotSampleSpecificationFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotSampleSpecificationFixture.cs @@ -5,13 +5,14 @@ using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; +using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Providers; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests +namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications { [TestFixture] public class NotSampleSpecificationFixture : CoreTest<NotSampleSpecification> diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotUnpackingSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecificationFixture.cs similarity index 91% rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotUnpackingSpecificationFixture.cs rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecificationFixture.cs index 102f7a4f1..05a45a1f6 100644 --- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotUnpackingSpecificationFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecificationFixture.cs @@ -1,22 +1,17 @@ using System; -using System.IO; -using System.Linq; using FizzWare.NBuilder; using FluentAssertions; -using Marr.Data; using Moq; -using Newtonsoft.Json.Serialization; using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Core.Configuration; -using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests +namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications { [TestFixture] public class NotUnpackingSpecificationFixture : CoreTest<NotUnpackingSpecification> diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/UpgradeSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs similarity index 99% rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/UpgradeSpecificationFixture.cs rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs index 9ac31aa1b..a0d0f7aef 100644 --- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/UpgradeSpecificationFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs @@ -10,7 +10,7 @@ using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; -namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests +namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications { [TestFixture] public class UpgradeSpecificationFixture : CoreTest<UpgradeSpecification> diff --git a/NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs b/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs similarity index 95% rename from NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs rename to NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs index a7d13ff0c..b1cab5f58 100644 --- a/NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs @@ -4,17 +4,18 @@ using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; -using NzbDrone.Common.Messaging; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests +namespace NzbDrone.Core.Test.MediaFiles { [TestFixture] public class ImportApprovedEpisodesFixture : CoreTest<ImportApprovedEpisodes> @@ -103,7 +104,7 @@ namespace NzbDrone.Core.Test.MediaFileTests { Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true); - Mocker.GetMock<IMessageAggregator>() + Mocker.GetMock<IEventAggregator>() .Verify(v => v.PublishEvent(It.IsAny<EpisodeImportedEvent>()), Times.Once()); } @@ -122,7 +123,7 @@ namespace NzbDrone.Core.Test.MediaFileTests { Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }); - Mocker.GetMock<IMessageAggregator>() + Mocker.GetMock<IEventAggregator>() .Verify(v => v.PublishEvent(It.IsAny<EpisodeImportedEvent>()), Times.Never()); } } diff --git a/NzbDrone.Core.Test/MediaFileTests/MediaFileRepositoryFixture.cs b/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs similarity index 97% rename from NzbDrone.Core.Test/MediaFileTests/MediaFileRepositoryFixture.cs rename to NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs index 567a55949..2b54fff52 100644 --- a/NzbDrone.Core.Test/MediaFileTests/MediaFileRepositoryFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs @@ -8,7 +8,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests +namespace NzbDrone.Core.Test.MediaFiles { [TestFixture] public class MediaFileRepositoryFixture : DbTest<MediaFileRepository, EpisodeFile> diff --git a/NzbDrone.Core.Test/MediaFileTests/MediaFileServiceTest.cs b/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs similarity index 99% rename from NzbDrone.Core.Test/MediaFileTests/MediaFileServiceTest.cs rename to NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs index 1e1060239..2135e773a 100644 --- a/NzbDrone.Core.Test/MediaFileTests/MediaFileServiceTest.cs +++ b/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs @@ -8,7 +8,7 @@ using NzbDrone.Core.Organizer; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.MediaFileTests +namespace NzbDrone.Core.Test.MediaFiles { [TestFixture] public class MediaFileServiceTest : CoreTest<MediaFileService> diff --git a/NzbDrone.Core.Test/MediaFileTests/MediaFileTableCleanupServiceFixture.cs b/NzbDrone.Core.Test/MediaFiles/MediaFileTableCleanupServiceFixture.cs similarity index 99% rename from NzbDrone.Core.Test/MediaFileTests/MediaFileTableCleanupServiceFixture.cs rename to NzbDrone.Core.Test/MediaFiles/MediaFileTableCleanupServiceFixture.cs index 0b61e29d0..343a03158 100644 --- a/NzbDrone.Core.Test/MediaFileTests/MediaFileTableCleanupServiceFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/MediaFileTableCleanupServiceFixture.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using FizzWare.NBuilder; using Moq; using NUnit.Framework; @@ -8,9 +9,8 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; -using System.Linq; -namespace NzbDrone.Core.Test.MediaFileTests +namespace NzbDrone.Core.Test.MediaFiles { public class MediaFileTableCleanupServiceFixture : CoreTest<MediaFileTableCleanupService> { diff --git a/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs b/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs new file mode 100644 index 000000000..c3267089b --- /dev/null +++ b/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs @@ -0,0 +1,22 @@ +using System.IO; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.Categories; + +namespace NzbDrone.Core.Test.MediaFiles.MediaInfo +{ + [TestFixture] + [DiskAccessTest] + public class VideoFileInfoReaderFixture : CoreTest<VideoFileInfoReader> + { + [Test] + public void get_runtime() + { + var path = Path.Combine(Directory.GetCurrentDirectory(), "Files", "Media", "H264_sample.mp4"); + + Subject.GetRunTime(path).Seconds.Should().Be(10); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs b/NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs similarity index 95% rename from NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs rename to NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs index 80b4dc855..070b4eb03 100644 --- a/NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs @@ -3,14 +3,15 @@ using System.Linq; using FizzWare.NBuilder; using Moq; using NUnit.Framework; -using NzbDrone.Common.Messaging; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Events; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Tv; -namespace NzbDrone.Core.Test.MediaFileTests +namespace NzbDrone.Core.Test.MediaFiles { public class RenameEpisodeFileServiceFixture : CoreTest<RenameEpisodeFileService> { @@ -70,7 +71,7 @@ namespace NzbDrone.Core.Test.MediaFileTests Subject.Execute(new RenameSeriesCommand(_series.Id)); - Mocker.GetMock<IMessageAggregator>() + Mocker.GetMock<IEventAggregator>() .Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Never()); } @@ -85,7 +86,7 @@ namespace NzbDrone.Core.Test.MediaFileTests Subject.Execute(new RenameSeriesCommand(_series.Id)); - Mocker.GetMock<IMessageAggregator>() + Mocker.GetMock<IEventAggregator>() .Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Never()); } @@ -97,7 +98,7 @@ namespace NzbDrone.Core.Test.MediaFileTests Subject.Execute(new RenameSeriesCommand(_series.Id)); - Mocker.GetMock<IMessageAggregator>() + Mocker.GetMock<IEventAggregator>() .Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Once()); } diff --git a/NzbDrone.Core.Test/MediaFileTests/UpgradeMediaFileServiceFixture.cs b/NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs similarity index 98% rename from NzbDrone.Core.Test/MediaFileTests/UpgradeMediaFileServiceFixture.cs rename to NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs index 210eaff99..6594a3c77 100644 --- a/NzbDrone.Core.Test/MediaFileTests/UpgradeMediaFileServiceFixture.cs +++ b/NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using FizzWare.NBuilder; using Marr.Data; using Moq; @@ -10,7 +9,7 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; -namespace NzbDrone.Core.Test.MediaFileTests +namespace NzbDrone.Core.Test.MediaFiles { public class UpgradeMediaFileServiceFixture : CoreTest<UpgradeMediaFileService> { diff --git a/NzbDrone.Core.Test/Messaging/Commands/CommandEqualityComparerFixture.cs b/NzbDrone.Core.Test/Messaging/Commands/CommandEqualityComparerFixture.cs new file mode 100644 index 000000000..34d4b1d30 --- /dev/null +++ b/NzbDrone.Core.Test/Messaging/Commands/CommandEqualityComparerFixture.cs @@ -0,0 +1,97 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.IndexerSearch; +using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Update.Commands; +using NzbDrone.SignalR; + +namespace NzbDrone.Core.Test.Messaging.Commands +{ + [TestFixture] + public class CommandEqualityComparerFixture + { + [Test] + public void should_return_true_when_there_are_no_properties() + { + var command1 = new DownloadedEpisodesScanCommand(); + var command2 = new DownloadedEpisodesScanCommand(); + + CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_single_property_matches() + { + var command1 = new EpisodeSearchCommand { EpisodeId = 1 }; + var command2 = new EpisodeSearchCommand { EpisodeId = 1 }; + + CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_multiple_properties_match() + { + var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 }; + var command2 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 }; + + CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeTrue(); + } + + [Test] + public void should_return_false_when_single_property_doesnt_match() + { + var command1 = new EpisodeSearchCommand { EpisodeId = 1 }; + var command2 = new EpisodeSearchCommand { EpisodeId = 2 }; + + CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_only_one_property_matches() + { + var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 }; + var command2 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 2 }; + + CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_no_properties_match() + { + var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 }; + var command2 = new SeasonSearchCommand { SeriesId = 2, SeasonNumber = 2 }; + + CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_only_one_has_properties() + { + var command1 = new SeasonSearchCommand(); + var command2 = new SeasonSearchCommand { SeriesId = 2, SeasonNumber = 2 }; + + CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse(); + } + + + [Test] + public void should_return_false_when_only_one_has_null_property() + { + var command1 = new BroadcastSignalRMessage(null); + var command2 = new BroadcastSignalRMessage(new SignalRMessage()); + + CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse(); + } + + + [Test] + public void should_return_false_when_commands_are_diffrent_types() + { + CommandEqualityComparer.Instance.Equals(new RssSyncCommand(), new ApplicationUpdateCommand()).Should().BeFalse(); + } + + + } +} diff --git a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs b/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs similarity index 63% rename from NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs rename to NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs index 1e450ae21..650f81224 100644 --- a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs +++ b/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs @@ -2,13 +2,16 @@ using System.Collections.Generic; using Moq; using NUnit.Framework; -using NzbDrone.Common.Messaging; +using NzbDrone.Common; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Commands.Tracking; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Test.Common; -namespace NzbDrone.Common.Test.EventingTests +namespace NzbDrone.Core.Test.Messaging.Commands { [TestFixture] - public class MessageAggregatorCommandTests : TestBase<MessageAggregator> + public class CommandExecutorFixture : TestBase<CommandExecutor> { private Mock<IExecute<CommandA>> _executorA; private Mock<IExecute<CommandB>> _executorB; @@ -27,6 +30,10 @@ namespace NzbDrone.Common.Test.EventingTests .Setup(c => c.Build(typeof(IExecute<CommandB>))) .Returns(_executorB.Object); + + Mocker.GetMock<ITrackCommands>() + .Setup(c => c.FindExisting(It.IsAny<Command>())) + .Returns<Command>(null); } [Test] @@ -42,7 +49,7 @@ namespace NzbDrone.Common.Test.EventingTests [Test] public void should_publish_command_by_with_optional_arg_using_name() { - Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(ICommand))) + Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(Command))) .Returns(new List<Type> { typeof(CommandA), typeof(CommandB) }); Subject.PublishCommand(typeof(CommandA).FullName); @@ -55,7 +62,6 @@ namespace NzbDrone.Common.Test.EventingTests { var commandA = new CommandA(); - Subject.PublishCommand(commandA); _executorA.Verify(c => c.Execute(commandA), Times.Once()); @@ -72,21 +78,44 @@ namespace NzbDrone.Common.Test.EventingTests Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA)); } + + + [Test] + public void broken_executor_should_publish_executed_event() + { + var commandA = new CommandA(); + + _executorA.Setup(c => c.Execute(It.IsAny<CommandA>())) + .Throws(new NotImplementedException()); + + Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA)); + + VerifyEventPublished<CommandExecutedEvent>(); + } + + [Test] + public void should_publish_executed_event_on_success() + { + var commandA = new CommandA(); + Subject.PublishCommand(commandA); + + VerifyEventPublished<CommandExecutedEvent>(); + } } - public class CommandA : ICommand + public class CommandA : Command { -// ReSharper disable UnusedParameter.Local public CommandA(int id = 0) -// ReSharper restore UnusedParameter.Local { - } } - public class CommandB : ICommand + public class CommandB : Command { + public CommandB() + { + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/Messaging/Commands/CommandFixture.cs b/NzbDrone.Core.Test/Messaging/Commands/CommandFixture.cs new file mode 100644 index 000000000..a75fc2224 --- /dev/null +++ b/NzbDrone.Core.Test/Messaging/Commands/CommandFixture.cs @@ -0,0 +1,19 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Update.Commands; + +namespace NzbDrone.Core.Test.Messaging.Commands +{ + [TestFixture] + public class CommandFixture + { + [Test] + public void default_values() + { + var command = new ApplicationUpdateCommand(); + + command.Id.Should().NotBe(0); + command.Name.Should().Be("ApplicationUpdate"); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs b/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs index 4a2883253..412239c0b 100644 --- a/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs +++ b/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs @@ -47,11 +47,11 @@ namespace NzbDrone.Core.Test.MetadataSourceTests ValidateEpisodes(details.Item2); } - [Test] public void getting_details_of_invalid_series() { Assert.Throws<RestException>(() => Subject.GetSeriesInfo(Int32.MaxValue)); + ExceptionVerification.ExpectedWarns(1); } @@ -88,12 +88,16 @@ namespace NzbDrone.Core.Test.MetadataSourceTests 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(); + episode.Title.Should().NotBeBlank(); //episode.Title.Should().NotBeBlank(); episode.EpisodeNumber.Should().NotBe(0); episode.TvDbEpisodeId.Should().BeGreaterThan(0); diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 524d84b1d..5035ea37e 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -108,8 +108,11 @@ <Compile Include="Datastore\PagingSpecExtenstionsTests\ToSortDirectionFixture.cs" /> <Compile Include="Datastore\PagingSpecExtenstionsTests\PagingOffsetFixture.cs" /> <Compile Include="Datastore\ReflectionStrategyFixture\Benchmarks.cs" /> - <Compile Include="Datastore\SQLiteMigrationHelperFixture.cs" /> + <Compile Include="Datastore\SQLiteMigrationHelperTests\AlterFixture.cs" /> + <Compile Include="Datastore\SQLiteMigrationHelperTests\DuplicateFixture.cs" /> + <Compile Include="DecisionEngineTests\CutoffSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\NotRestrictedReleaseSpecificationFixture.cs" /> + <Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" /> <Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" /> <Compile Include="Download\DownloadApprovedReportsTests\GetQualifiedReportsFixture.cs" /> <Compile Include="Download\DownloadClientTests\BlackholeProviderFixture.cs" /> @@ -132,17 +135,21 @@ <Compile Include="JobTests\TestJobs.cs" /> <Compile Include="MediaCoverTests\CoverExistsSpecificationFixture.cs" /> <Compile Include="MediaCoverTests\MediaCoverServiceFixture.cs" /> - <Compile Include="MediaFileTests\EpisodeImportTests\NotUnpackingSpecificationFixture.cs" /> - <Compile Include="MediaFileTests\EpisodeImportTests\NotInUseSpecificationFixture.cs" /> - <Compile Include="MediaFileTests\EpisodeImportTests\FreeSpaceSpecificationFixture.cs" /> - <Compile Include="MediaFileTests\RenameEpisodeFileServiceFixture.cs" /> - <Compile Include="MediaFileTests\UpgradeMediaFileServiceFixture.cs" /> - <Compile Include="MediaFileTests\ImportApprovedEpisodesFixture.cs" /> - <Compile Include="MediaFileTests\EpisodeImportTests\UpgradeSpecificationFixture.cs" /> - <Compile Include="MediaFileTests\EpisodeImportTests\NotSampleSpecificationFixture.cs" /> - <Compile Include="MediaFileTests\EpisodeImportTests\ImportDecisionMakerFixture.cs" /> - <Compile Include="MediaFileTests\MediaFileTableCleanupServiceFixture.cs" /> - <Compile Include="MediaFileTests\MediaFileRepositoryFixture.cs" /> + <Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" /> + <Compile Include="MediaFiles\EpisodeImport\ImportDecisionMakerFixture.cs" /> + <Compile Include="MediaFiles\EpisodeImport\Specifications\NotInUseSpecificationFixture.cs" /> + <Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecificationFixture.cs" /> + <Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecificationFixture.cs" /> + <Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" /> + <Compile Include="MediaFiles\MediaInfo\VideoFileInfoReaderFixture.cs" /> + <Compile Include="MediaFiles\RenameEpisodeFileServiceFixture.cs" /> + <Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" /> + <Compile Include="MediaFiles\ImportApprovedEpisodesFixture.cs" /> + <Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" /> + <Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" /> + <Compile Include="Messaging\Commands\CommandEqualityComparerFixture.cs" /> + <Compile Include="Messaging\Commands\CommandExecutorFixture.cs" /> + <Compile Include="Messaging\Commands\CommandFixture.cs" /> <Compile Include="MetadataSourceTests\TraktProxyFixture.cs" /> <Compile Include="NotificationTests\NotificationServiceFixture.cs" /> <Compile Include="NotificationTests\Xbmc\GetJsonVersionFixture.cs" /> @@ -155,6 +162,8 @@ <Compile Include="NotificationTests\Xbmc\Json\GetSeriesPathFixture.cs" /> <Compile Include="NotificationTests\Xbmc\Json\UpdateFixture.cs" /> <Compile Include="OrganizerTests\BuildFilePathFixture.cs" /> + <Compile Include="ParserTests\ParsingServiceTests\GetEpisodesFixture.cs" /> + <Compile Include="ParserTests\ParsingServiceTests\MapFixture.cs" /> <Compile Include="Qualities\QualitySizeRepositoryFixture.cs" /> <Compile Include="Qualities\QualityProfileRepositoryFixture.cs" /> <Compile Include="RootFolderTests\FreeSpaceOnDrivesFixture.cs" /> @@ -174,22 +183,18 @@ <Compile Include="TvTests\EpisodeRepositoryTests\EpisodesRepositoryReadFixture.cs" /> <Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWithoutFilesFixture.cs" /> <Compile Include="TvTests\EpisodeRepositoryTests\EpisodesBetweenDatesFixture.cs" /> - <Compile Include="TvTests\SeasonProviderTest.cs" /> <Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\UpgradeHistorySpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\UpgradeDiskSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\QualityUpgradeSpecificationFixture.cs" /> - <Compile Include="DecisionEngineTests\QualityUpgradableSpecificationFixture.cs" /> <Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceFixture.cs" /> <Compile Include="NotificationTests\ProwlProviderTest.cs" /> <Compile Include="ProviderTests\DiskProviderTests\ArchiveProviderFixture.cs" /> - <Compile Include="MediaFileTests\DropFolderImportServiceFixture.cs" /> + <Compile Include="MediaFiles\DownloadedEpisodesImportServiceFixture.cs" /> <Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" /> - <Compile Include="TvTests\SeasonServiceTests\HandleEpisodeInfoDeletedEventFixture.cs" /> - <Compile Include="TvTests\SeasonServiceTests\SetSeasonPassFixture.cs" /> - <Compile Include="TvTests\SeasonServiceTests\SetMonitoredFixture.cs" /> <Compile Include="TvTests\SeriesRepositoryTests\QualityProfileRepositoryFixture.cs" /> + <Compile Include="TvTests\SeriesServiceTests\UpdateSeriesFixture.cs" /> <Compile Include="UpdateTests\UpdateServiceFixture.cs" /> <Compile Include="ProviderTests\XemCommunicationProviderTests\GetSceneTvdbMappingsFixture.cs" /> <Compile Include="ProviderTests\XemCommunicationProviderTests\GetXemSeriesIdsFixture.cs" /> @@ -205,14 +210,14 @@ <Compile Include="TvTests\QualityModelFixture.cs" /> <Compile Include="RootFolderTests\RootFolderServiceFixture.cs" /> <Compile Include="HistoryTests\HistoryRepositoryFixture.cs" /> - <Compile Include="MediaFileTests\MediaFileServiceTest.cs" /> + <Compile Include="MediaFiles\MediaFileServiceTest.cs" /> <Compile Include="Configuration\ConfigServiceFixture.cs" /> <Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest.cs" /> <Compile Include="Framework\TestDbHelper.cs" /> <Compile Include="ParserTests\ParserFixture.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Qualities\QualityProfileServiceFixture.cs" /> - <Compile Include="TvTests\SeriesServiceFixture.cs" /> + <Compile Include="TvTests\SeriesServiceTests\AddSeriesFixture.cs" /> <Compile Include="UpdateTests\UpdatePackageProviderFixture.cs" /> <Compile Include="XbmcVersionTests.cs" /> </ItemGroup> @@ -229,6 +234,10 @@ <Project>{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}</Project> <Name>NzbDrone.Core</Name> </ProjectReference> + <ProjectReference Include="..\NzbDrone.SignalR\NzbDrone.SignalR.csproj"> + <Project>{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}</Project> + <Name>NzbDrone.SignalR</Name> + </ProjectReference> <ProjectReference Include="..\NzbDrone.Test.Common\NzbDrone.Test.Common.csproj"> <Project>{CADDFCE0-7509-4430-8364-2074E1EEFCA2}</Project> <Name>NzbDrone.Test.Common</Name> @@ -242,6 +251,9 @@ <Content Include="App_Data\Config.xml"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> + <Content Include="Files\Media\H264_sample.mp4"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> <Content Include="Files\Nzbget\JsonError.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> diff --git a/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/NzbDrone.Core.Test/ParserTests/ParserFixture.cs index 192ff7380..ace78f50e 100644 --- a/NzbDrone.Core.Test/ParserTests/ParserFixture.cs +++ b/NzbDrone.Core.Test/ParserTests/ParserFixture.cs @@ -4,11 +4,10 @@ using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Common.Contract; -using NzbDrone.Core.Indexers; +using NzbDrone.Common.Expansive; using NzbDrone.Core.Parser; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; -using NzbDrone.Common.Expansive; namespace NzbDrone.Core.Test.ParserTests { @@ -80,6 +79,8 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Top_Gear.19x06.720p_HDTV_x264-FoV", "Top Gear", 19, 6)] [TestCase("Portlandia.S03E10.Alexandra.720p.WEB-DL.AAC2.0.H.264-CROM.mkv", "Portlandia", 3, 10)] [TestCase("(Game of Thrones s03 e - \"Game of Thrones Season 3 Episode 10\"", "Game of Thrones", 3, 10)] + [TestCase("House.Hunters.International.S05E607.720p.hdtv.x264", "House.Hunters.International", 5, 607)] + [TestCase("Adventure.Time.With.Finn.And.Jake.S01E20.720p.BluRay.x264-DEiMOS", "Adventure.Time.With.Finn.And.Jake", 1, 20)] public void ParseTitle_single(string postTitle, string title, int seasonNumber, int episodeNumber) { var result = Parser.Parser.ParseTitle(postTitle); @@ -103,6 +104,8 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase(@"/TV Drop/King of the Hill - 10x12 - 24 Hour Propane People [SDTV]/1012 - 24 Hour Propane People.avi", 10, 12)] [TestCase(@"S:\TV Drop\King of the Hill - 10x12 - 24 Hour Propane People [SDTV]\Hour Propane People.avi", 10, 12)] [TestCase(@"/TV Drop/King of the Hill - 10x12 - 24 Hour Propane People [SDTV]/Hour Propane People.avi", 10, 12)] + [TestCase(@"E:\Downloads\tv\The.Big.Bang.Theory.S01E01.720p.HDTV\ajifajjjeaeaeqwer_eppj.avi", 1, 1)] + [TestCase(@"C:\Test\Unsorted\The.Big.Bang.Theory.S01E01.720p.HDTV\tbbt101.avi", 1, 1)] public void PathParse_tests(string path, int season, int episode) { var result = Parser.Parser.ParsePath(path); @@ -396,14 +399,6 @@ namespace NzbDrone.Core.Test.ParserTests result.Should().BeNull(); } - [TestCase("[112461]-[FULL]-[#a.b.teevee@EFNet]-[ 666.Park.Avenue.S01E03.720p.HDTV.X264-DIMENSION ]-[02/31] - \"the.devils.address.103.720p-dimension.par2\" yEnc", "666.Park.Avenue.S01E03.720p.HDTV.X264-DIMENSION")] - [TestCase("[112438]-[FULL]-[#a.b.teevee@EFNet]-[ Downton_Abbey.3x05.HDTV_x264-FoV ]-[01/26] - \"downton_abbey.3x05.hdtv_x264-fov.nfo\" yEnc", "Downton_Abbey.3x05.HDTV_x264-FoV")] - [TestCase("[ 21154 ] - [ TrollHD ] - [ 00/73 ] - \"MythBusters S03E20 Escape Slide Parachute 1080i HDTV-UPSCALE DD5.1 MPEG2-TrollHD.nzb\" yEnc", "MythBusters S03E20 Escape Slide Parachute 1080i HDTV-UPSCALE DD5.1 MPEG2-TrollHD.nzb")] - public void parse_header(string title, string expected) - { - BasicRssParser.ParseHeader(title).Should().Be(expected); - } - [TestCase("76El6LcgLzqb426WoVFg1vVVVGx4uCYopQkfjmLe")] [TestCase("Vrq6e1Aba3U amCjuEgV5R2QvdsLEGYF3YQAQkw8")] [TestCase("TDAsqTea7k4o6iofVx3MQGuDK116FSjPobMuh8oB")] diff --git a/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs b/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs new file mode 100644 index 000000000..b0692b6fb --- /dev/null +++ b/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests +{ + [TestFixture] + public class GetEpisodesFixture : TestBase<ParsingService> + { + private Series _series; + private List<Episode> _episodes; + private ParsedEpisodeInfo _parsedEpisodeInfo; + private SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria; + + [SetUp] + public void Setup() + { + _series = Builder<Series>.CreateNew() + .With(s => s.Title = "30 Rock") + .With(s => s.CleanTitle = "rock") + .Build(); + + _episodes = Builder<Episode>.CreateListOfSize(1) + .All() + .With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT)) + .Build() + .ToList(); + + _parsedEpisodeInfo = new ParsedEpisodeInfo + { + SeriesTitle = _series.Title, + SeasonNumber = 1, + EpisodeNumbers = new[] { 1 } + }; + + _singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria + { + Series = _series, + EpisodeNumber = _episodes.First().EpisodeNumber, + SeasonNumber = _episodes.First().SeasonNumber, + Episodes = _episodes + }; + + Mocker.GetMock<ISeriesService>() + .Setup(s => s.FindByTitle(It.IsAny<String>())) + .Returns(_series); + } + + private void GivenDailySeries() + { + _series.SeriesType = SeriesTypes.Daily; + } + + private void GivenDailyParseResult() + { + _parsedEpisodeInfo.AirDate = DateTime.Today; + } + + private void GivenSceneNumberingSeries() + { + _series.UseSceneNumbering = true; + } + + [Test] + public void should_get_daily_episode_episode_when_search_criteria_is_null() + { + GivenDailySeries(); + GivenDailyParseResult(); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Once()); + } + + [Test] + public void should_use_search_criteria_episode_when_it_matches_daily() + { + GivenDailySeries(); + GivenDailyParseResult(); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Never()); + } + + [Test] + public void should_fallback_to_daily_episode_lookup_when_search_criteria_episode_doesnt_match() + { + GivenDailySeries(); + _parsedEpisodeInfo.AirDate = DateTime.Today.AddDays(-5); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Once()); + } + + [Test] + public void should_use_scene_numbering_when_series_uses_scene_numbering() + { + GivenSceneNumberingSeries(); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), true), Times.Once()); + } + + [Test] + public void should_match_search_criteria_by_scene_numbering() + { + GivenSceneNumberingSeries(); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), true), Times.Never()); + } + + [Test] + public void should_fallback_to_findEpisode_when_search_criteria_match_fails_for_scene_numbering() + { + GivenSceneNumberingSeries(); + _episodes.First().SceneEpisodeNumber = 10; + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), true), Times.Once()); + } + + [Test] + public void should_find_episode() + { + Subject.Map(_parsedEpisodeInfo, _series.TvRageId); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), false), Times.Once()); + } + + [Test] + public void should_match_episode_with_search_criteria() + { + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), false), Times.Never()); + } + + [Test] + public void should_fallback_to_findEpisode_when_search_criteria_match_fails() + { + _episodes.First().EpisodeNumber = 10; + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<IEpisodeService>() + .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), false), Times.Once()); + } + } +} diff --git a/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs b/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs new file mode 100644 index 000000000..efa6c5b3d --- /dev/null +++ b/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests +{ + [TestFixture] + public class MapFixture : TestBase<ParsingService> + { + private Series _series; + private List<Episode> _episodes; + private ParsedEpisodeInfo _parsedEpisodeInfo; + private SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria; + + [SetUp] + public void Setup() + { + _series = Builder<Series>.CreateNew() + .With(s => s.Title = "30 Rock") + .With(s => s.CleanTitle = "rock") + .Build(); + + _episodes = Builder<Episode>.CreateListOfSize(1) + .All() + .With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT)) + .Build() + .ToList(); + + _parsedEpisodeInfo = new ParsedEpisodeInfo + { + SeriesTitle = _series.Title, + SeasonNumber = 1, + EpisodeNumbers = new[] { 1 } + }; + + _singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria + { + Series = _series, + EpisodeNumber = _episodes.First().EpisodeNumber, + SeasonNumber = _episodes.First().SeasonNumber, + Episodes = _episodes + }; + } + + private void GivenMatchBySeriesTitle() + { + Mocker.GetMock<ISeriesService>() + .Setup(s => s.FindByTitle(It.IsAny<String>())) + .Returns(_series); + } + + private void GivenMatchByTvRageId() + { + Mocker.GetMock<ISeriesService>() + .Setup(s => s.FindByTvRageId(It.IsAny<Int32>())) + .Returns(_series); + } + + private void GivenParseResultSeriesDoesntMatchSearchCriteria() + { + _parsedEpisodeInfo.SeriesTitle = "Another Name"; + } + + [Test] + public void should_lookup_series_by_name() + { + GivenMatchBySeriesTitle(); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId); + + Mocker.GetMock<ISeriesService>() + .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Once()); + } + + [Test] + public void should_use_tvrageid_when_series_title_lookup_fails() + { + GivenMatchByTvRageId(); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId); + + Mocker.GetMock<ISeriesService>() + .Verify(v => v.FindByTvRageId(It.IsAny<Int32>()), Times.Once()); + } + + [Test] + public void should_use_search_criteria_series_title() + { + GivenMatchBySeriesTitle(); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<ISeriesService>() + .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Never()); + } + + [Test] + public void should_FindByTitle_when_search_criteria_matching_fails() + { + GivenParseResultSeriesDoesntMatchSearchCriteria(); + + Subject.Map(_parsedEpisodeInfo, 10, _singleEpisodeSearchCriteria); + + Mocker.GetMock<ISeriesService>() + .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Once()); + } + + [Test] + public void should_FindByTvRageId_when_search_criteria_and_FIndByTitle_matching_fails() + { + GivenParseResultSeriesDoesntMatchSearchCriteria(); + + Subject.Map(_parsedEpisodeInfo, 10, _singleEpisodeSearchCriteria); + + Mocker.GetMock<ISeriesService>() + .Verify(v => v.FindByTvRageId(It.IsAny<Int32>()), Times.Once()); + } + + [Test] + public void should_use_tvdbid_matching_when_alias_is_found() + { + Mocker.GetMock<ISceneMappingService>() + .Setup(s => s.GetTvDbId(It.IsAny<String>())) + .Returns(_series.TvdbId); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<ISeriesService>() + .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Never()); + } + + [Test] + public void should_use_tvrageid_match_from_search_criteria_when_title_match_fails() + { + GivenParseResultSeriesDoesntMatchSearchCriteria(); + + Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria); + + Mocker.GetMock<ISeriesService>() + .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Never()); + } + } +} diff --git a/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index a1a8c60f0..3eb98a5b9 100644 --- a/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -1,6 +1,4 @@ - - -using System; +using System; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Qualities; @@ -12,120 +10,6 @@ namespace NzbDrone.Core.Test.ParserTests public class QualityParserFixture : CoreTest { - public static object[] SdtvCases = - { - new object[] { "S07E23 .avi ", false }, - new object[] {"The.Shield.S01E13.x264-CtrlSD", false}, - new object[] { "Nikita S02E01 HDTV XviD 2HD", false }, - new object[] { "Gossip Girl S05E11 PROPER HDTV XviD 2HD", true }, - new object[] { "The Jonathan Ross Show S02E08 HDTV x264 FTP", false }, - new object[] { "White.Van.Man.2011.S02E01.WS.PDTV.x264-TLA", false }, - new object[] { "White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA", true }, - new object[] { "The Real Housewives of Vancouver S01E04 DSR x264 2HD", false }, - new object[] { "Vanguard S01E04 Mexicos Death Train DSR x264 MiNDTHEGAP", false }, - new object[] { "Chuck S11E03 has no periods or extension HDTV", false }, - new object[] { "Chuck.S04E05.HDTV.XviD-LOL", false }, - new object[] { "Sonny.With.a.Chance.S02E15.avi", false }, - new object[] { "Sonny.With.a.Chance.S02E15.xvid", false }, - new object[] { "Sonny.With.a.Chance.S02E15.divx", false }, - new object[] { "The.Girls.Next.Door.S03E06.HDTV-WiDE", false }, - new object[] { "Degrassi.S10E27.WS.DSR.XviD-2HD", false }, - }; - - public static object[] DvdCases = - { - new object[] { "WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3-REPACK.-HELLYWOOD.avi", true }, - new object[] { "The.Shield.S01E13.NTSC.x264-CtrlSD", false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false }, - new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false }, - new object[] { "WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3.-HELLYWOOD.avi", false }, - new object[] { "The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", false }, - new object[] { "The.Girls.Next.Door.S03E06.DVD.Rip.XviD-WiDE", false }, - new object[] { "the.shield.1x13.circles.ws.xvidvd-tns", false} - }; - - public static object[] Webdl480pCases = - { - new object[] { "Elementary.S01E10.The.Leviathan.480p.WEB-DL.x264-mSD", false }, - new object[] { "Glee.S04E10.Glee.Actually.480p.WEB-DL.x264-mSD", false }, - new object[] { "The.Big.Bang.Theory.S06E11.The.Santa.Simulation.480p.WEB-DL.x264-mSD", false }, - }; - - public static object[] Hdtv720pCases = - { - new object[] { "Dexter - S01E01 - Title [HDTV]", false }, - new object[] { "Dexter - S01E01 - Title [HDTV-720p]", false }, - new object[] { "Pawn Stars S04E87 REPACK 720p HDTV x264 aAF", true }, - new object[] { "Sonny.With.a.Chance.S02E15.720p", false }, - new object[] { "S07E23 - [HDTV-720p].mkv ", false }, - new object[] { "Chuck - S22E03 - MoneyBART - HD TV.mkv", false }, - new object[] { "S07E23.mkv ", false }, - new object[] { "Two.and.a.Half.Men.S08E05.720p.HDTV.X264-DIMENSION", false }, - new object[] { "Sonny.With.a.Chance.S02E15.mkv", false }, - }; - - public static object[] Hdtv1080pCases = - { - new object[] { "Under the Dome S01E10 Let the Games Begin 1080p", false }, - new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.X264-QCF", false }, - new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.x264-QCF", false }, - new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.proper.X264-QCF", true }, - new object[] { "Dexter - S01E01 - Title [HDTV-1080p]", false }, - }; - - public static object[] Webdl720pCases = - { - new object[] { "Arrested.Development.S04E01.720p.WEBRip.AAC2.0.x264-NFRiP", false }, - new object[] { "Vanguard S01E04 Mexicos Death Train 720p WEB DL", false }, - new object[] { "Hawaii Five 0 S02E21 720p WEB DL DD5 1 H 264", false }, - new object[] { "Castle S04E22 720p WEB DL DD5 1 H 264 NFHD", false }, - new object[] { "Chuck - S11E06 - D-Yikes! - 720p WEB-DL.mkv", false }, - new object[] { "Sonny.With.a.Chance.S02E15.720p.WEB-DL.DD5.1.H.264-SURFER", false }, - new object[] { "S07E23 - [WEBDL].mkv ", false }, - new object[] { "Fringe S04E22 720p WEB-DL DD5.1 H264-EbP.mkv", false }, - }; - - public static object[] Webdl1080pCases = - { - new object[] { "Arrested.Development.S04E01.iNTERNAL.1080p.WEBRip.x264-QRUS", false }, - new object[] { "CSI NY S09E03 1080p WEB DL DD5 1 H264 NFHD", false }, - new object[] { "Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 NFHD", false }, - new object[] { "Criminal.Minds.S08E01.1080p.WEB-DL.DD5.1.H264-NFHD", false }, - new object[] { "Its.Always.Sunny.in.Philadelphia.S08E01.1080p.WEB-DL.proper.AAC2.0.H.264", true }, - new object[] { "Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 REPACK NFHD", true }, - new object[] { "Glee.S04E09.Swan.Song.1080p.WEB-DL.DD5.1.H.264-ECI", false }, - new object[] { "The.Big.Bang.Theory.S06E11.The.Santa.Simulation.1080p.WEB-DL.DD5.1.H.264", false }, - }; - - public static object[] Bluray720pCases = - { - new object[] { "WEEDS.S03E01-06.DUAL.Bluray.AC3.-HELLYWOOD.avi", false }, - new object[] { "Chuck - S01E03 - Come Fly With Me - 720p BluRay.mkv", false }, - }; - - public static object[] Bluray1080pCases = - { - new object[] { "Chuck - S01E03 - Come Fly With Me - 1080p BluRay.mkv", false }, - new object[] { "Sons.Of.Anarchy.S02E13.1080p.BluRay.x264-AVCDVD", false }, - }; - - public static object[] RawCases = - { - new object[] { "POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false }, - new object[] { "How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false }, - new object[] { "The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false }, - }; - - public static object[] UnknownCases = - { - new object[] { "Sonny.With.a.Chance.S02E15", false }, - new object[] { "Law & Order: Special Victims Unit - 11x11 - Quickie", false }, - - }; - public static object[] SelfQualityParserCases = { new object[] { Quality.SDTV }, @@ -138,92 +22,130 @@ namespace NzbDrone.Core.Test.ParserTests new object[] { Quality.Bluray1080p } }; - [Test, TestCaseSource("SdtvCases")] - public void should_parse_sdtv_quality(string postTitle, bool proper) - { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.SDTV); - result.Proper.Should().Be(proper); + [TestCase("S07E23 .avi ", false)] + [TestCase("The.Shield.S01E13.x264-CtrlSD", false)] + [TestCase("Nikita S02E01 HDTV XviD 2HD", false)] + [TestCase("Gossip Girl S05E11 PROPER HDTV XviD 2HD", true)] + [TestCase("The Jonathan Ross Show S02E08 HDTV x264 FTP", false)] + [TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-TLA", false)] + [TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA", true)] + [TestCase("The Real Housewives of Vancouver S01E04 DSR x264 2HD", false)] + [TestCase("Vanguard S01E04 Mexicos Death Train DSR x264 MiNDTHEGAP", false)] + [TestCase("Chuck S11E03 has no periods or extension HDTV", false)] + [TestCase("Chuck.S04E05.HDTV.XviD-LOL", false)] + [TestCase("Sonny.With.a.Chance.S02E15.avi", false)] + [TestCase("Sonny.With.a.Chance.S02E15.xvid", false)] + [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)] + public void should_parse_sdtv_quality(string title, bool proper) + { + ParseAndVerifyQuality(title, Quality.SDTV, proper); } - [Test, TestCaseSource("DvdCases")] - public void should_parse_dvd_quality(string postTitle, bool proper) - { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.DVD); - result.Proper.Should().Be(proper); + [TestCase("WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3-REPACK.-HELLYWOOD.avi", true)] + [TestCase("The.Shield.S01E13.NTSC.x264-CtrlSD", false)] + [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", false)] + [TestCase("WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", false)] + [TestCase("WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", false)] + [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false)] + [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false)] + [TestCase("WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3.-HELLYWOOD.avi", false)] + [TestCase("The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", false)] + [TestCase("The.Girls.Next.Door.S03E06.DVD.Rip.XviD-WiDE", false)] + [TestCase("the.shield.1x13.circles.ws.xvidvd-tns", false)] + public void should_parse_dvd_quality(string title, bool proper) + { + ParseAndVerifyQuality(title, Quality.DVD, proper); } - [Test, TestCaseSource("Webdl480pCases")] - public void should_parse_webdl480p_quality(string postTitle, bool proper) + [TestCase("Elementary.S01E10.The.Leviathan.480p.WEB-DL.x264-mSD", false)] + [TestCase("Glee.S04E10.Glee.Actually.480p.WEB-DL.x264-mSD", false)] + [TestCase("The.Big.Bang.Theory.S06E11.The.Santa.Simulation.480p.WEB-DL.x264-mSD", false)] + public void should_parse_webdl480p_quality(string title, bool proper) { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.WEBDL480p); - result.Proper.Should().Be(proper); + ParseAndVerifyQuality(title, Quality.WEBDL480p, proper); } - [Test, TestCaseSource("Hdtv720pCases")] - public void should_parse_hdtv720p_quality(string postTitle, bool proper) - { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.HDTV720p); - result.Proper.Should().Be(proper); + [TestCase("Dexter - S01E01 - Title [HDTV]", false)] + [TestCase("Dexter - S01E01 - Title [HDTV-720p]", false)] + [TestCase("Pawn Stars S04E87 REPACK 720p HDTV x264 aAF", true)] + [TestCase("Sonny.With.a.Chance.S02E15.720p", false)] + [TestCase("S07E23 - [HDTV-720p].mkv ", false)] + [TestCase("Chuck - S22E03 - MoneyBART - HD TV.mkv", false)] + [TestCase("S07E23.mkv ", false)] + [TestCase("Two.and.a.Half.Men.S08E05.720p.HDTV.X264-DIMENSION", false)] + [TestCase("Sonny.With.a.Chance.S02E15.mkv", false)] + [TestCase(@"E:\Downloads\tv\The.Big.Bang.Theory.S01E01.720p.HDTV\ajifajjjeaeaeqwer_eppj.avi", false)] + public void should_parse_hdtv720p_quality(string title, bool proper) + { + ParseAndVerifyQuality(title, Quality.HDTV720p, proper); } - [Test, TestCaseSource("Hdtv1080pCases")] - public void should_parse_hdtv1080p_quality(string postTitle, bool proper) + [TestCase("Under the Dome S01E10 Let the Games Begin 1080p", false)] + [TestCase("DEXTER.S07E01.ARE.YOU.1080P.HDTV.X264-QCF", false)] + [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)] + public void should_parse_hdtv1080p_quality(string title, bool proper) { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.HDTV1080p); - result.Proper.Should().Be(proper); + ParseAndVerifyQuality(title, Quality.HDTV1080p, proper); } - [Test, TestCaseSource("Webdl720pCases")] - public void should_parse_webdl720p_quality(string postTitle, bool proper) - { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.WEBDL720p); - result.Proper.Should().Be(proper); + [TestCase("Arrested.Development.S04E01.720p.WEBRip.AAC2.0.x264-NFRiP", false)] + [TestCase("Vanguard S01E04 Mexicos Death Train 720p WEB DL", false)] + [TestCase("Hawaii Five 0 S02E21 720p WEB DL DD5 1 H 264", false)] + [TestCase("Castle S04E22 720p WEB DL DD5 1 H 264 NFHD", false)] + [TestCase("Chuck - S11E06 - D-Yikes! - 720p WEB-DL.mkv", false)] + [TestCase("Sonny.With.a.Chance.S02E15.720p.WEB-DL.DD5.1.H.264-SURFER", false)] + [TestCase("S07E23 - [WEBDL].mkv ", false)] + [TestCase("Fringe S04E22 720p WEB-DL DD5.1 H264-EbP.mkv", false)] + public void should_parse_webdl720p_quality(string title, bool proper) + { + ParseAndVerifyQuality(title, Quality.WEBDL720p, proper); } - [Test, TestCaseSource("Webdl1080pCases")] - public void should_parse_webdl1080p_quality(string postTitle, bool proper) - { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.WEBDL1080p); - result.Proper.Should().Be(proper); + [TestCase("Arrested.Development.S04E01.iNTERNAL.1080p.WEBRip.x264-QRUS", false)] + [TestCase("CSI NY S09E03 1080p WEB DL DD5 1 H264 NFHD", false)] + [TestCase("Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 NFHD", false)] + [TestCase("Criminal.Minds.S08E01.1080p.WEB-DL.DD5.1.H264-NFHD", false)] + [TestCase("Its.Always.Sunny.in.Philadelphia.S08E01.1080p.WEB-DL.proper.AAC2.0.H.264", true)] + [TestCase("Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 REPACK NFHD", true)] + [TestCase("Glee.S04E09.Swan.Song.1080p.WEB-DL.DD5.1.H.264-ECI", false)] + [TestCase("The.Big.Bang.Theory.S06E11.The.Santa.Simulation.1080p.WEB-DL.DD5.1.H.264", false)] + public void should_parse_webdl1080p_quality(string title, bool proper) + { + ParseAndVerifyQuality(title, Quality.WEBDL1080p, proper); } - [Test, TestCaseSource("Bluray720pCases")] - public void should_parse_bluray720p_quality(string postTitle, bool proper) + [TestCase("WEEDS.S03E01-06.DUAL.Bluray.AC3.-HELLYWOOD.avi", false)] + [TestCase("Chuck - S01E03 - Come Fly With Me - 720p BluRay.mkv", false)] + [TestCase("The Big Bang Theory.S03E01.The Electric Can Opener Fluctuation.m2ts", false)] + public void should_parse_bluray720p_quality(string title, bool proper) { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.Bluray720p); - result.Proper.Should().Be(proper); + ParseAndVerifyQuality(title, Quality.Bluray720p, proper); } - [Test, TestCaseSource("Bluray1080pCases")] - public void should_parse_bluray1080p_quality(string postTitle, bool proper) + [TestCase("Chuck - S01E03 - Come Fly With Me - 1080p BluRay.mkv", false)] + [TestCase("Sons.Of.Anarchy.S02E13.1080p.BluRay.x264-AVCDVD", false)] + public void should_parse_bluray1080p_quality(string title, bool proper) { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.Bluray1080p); - result.Proper.Should().Be(proper); + ParseAndVerifyQuality(title, Quality.Bluray1080p, proper); } - [Test, TestCaseSource("RawCases")] - public void should_parse_raw_quality(string postTitle, bool proper) + [TestCase("POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false)] + [TestCase("How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false)] + [TestCase("The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false)] + public void should_parse_raw_quality(string title, bool proper) { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.RAWHD); - result.Proper.Should().Be(proper); + ParseAndVerifyQuality(title, Quality.RAWHD, proper); } - [Test, TestCaseSource("UnknownCases")] - public void quality_parse(string postTitle, bool proper) + [TestCase("Sonny.With.a.Chance.S02E15", false)] + [TestCase("Law & Order: Special Victims Unit - 11x11 - Quickie", false)] + public void quality_parse(string title, bool proper) { - var result = Parser.QualityParser.ParseQuality(postTitle); - result.Quality.Should().Be(Quality.Unknown); - result.Proper.Should().Be(proper); + ParseAndVerifyQuality(title, Quality.Unknown, proper); } [Test, TestCaseSource("SelfQualityParserCases")] @@ -233,5 +155,12 @@ namespace NzbDrone.Core.Test.ParserTests var result = Parser.QualityParser.ParseQuality(fileName); result.Quality.Should().Be(quality); } + + private void ParseAndVerifyQuality(string title, Quality quality, bool proper) + { + var result = Parser.QualityParser.ParseQuality(title); + result.Quality.Should().Be(quality); + result.Proper.Should().Be(proper); + } } } diff --git a/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetVideoFilesFixture.cs b/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetVideoFilesFixture.cs index b73e09984..bb2bb4f93 100644 --- a/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetVideoFilesFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetVideoFilesFixture.cs @@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests public void should_return_video_files_only() { var path = @"C:\Test\"; - + var test = Subject.GetVideoFiles(path); Subject.GetVideoFiles(path).Should().HaveCount(4); } } diff --git a/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs b/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs index 2589fe5df..1b689c53c 100644 --- a/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs +++ b/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs @@ -12,34 +12,88 @@ namespace NzbDrone.Core.Test.SeriesStatsTests [TestFixture] public class SeriesStatisticsFixture : DbTest<SeriesStatisticsRepository, Series> { + private Series _series; private Episode _episode; [SetUp] public void Setup() { - var series = Builder<Series>.CreateNew() + _series = Builder<Series>.CreateNew() .With(s => s.Id = 0) .With(s => s.Runtime = 30) .Build(); - series.Id = Db.Insert(series).Id; + _series.Id = Db.Insert(_series).Id; _episode = Builder<Episode>.CreateNew() .With(e => e.Id = 0) - .With(e => e.SeriesId = series.Id) + .With(e => e.EpisodeFileId = 0) + .With(e => e.Monitored = false) + .With(e => e.SeriesId = _series.Id) .With(e => e.AirDateUtc = DateTime.Today.AddDays(5)) .Build(); + } + private void GivenEpisodeWithFile() + { + _episode.EpisodeFileId = 1; + } + + private void GivenMonitoredEpisode() + { + _episode.Monitored = true; + } + + private void GivenFile() + { Db.Insert(_episode); } [Test] public void should_get_stats_for_series() { + GivenMonitoredEpisode(); + GivenFile(); + var stats = Subject.SeriesStatistics(); stats.Should().HaveCount(1); stats.First().NextAiring.Should().Be(_episode.AirDateUtc); } + + [Test] + public void should_not_have_next_airing_for_episode_with_file() + { + GivenEpisodeWithFile(); + GivenFile(); + + var stats = Subject.SeriesStatistics(); + + stats.Should().HaveCount(1); + stats.First().NextAiring.Should().NotHaveValue(); + } + + [Test] + public void should_not_include_unmonitored_episode_in_episode_count() + { + GivenFile(); + + var stats = Subject.SeriesStatistics(); + + stats.Should().HaveCount(1); + stats.First().EpisodeCount.Should().Be(0); + } + + [Test] + public void should_include_unmonitored_episode_with_file_in_episode_count() + { + GivenEpisodeWithFile(); + GivenFile(); + + var stats = Subject.SeriesStatistics(); + + stats.Should().HaveCount(1); + stats.First().EpisodeCount.Should().Be(1); + } } } diff --git a/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs b/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs index 496bcf77e..e08d77b23 100644 --- a/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs +++ b/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs @@ -14,20 +14,24 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests private Series _monitoredSeries; private Series _unmonitoredSeries; private PagingSpec<Episode> _pagingSpec; - + [SetUp] public void Setup() { _monitoredSeries = Builder<Series>.CreateNew() .With(s => s.Id = 0) + .With(s => s.TvRageId = RandomNumber) .With(s => s.Runtime = 30) .With(s => s.Monitored = true) + .With(s => s.TitleSlug = "Title3") .Build(); _unmonitoredSeries = Builder<Series>.CreateNew() .With(s => s.Id = 0) + .With(s => s.TvdbId = RandomNumber) .With(s => s.Runtime = 30) .With(s => s.Monitored = false) + .With(s => s.TitleSlug = "Title2") .Build(); _monitoredSeries.Id = Db.Insert(_monitoredSeries).Id; @@ -44,6 +48,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests var monitoredSeriesEpisodes = Builder<Episode>.CreateListOfSize(3) .All() .With(e => e.Id = 0) + .With(e => e.TvDbEpisodeId = RandomNumber) .With(e => e.SeriesId = _monitoredSeries.Id) .With(e => e.EpisodeFileId = 0) .With(e => e.AirDateUtc = DateTime.Now.AddDays(-5)) @@ -57,6 +62,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests var unmonitoredSeriesEpisodes = Builder<Episode>.CreateListOfSize(3) .All() .With(e => e.Id = 0) + .With(e => e.TvDbEpisodeId = RandomNumber) .With(e => e.SeriesId = _unmonitoredSeries.Id) .With(e => e.EpisodeFileId = 0) .With(e => e.AirDateUtc = DateTime.Now.AddDays(-5)) @@ -67,6 +73,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests .With(e => e.SeasonNumber = 0) .Build(); + Db.InsertMany(monitoredSeriesEpisodes); Db.InsertMany(unmonitoredSeriesEpisodes); } diff --git a/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs b/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs index 428568e8d..a3cd0b10a 100644 Binary files a/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs and b/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs differ diff --git a/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs b/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs deleted file mode 100644 index efb11c7bb..000000000 --- a/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.TvTests -{ - public class SeasonProviderTest : DbTest<SeasonRepository, Season> - { - [TestCase(true)] - [TestCase(false)] - public void Ismonitored_should_return_monitored_status_of_season(bool monitored) - { - var fakeSeason = Builder<Season>.CreateNew() - .With(s => s.Monitored = monitored) - .BuildNew<Season>(); - - Db.Insert(fakeSeason); - - var result = Subject.IsMonitored(fakeSeason.SeriesId, fakeSeason.SeasonNumber); - - result.Should().Be(monitored); - } - - [Test] - public void Monitored_should_return_true_if_not_in_db() - { - Subject.IsMonitored(10, 0).Should().BeTrue(); - } - - [Test] - public void GetSeason_should_return_seasons_for_specified_series_only() - { - var seriesA = new[] { 1, 2, 3 }; - var seriesB = new[] { 4, 5, 6 }; - - var seasonsA = seriesA.Select(c => new Season {SeasonNumber = c, SeriesId = 1}).ToList(); - var seasonsB = seriesB.Select(c => new Season {SeasonNumber = c, SeriesId = 2}).ToList(); - - Subject.InsertMany(seasonsA); - Subject.InsertMany(seasonsB); - - Subject.GetSeasonNumbers(1).Should().Equal(seriesA); - Subject.GetSeasonNumbers(2).Should().Equal(seriesB); - } - - - [Test] - public void GetSeason_should_return_emptylist_if_series_doesnt_exist() - { - Subject.GetSeasonNumbers(1).Should().BeEmpty(); - } - - } -} diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs deleted file mode 100644 index d31c2b679..000000000 --- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using FizzWare.NBuilder; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Tv.Events; - -namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests -{ - [TestFixture] - public class HandleEpisodeInfoDeletedEventFixture : CoreTest<SeasonService> - { - private List<Season> _seasons; - private List<Episode> _episodes; - - [SetUp] - public void Setup() - { - _seasons = Builder<Season> - .CreateListOfSize(1) - .All() - .With(s => s.SeriesId = 1) - .Build() - .ToList(); - - _episodes = Builder<Episode> - .CreateListOfSize(1) - .All() - .With(e => e.SeasonNumber = _seasons.First().SeasonNumber) - .With(s => s.SeriesId = _seasons.First().SeasonNumber) - .Build() - .ToList(); - - Mocker.GetMock<ISeasonRepository>() - .Setup(s => s.GetSeasonBySeries(It.IsAny<int>())) - .Returns(_seasons); - - Mocker.GetMock<IEpisodeService>() - .Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), _seasons.First().SeasonNumber)) - .Returns(_episodes); - } - - private void GivenAbandonedSeason() - { - Mocker.GetMock<IEpisodeService>() - .Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), _seasons.First().SeasonNumber)) - .Returns(new List<Episode>()); - } - - [Test] - public void should_not_delete_when_season_is_still_valid() - { - Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); - - Mocker.GetMock<ISeasonRepository>() - .Verify(v => v.Delete(It.IsAny<Season>()), Times.Never()); - } - - [Test] - public void should_delete_season_if_no_episodes_exist_in_that_season() - { - GivenAbandonedSeason(); - - Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); - - Mocker.GetMock<ISeasonRepository>() - .Verify(v => v.Delete(It.IsAny<Season>()), Times.Once()); - } - - [Test] - public void should_only_delete_a_season_once() - { - _episodes = Builder<Episode> - .CreateListOfSize(5) - .All() - .With(e => e.SeasonNumber = _seasons.First().SeasonNumber) - .With(s => s.SeriesId = _seasons.First().SeasonNumber) - .Build() - .ToList(); - - GivenAbandonedSeason(); - - Subject.Handle(new EpisodeInfoDeletedEvent(_episodes)); - - Mocker.GetMock<ISeasonRepository>() - .Verify(v => v.Delete(It.IsAny<Season>()), Times.Once()); - } - } -} diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs deleted file mode 100644 index 775d7d41c..000000000 --- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests -{ - [TestFixture] - public class SetMonitoredFixture : CoreTest<SeasonService> - { - private Season _season; - - [SetUp] - public void Setup() - { - _season = new Season - { - Id = 1, - SeasonNumber = 1, - SeriesId = 1, - Monitored = false - }; - - Mocker.GetMock<ISeasonRepository>() - .Setup(s => s.Get(It.IsAny<Int32>(), It.IsAny<Int32>())) - .Returns(_season); - } - - [TestCase(true)] - [TestCase(false)] - public void should_update_season(bool monitored) - { - Subject.SetMonitored(_season.SeriesId, _season.SeasonNumber, monitored); - - Mocker.GetMock<ISeasonRepository>() - .Verify(v => v.Update(_season), Times.Once()); - } - - [TestCase(true)] - [TestCase(false)] - public void should_update_episodes_in_season(bool monitored) - { - Subject.SetMonitored(_season.SeriesId, _season.SeasonNumber, monitored); - - Mocker.GetMock<IEpisodeService>() - .Verify(v => v.SetEpisodeMonitoredBySeason(_season.SeriesId, _season.SeasonNumber, monitored), Times.Once()); - } - } -} diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs deleted file mode 100644 index b8ac4a9ea..000000000 --- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests -{ - [TestFixture] - public class SetSeasonPassFixture : CoreTest<SeasonService> - { - private const Int32 SERIES_ID = 1; - private List<Season> _seasons; - - [SetUp] - public void Setup() - { - _seasons = Builder<Season>.CreateListOfSize(5) - .All() - .With(s => s.SeriesId = SERIES_ID) - .Build() - .ToList(); - - Mocker.GetMock<ISeasonRepository>() - .Setup(s => s.GetSeasonBySeries(It.IsAny<Int32>())) - .Returns(_seasons); - } - - [Test] - public void should_updateMany() - { - Subject.SetSeasonPass(SERIES_ID, 1); - - Mocker.GetMock<ISeasonRepository>() - .Verify(v => v.UpdateMany(It.IsAny<List<Season>>()), Times.Once()); - } - - [Test] - public void should_set_lower_seasons_to_false() - { - const int seasonNumber = 3; - - var result = Subject.SetSeasonPass(SERIES_ID, seasonNumber); - - result.Where(s => s.SeasonNumber < seasonNumber).Should().OnlyContain(s => s.Monitored == false); - } - - [Test] - public void should_set_equal_or_higher_seasons_to_false() - { - const int seasonNumber = 3; - - var result = Subject.SetSeasonPass(SERIES_ID, seasonNumber); - - result.Where(s => s.SeasonNumber >= seasonNumber).Should().OnlyContain(s => s.Monitored == true); - } - - [Test] - public void should_set_episodes_in_lower_seasons_to_false() - { - const int seasonNumber = 3; - - Subject.SetSeasonPass(SERIES_ID, seasonNumber); - - Mocker.GetMock<IEpisodeService>() - .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i < seasonNumber), false), Times.AtLeastOnce()); - - Mocker.GetMock<IEpisodeService>() - .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i < seasonNumber), true), Times.Never()); - } - - [Test] - public void should_set_episodes_in_equal_or_higher_seasons_to_false() - { - const int seasonNumber = 3; - - Subject.SetSeasonPass(SERIES_ID, seasonNumber); - - Mocker.GetMock<IEpisodeService>() - .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i >= seasonNumber), true), Times.AtLeastOnce()); - - Mocker.GetMock<IEpisodeService>() - .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i >= seasonNumber), false), Times.Never()); - } - } -} diff --git a/NzbDrone.Core.Test/TvTests/SeriesServiceFixture.cs b/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs similarity index 75% rename from NzbDrone.Core.Test/TvTests/SeriesServiceFixture.cs rename to NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs index 99ead2647..ab33ff072 100644 Binary files a/NzbDrone.Core.Test/TvTests/SeriesServiceFixture.cs and b/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs differ diff --git a/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs b/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs new file mode 100644 index 000000000..903e98102 Binary files /dev/null and b/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs differ diff --git a/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs b/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs index 138e3b404..aa58b0bbd 100644 --- a/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs +++ b/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs @@ -1,22 +1,25 @@ +using System; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.Configuration; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Update; -using System.Linq; namespace NzbDrone.Core.Test.UpdateTests { public class UpdatePackageProviderFixture : CoreTest<UpdatePackageProvider> { [Test] - public void should_get_list_of_available_updates() + public void no_update_when_version_higher() { UseRealHttp(); + Subject.GetLatestUpdate("master", new Version(10,0)).Should().BeNull(); + } - Mocker.GetMock<IConfigFileProvider>().SetupGet(c => c.Branch).Returns("master"); - - Subject.GetLatestUpdate().Should().BeNull(); + [Test] + public void finds_update_when_version_lower() + { + UseRealHttp(); + Subject.GetLatestUpdate("master", new Version(1, 0)).Should().NotBeNull(); } } } diff --git a/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs index 2a485146c..31ebee3e5 100644 --- a/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs +++ b/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.IO; using FluentAssertions; using Moq; diff --git a/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/NzbDrone.Core/Configuration/ConfigFileProvider.cs index fa694700d..8389ec658 100644 --- a/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -2,13 +2,16 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Xml; using System.Xml.Linq; using NzbDrone.Common; using NzbDrone.Common.Cache; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Configuration { @@ -24,24 +27,23 @@ namespace NzbDrone.Core.Configuration string Password { get; } string LogLevel { get; } string Branch { get; } + bool Torrent { get; } } public class ConfigFileProvider : IConfigFileProvider { private const string CONFIG_ELEMENT_NAME = "Config"; - private readonly IAppFolderInfo _appFolderInfo; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly ICached<string> _cache; private readonly string _configFile; - public ConfigFileProvider(IAppFolderInfo appFolderInfo, ICacheManger cacheManger, IMessageAggregator messageAggregator) + public ConfigFileProvider(IAppFolderInfo appFolderInfo, ICacheManger cacheManger, IEventAggregator eventAggregator) { - _appFolderInfo = appFolderInfo; _cache = cacheManger.GetCache<string>(GetType()); - _messageAggregator = messageAggregator; - _configFile = _appFolderInfo.GetConfigPath(); + _eventAggregator = eventAggregator; + _configFile = appFolderInfo.GetConfigPath(); } public Dictionary<string, object> GetConfigDictionary() @@ -81,7 +83,7 @@ namespace NzbDrone.Core.Configuration } } - _messageAggregator.PublishEvent(new ConfigFileSavedEvent()); + _eventAggregator.PublishEvent(new ConfigFileSavedEvent()); } public int Port @@ -94,6 +96,11 @@ namespace NzbDrone.Core.Configuration get { return GetValueBoolean("LaunchBrowser", true); } } + public bool Torrent + { + get { return GetValueBoolean("Torrent", false, persist: false); } + } + public bool AuthenticationEnabled { get { return GetValueBoolean("AuthenticationEnabled", false); } @@ -124,9 +131,9 @@ namespace NzbDrone.Core.Configuration return Convert.ToInt32(GetValue(key, defaultValue)); } - public bool GetValueBoolean(string key, bool defaultValue) + public bool GetValueBoolean(string key, bool defaultValue, bool persist = true) { - return Convert.ToBoolean(GetValue(key, defaultValue)); + return Convert.ToBoolean(GetValue(key, defaultValue, persist)); } public T GetValueEnum<T>(string key, T defaultValue) @@ -134,13 +141,13 @@ namespace NzbDrone.Core.Configuration return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), true); } - public string GetValue(string key, object defaultValue) + public string GetValue(string key, object defaultValue, bool persist = true) { return _cache.Get(key, () => { EnsureDefaultConfigFile(); - var xDoc = XDocument.Load(_configFile); + var xDoc = LoadConfigFile(); var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single(); var parentContainer = config; @@ -151,7 +158,10 @@ namespace NzbDrone.Core.Configuration return valueHolder.First().Value; //Save the value - SetValue(key, defaultValue); + if (persist) + { + SetValue(key, defaultValue); + } //return the default value return defaultValue.ToString(); @@ -162,7 +172,7 @@ namespace NzbDrone.Core.Configuration { EnsureDefaultConfigFile(); - var xDoc = XDocument.Load(_configFile); + var xDoc = LoadConfigFile(); var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single(); var parentContainer = config; @@ -203,7 +213,7 @@ namespace NzbDrone.Core.Configuration { EnsureDefaultConfigFile(); - var xDoc = XDocument.Load(_configFile); + var xDoc = LoadConfigFile(); var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single(); var type = GetType(); @@ -222,6 +232,19 @@ namespace NzbDrone.Core.Configuration xDoc.Save(_configFile); } + private XDocument LoadConfigFile() + { + try + { + return XDocument.Load(_configFile); + } + + catch (XmlException ex) + { + throw new InvalidConfigFileException(_configFile + " is invalid, please see the http://wiki.nzbdrone.com for steps to resolve this issue.", ex); + } + } + public void HandleAsync(ApplicationStartedEvent message) { DeleteOldValues(); diff --git a/NzbDrone.Core/Configuration/ConfigRepository.cs b/NzbDrone.Core/Configuration/ConfigRepository.cs index b0665445e..0b8c3fffb 100644 --- a/NzbDrone.Core/Configuration/ConfigRepository.cs +++ b/NzbDrone.Core/Configuration/ConfigRepository.cs @@ -1,6 +1,8 @@ using System.Linq; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Configuration { @@ -12,8 +14,8 @@ namespace NzbDrone.Core.Configuration public class ConfigRepository : BasicRepository<Config>, IConfigRepository { - public ConfigRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public ConfigRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/Configuration/ConfigService.cs b/NzbDrone.Core/Configuration/ConfigService.cs index e3ca0317b..1e1af6182 100644 --- a/NzbDrone.Core/Configuration/ConfigService.cs +++ b/NzbDrone.Core/Configuration/ConfigService.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Download; using NzbDrone.Core.Download.Clients.Nzbget; using NzbDrone.Core.Download.Clients.Sabnzbd; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Configuration { @@ -18,14 +20,14 @@ namespace NzbDrone.Core.Configuration public class ConfigService : IConfigService { private readonly IConfigRepository _repository; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; private static Dictionary<string, string> _cache; - public ConfigService(IConfigRepository repository, IMessageAggregator messageAggregator, Logger logger) + public ConfigService(IConfigRepository repository, IEventAggregator eventAggregator, Logger logger) { _repository = repository; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _logger = logger; _cache = new Dictionary<string, string>(); } @@ -68,7 +70,7 @@ namespace NzbDrone.Core.Configuration SetValue(configValue.Key, configValue.Value.ToString()); } - _messageAggregator.PublishEvent(new ConfigSavedEvent()); + _eventAggregator.PublishEvent(new ConfigSavedEvent()); } public String SabHost diff --git a/NzbDrone.Core/Configuration/InvalidConfigFileException.cs b/NzbDrone.Core/Configuration/InvalidConfigFileException.cs new file mode 100644 index 000000000..7069749ae --- /dev/null +++ b/NzbDrone.Core/Configuration/InvalidConfigFileException.cs @@ -0,0 +1,12 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Configuration +{ + public class InvalidConfigFileException : NzbDroneException + { + public InvalidConfigFileException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs b/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs index f59ff9ee9..b5977e053 100644 --- a/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs +++ b/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries { try { - var dailySeriesIds = _httpProvider.DownloadString(Services.RootUrl + "/DailySeries/AllIds"); + var dailySeriesIds = _httpProvider.DownloadString(Services.RootUrl + "/v1/DailySeries"); var seriesIds = Json.Deserialize<List<int>>(dailySeriesIds); @@ -46,7 +46,7 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries { try { - var result = _httpProvider.DownloadString(Services.RootUrl + "/DailySeries/Check?seriesId=" + tvdbid); + var result = _httpProvider.DownloadString(Services.RootUrl + "/v1/DailySeries?seriesId=" + tvdbid); return Convert.ToBoolean(result); } catch (Exception ex) diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs index 011096f15..07bdfc065 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs @@ -5,15 +5,15 @@ namespace NzbDrone.Core.DataAugmentation.Scene { public class SceneMapping : ModelBase { - [JsonProperty("CleanTitle")] + [JsonProperty("title")] public string ParseTerm { get; set; } - [JsonProperty("Title")] + [JsonProperty("searchTitle")] public string SearchTerm { get; set; } - [JsonProperty("Id")] public int TvdbId { get; set; } + [JsonProperty("season")] public int SeasonNumber { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs index 2447a96ec..9bda36efc 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using NzbDrone.Common; using NzbDrone.Common.Serializer; -using NzbDrone.Core.Configuration; namespace NzbDrone.Core.DataAugmentation.Scene { @@ -21,7 +20,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene public List<SceneMapping> Fetch() { - var mappingsJson = _httpProvider.DownloadString(Services.RootUrl + "/SceneMapping/Active"); + var mappingsJson = _httpProvider.DownloadString(Services.RootUrl + "/v1/SceneMapping"); return Json.Deserialize<List<SceneMapping>>(mappingsJson); } } diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingRepository.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingRepository.cs index 417b6cf11..5f1ade5c2 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingRepository.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingRepository.cs @@ -1,5 +1,7 @@ -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.DataAugmentation.Scene { @@ -10,8 +12,8 @@ namespace NzbDrone.Core.DataAugmentation.Scene public class SceneMappingRepository : BasicRepository<SceneMapping>, ISceneMappingRepository { - public SceneMappingRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public SceneMappingRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs index 3c84d378c..d05dd69fd 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs @@ -2,8 +2,10 @@ using System; using System.Linq; using NLog; using NzbDrone.Common.Cache; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; namespace NzbDrone.Core.DataAugmentation.Scene @@ -18,7 +20,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene IHandleAsync<ApplicationStartedEvent>, IExecute<UpdateSceneMappingCommand> { - private readonly ISceneMappingRepository _repository; private readonly ISceneMappingProxy _sceneMappingProxy; private readonly Logger _logger; @@ -44,7 +45,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene return mapping.SearchTerm; } - public Nullable<Int32> GetTvDbId(string cleanName) { var mapping = _gettvdbIdCache.Find(cleanName.CleanSeriesTitle()); @@ -55,7 +55,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene return mapping.TvdbId; } - private void UpdateMappings() { _logger.Info("Updating Scene mapping"); @@ -74,7 +73,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene } _repository.InsertMany(mappings); - } else { diff --git a/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs b/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs index 68356ab6e..215f8e033 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs @@ -1,8 +1,9 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.DataAugmentation.Scene { - public class UpdateSceneMappingCommand : ICommand + public class UpdateSceneMappingCommand : Command { + } } \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/BasicRepository.cs b/NzbDrone.Core/Datastore/BasicRepository.cs index daefd7eac..8552930fd 100644 --- a/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/NzbDrone.Core/Datastore/BasicRepository.cs @@ -4,9 +4,10 @@ using System.Linq; using System.Linq.Expressions; using Marr.Data; using Marr.Data.QGen; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore.Events; using NzbDrone.Common; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Datastore @@ -38,17 +39,17 @@ namespace NzbDrone.Core.Datastore public class BasicRepository<TModel> : IBasicRepository<TModel> where TModel : ModelBase, new() { private readonly IDatabase _database; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private IDataMapper DataMapper { get { return _database.GetDataMapper(); } } - public BasicRepository(IDatabase database, IMessageAggregator messageAggregator) + public BasicRepository(IDatabase database, IEventAggregator eventAggregator) { _database = database; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; } protected QueryBuilder<TModel> Query @@ -115,7 +116,6 @@ namespace NzbDrone.Core.Datastore } DataMapper.Insert(model); - PublishModelEvent(model, RepositoryAction.Created); return model; } @@ -222,22 +222,27 @@ namespace NzbDrone.Core.Datastore DataMapper.Delete<TModel>(c => c.Id > 0); } - private void PublishModelEvent(TModel model, RepositoryAction action) + protected void ModelCreated(TModel model) { - if (PublishModelEvents) - { - _messageAggregator.PublishEvent(new ModelEvent<TModel>(model, action)); - } + PublishModelEvent(model, ModelAction.Created); } - protected virtual void OnModelChanged(IEnumerable<TModel> models) + protected void ModelUpdated(TModel model) { - + PublishModelEvent(model, ModelAction.Updated); } - protected virtual void OnModelDeleted(IEnumerable<TModel> models) + protected void ModelDeleted(TModel model) { + PublishModelEvent(model, ModelAction.Deleted); + } + private void PublishModelEvent(TModel model, ModelAction action) + { + if (PublishModelEvents) + { + _eventAggregator.PublishEvent(new ModelEvent<TModel>(model, action)); + } } protected virtual bool PublishModelEvents diff --git a/NzbDrone.Core/Datastore/CachedBasicRepository.cs b/NzbDrone.Core/Datastore/CachedBasicRepository.cs deleted file mode 100644 index 1938af390..000000000 --- a/NzbDrone.Core/Datastore/CachedBasicRepository.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Common.Cache; -using NzbDrone.Common.Messaging; - -namespace NzbDrone.Core.Datastore -{ - public abstract class CachedBasicRepository<TModel> : BasicRepository<TModel> where TModel : ModelBase, new() - { - private readonly ICacheManger _cacheManger; - - protected CachedBasicRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) - { - _cacheManger = new CacheManger(); - } - - protected ICached<T> GetCache<T>(string name) - { - return _cacheManger.GetCache<T>(GetType(), name); - } - - protected override void OnModelChanged(IEnumerable<TModel> models) - { - PurgeCache(); - } - - protected override void OnModelDeleted(IEnumerable<TModel> models) - { - PurgeCache(); - } - - private void PurgeCache() - { - foreach (var model in _cacheManger.Caches) - { - model.Clear(); - } - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/DbFactory.cs b/NzbDrone.Core/Datastore/DbFactory.cs index fb8d43a37..537fec3b0 100644 --- a/NzbDrone.Core/Datastore/DbFactory.cs +++ b/NzbDrone.Core/Datastore/DbFactory.cs @@ -3,9 +3,10 @@ using System.Data.SQLite; using Marr.Data; using Marr.Data.Reflection; using NzbDrone.Common.Composition; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Datastore @@ -29,12 +30,16 @@ namespace NzbDrone.Core.Datastore public static void RegisterDatabase(IContainer container) { + container.Resolve<IDbFactory>().Create(); + container.Register(c => c.Resolve<IDbFactory>().Create()); + container.Resolve<IDbFactory>().Create(MigrationType.Log); + container.Register<ILogRepository>(c => { var db = c.Resolve<IDbFactory>().Create(MigrationType.Log); - return new LogRepository(db, c.Resolve<IMessageAggregator>()); + return new LogRepository(db, c.Resolve<IEventAggregator>()); }); } diff --git a/NzbDrone.Core/Datastore/Events/ModelEvent.cs b/NzbDrone.Core/Datastore/Events/ModelEvent.cs index 30b9af580..3cabd3a69 100644 --- a/NzbDrone.Core/Datastore/Events/ModelEvent.cs +++ b/NzbDrone.Core/Datastore/Events/ModelEvent.cs @@ -2,23 +2,25 @@ namespace NzbDrone.Core.Datastore.Events { - public class ModelEvent<T> : IEvent where T : ModelBase + public class ModelEvent <TModel> : IEvent { - public T Model { get; set; } - public RepositoryAction Action { get; set; } + public TModel Model { get; set; } + public ModelAction Action { get; set; } - public ModelEvent(T model, RepositoryAction action) + public ModelEvent(TModel model, ModelAction action) { Model = model; Action = action; } } - public enum RepositoryAction + public enum ModelAction { + Unknow = 0, Created = 1, Updated = 2, - Deleted = 3 + Deleted = 3, + Sync = 4 } diff --git a/NzbDrone.Core/Datastore/Migration/008_remove_backlog.cs b/NzbDrone.Core/Datastore/Migration/008_remove_backlog.cs index 6614e2c14..83fd62478 100644 --- a/NzbDrone.Core/Datastore/Migration/008_remove_backlog.cs +++ b/NzbDrone.Core/Datastore/Migration/008_remove_backlog.cs @@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - SQLiteAlter.DropColumns("Series", new[] { "BacklogSetting" }); - SQLiteAlter.DropColumns("NamingConfig", new[] { "UseSceneName" }); + SqLiteAlter.DropColumns("Series", new[] { "BacklogSetting" }); + SqLiteAlter.DropColumns("NamingConfig", new[] { "UseSceneName" }); } } } diff --git a/NzbDrone.Core/Datastore/Migration/009_fix_renameEpisodes.cs b/NzbDrone.Core/Datastore/Migration/009_fix_renameEpisodes.cs index d52799a03..f5ef5b2d1 100644 --- a/NzbDrone.Core/Datastore/Migration/009_fix_renameEpisodes.cs +++ b/NzbDrone.Core/Datastore/Migration/009_fix_renameEpisodes.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - SQLiteAlter.DropColumns("NamingConfig", new[] { "SeasonFolderFormat" }); + SqLiteAlter.DropColumns("NamingConfig", new[] { "SeasonFolderFormat" }); Execute.Sql("UPDATE NamingConfig SET RenameEpisodes = 1 WHERE RenameEpisodes = -1"); Execute.Sql("UPDATE NamingConfig SET RenameEpisodes = 0 WHERE RenameEpisodes = -2"); diff --git a/NzbDrone.Core/Datastore/Migration/011_remove_ignored.cs b/NzbDrone.Core/Datastore/Migration/011_remove_ignored.cs index 103f0648a..8f62c3b46 100644 --- a/NzbDrone.Core/Datastore/Migration/011_remove_ignored.cs +++ b/NzbDrone.Core/Datastore/Migration/011_remove_ignored.cs @@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - SQLiteAlter.DropColumns("Episodes", new[] { "Ignored" }); - SQLiteAlter.DropColumns("Seasons", new[] { "Ignored" }); + SqLiteAlter.DropColumns("Episodes", new[] { "Ignored" }); + SqLiteAlter.DropColumns("Seasons", new[] { "Ignored" }); } } } diff --git a/NzbDrone.Core/Datastore/Migration/012_remove_custom_start_date.cs b/NzbDrone.Core/Datastore/Migration/012_remove_custom_start_date.cs index 417e73013..a7f156c87 100644 --- a/NzbDrone.Core/Datastore/Migration/012_remove_custom_start_date.cs +++ b/NzbDrone.Core/Datastore/Migration/012_remove_custom_start_date.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - SQLiteAlter.DropColumns("Series", new[] { "CustomStartDate" }); + SqLiteAlter.DropColumns("Series", new[] { "CustomStartDate" }); } } } diff --git a/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs b/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs index 434ccc8a2..b88282202 100644 --- a/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs +++ b/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - SQLiteAlter.DropColumns("Episodes", new []{ "AirDate" }); + SqLiteAlter.DropColumns("Episodes", new []{ "AirDate" }); } } } diff --git a/NzbDrone.Core/Datastore/Migration/018_remove_duplicates.cs b/NzbDrone.Core/Datastore/Migration/018_remove_duplicates.cs new file mode 100644 index 000000000..7fb11e5e7 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/018_remove_duplicates.cs @@ -0,0 +1,60 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System.Linq; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(18)] + public class remove_duplicates : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + using (var transaction = MigrationHelper.BeginTransaction()) + { + RemoveDuplicateSeries<int>("TvdbId"); + RemoveDuplicateSeries<string>("TitleSlug"); + + var duplicatedEpisodes = MigrationHelper.GetDuplicates<int>("Episodes", "TvDbEpisodeId"); + + foreach (var duplicate in duplicatedEpisodes) + { + foreach (var episodeId in duplicate.OrderBy(c => c.Key).Skip(1).Select(c => c.Key)) + { + RemoveEpisodeRows(episodeId); + } + } + + transaction.Commit(); + } + } + + private void RemoveDuplicateSeries<T>(string field) + { + var duplicatedSeries = MigrationHelper.GetDuplicates<T>("Series", field); + + foreach (var duplicate in duplicatedSeries) + { + foreach (var seriesId in duplicate.OrderBy(c => c.Key).Skip(1).Select(c => c.Key)) + { + RemoveSeriesRows(seriesId); + } + } + } + + private void RemoveSeriesRows(int seriesId) + { + MigrationHelper.ExecuteNonQuery("DELETE FROM Series WHERE Id = {0}", seriesId.ToString()); + MigrationHelper.ExecuteNonQuery("DELETE FROM Episodes WHERE SeriesId = {0}", seriesId.ToString()); + MigrationHelper.ExecuteNonQuery("DELETE FROM Seasons WHERE SeriesId = {0}", seriesId.ToString()); + MigrationHelper.ExecuteNonQuery("DELETE FROM History WHERE SeriesId = {0}", seriesId.ToString()); + MigrationHelper.ExecuteNonQuery("DELETE FROM EpisodeFiles WHERE SeriesId = {0}", seriesId.ToString()); + } + + private void RemoveEpisodeRows(int episodeId) + { + MigrationHelper.ExecuteNonQuery("DELETE FROM Episodes WHERE Id = {0}", episodeId.ToString()); + MigrationHelper.ExecuteNonQuery("DELETE FROM History WHERE EpisodeId = {0}", episodeId.ToString()); + } + + } +} diff --git a/NzbDrone.Core/Datastore/Migration/019_restore_unique_constraints.cs b/NzbDrone.Core/Datastore/Migration/019_restore_unique_constraints.cs new file mode 100644 index 000000000..c4e304df7 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/019_restore_unique_constraints.cs @@ -0,0 +1,20 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(19)] + public class restore_unique_constraints : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + SqLiteAlter.AddIndexes("Series", + new SQLiteIndex { Column = "TvdbId", Table = "Series", Unique = true }, + new SQLiteIndex { Column = "TitleSlug", Table = "Series", Unique = true }); + + SqLiteAlter.AddIndexes("Episodes", + new SQLiteIndex { Column = "TvDbEpisodeId", Table = "Episodes", Unique = true }); + } + + } +} diff --git a/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs b/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs new file mode 100644 index 000000000..0521d3bdb --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Data; +using FluentMigrator; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(20)] + public class add_year_and_seasons_to_series : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Series").AddColumn("Year").AsInt32().Nullable(); + Alter.Table("Series").AddColumn("Seasons").AsString().Nullable(); + + Execute.WithConnection(ConvertSeasons); + } + + private void ConvertSeasons(IDbConnection conn, IDbTransaction tran) + { + using (IDbCommand allSeriesCmd = conn.CreateCommand()) + { + allSeriesCmd.Transaction = tran; + allSeriesCmd.CommandText = @"SELECT Id FROM Series"; + using (IDataReader allSeriesReader = allSeriesCmd.ExecuteReader()) + { + while (allSeriesReader.Read()) + { + int seriesId = allSeriesReader.GetInt32(0); + var seasons = new List<dynamic>(); + + using (IDbCommand seasonsCmd = conn.CreateCommand()) + { + seasonsCmd.Transaction = tran; + seasonsCmd.CommandText = String.Format(@"SELECT SeasonNumber, Monitored FROM Seasons WHERE SeriesId = {0}", seriesId); + + using (IDataReader seasonReader = seasonsCmd.ExecuteReader()) + { + while (seasonReader.Read()) + { + int seasonNumber = seasonReader.GetInt32(0); + bool monitored = seasonReader.GetBoolean(1); + + if (seasonNumber == 0) + { + monitored = false; + } + + seasons.Add(new { seasonNumber, monitored }); + } + } + } + + using (IDbCommand updateCmd = conn.CreateCommand()) + { + var text = String.Format("UPDATE Series SET Seasons = '{0}' WHERE Id = {1}", seasons.ToJson() , seriesId); + + updateCmd.Transaction = tran; + updateCmd.CommandText = text; + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs b/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs new file mode 100644 index 000000000..d2527c755 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(21)] + public class drop_seasons_table : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Delete.Table("Seasons"); + } + } +} diff --git a/NzbDrone.Core/Datastore/Migration/Framework/MigrationContext.cs b/NzbDrone.Core/Datastore/Migration/Framework/MigrationContext.cs index ebf634c15..90ac77a1e 100644 --- a/NzbDrone.Core/Datastore/Migration/Framework/MigrationContext.cs +++ b/NzbDrone.Core/Datastore/Migration/Framework/MigrationContext.cs @@ -4,5 +4,6 @@ { public MigrationType MigrationType { get; set; } public ISQLiteAlter SQLiteAlter { get; set; } + public ISqLiteMigrationHelper MigrationHelper { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs b/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs index e2a0480f2..a930511c3 100644 --- a/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs +++ b/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs @@ -15,13 +15,15 @@ namespace NzbDrone.Core.Datastore.Migration.Framework { private readonly IAnnouncer _announcer; private readonly ISQLiteAlter _sqLiteAlter; + private readonly ISqLiteMigrationHelper _migrationHelper; private static readonly HashSet<string> MigrationCache = new HashSet<string>(); - public MigrationController(IAnnouncer announcer, ISQLiteAlter sqLiteAlter) + public MigrationController(IAnnouncer announcer, ISQLiteAlter sqLiteAlter, ISqLiteMigrationHelper migrationHelper) { _announcer = announcer; _sqLiteAlter = sqLiteAlter; + _migrationHelper = migrationHelper; } public void MigrateToLatest(string connectionString, MigrationType migrationType) @@ -40,7 +42,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework ApplicationContext = new MigrationContext { MigrationType = migrationType, - SQLiteAlter = _sqLiteAlter + SQLiteAlter = _sqLiteAlter, + MigrationHelper = _migrationHelper, } }; diff --git a/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneMigrationBase.cs b/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneMigrationBase.cs index c641d5f55..da9dde57a 100644 --- a/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneMigrationBase.cs +++ b/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneMigrationBase.cs @@ -1,9 +1,18 @@ using System; +using NLog; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Core.Datastore.Migration.Framework { public abstract class NzbDroneMigrationBase : FluentMigrator.Migration { + private Logger _logger; + + protected NzbDroneMigrationBase() + { + _logger = NzbDroneLogger.GetLogger(); + } + protected virtual void MainDbUpgrade() { } @@ -16,7 +25,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework { var context = (MigrationContext)ApplicationContext; - SQLiteAlter = context.SQLiteAlter; + SqLiteAlter = context.SQLiteAlter; + MigrationHelper = context.MigrationHelper; switch (context.MigrationType) { @@ -33,7 +43,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework } } - public ISQLiteAlter SQLiteAlter { get; private set; } + protected ISQLiteAlter SqLiteAlter { get; private set; } + protected ISqLiteMigrationHelper MigrationHelper { get; private set; } public override void Down() { diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteColumn.cs b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteColumn.cs new file mode 100644 index 000000000..976916885 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteColumn.cs @@ -0,0 +1,13 @@ +namespace NzbDrone.Core.Datastore.Migration.Framework +{ + public class SQLiteColumn + { + public string Name { get; set; } + public string Schema { get; set; } + + public override string ToString() + { + return string.Format("[{0}] {1}", Name, Schema); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs new file mode 100644 index 000000000..cbf18dbd9 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs @@ -0,0 +1,39 @@ +using System; + +namespace NzbDrone.Core.Datastore.Migration.Framework +{ + public class SQLiteIndex : IEquatable<SQLiteIndex> + { + public string Column { get; set; } + public string Table { get; set; } + public bool Unique { get; set; } + + public bool Equals(SQLiteIndex other) + { + return IndexName == other.IndexName; + } + + public override int GetHashCode() + { + return IndexName.GetHashCode(); + } + + public override string ToString() + { + return string.Format("[{0}] Unique: {1}", Column, Unique); + } + + public string IndexName + { + get + { + return string.Format("IX_{0}_{1}", Table, Column); + } + } + + public string CreateSql(string tableName) + { + return string.Format(@"CREATE UNIQUE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper (vaio's conflicted copy 2013-09-04).cs b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper (vaio's conflicted copy 2013-09-04).cs new file mode 100644 index 000000000..0cbf55e4c --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper (vaio's conflicted copy 2013-09-04).cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Data.SQLite; +using System.Linq; +using System.Text.RegularExpressions; +using NLog; + +namespace NzbDrone.Core.Datastore.Migration.Framework +{ + public interface ISQLiteMigrationHelper + { + Dictionary<String, SQLiteMigrationHelper.SQLiteColumn> GetColumns(string tableName); + void CreateTable(string tableName, IEnumerable<SQLiteMigrationHelper.SQLiteColumn> values, IEnumerable<SQLiteMigrationHelper.SQLiteIndex> indexes); + void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteMigrationHelper.SQLiteColumn> columns); + void DropTable(string tableName); + void RenameTable(string tableName, string newName); + List<T> GetDuplicates<T>(string tableName, string columnName); + SQLiteTransaction BeginTransaction(); + List<SQLiteMigrationHelper.SQLiteIndex> GetIndexes(string tableName); + } + + public class SQLiteMigrationHelper : ISQLiteMigrationHelper + { + private readonly SQLiteConnection _connection; + + private static readonly Regex SchemaRegex = new Regex(@"['\""\[](?<name>\w+)['\""\]]\s(?<schema>[\w-\s]+)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline); + + private static readonly Regex IndexRegex = new Regex(@"\(""(?<col>.*)""\s(?<direction>ASC|DESC)\)$", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline); + + public SQLiteMigrationHelper(IConnectionStringFactory connectionStringFactory, Logger logger) + { + try + { + _connection = new SQLiteConnection(connectionStringFactory.MainDbConnectionString); + _connection.Open(); + } + catch (Exception e) + { + logger.ErrorException("Couldn't open database " + connectionStringFactory.MainDbConnectionString, e); + throw; + } + + } + + private string GetOriginalSql(string tableName) + { + var command = + new SQLiteCommand(string.Format("SELECT sql FROM sqlite_master WHERE type='table' AND name ='{0}'", + tableName)); + + command.Connection = _connection; + + return (string)command.ExecuteScalar(); + } + + public Dictionary<String, SQLiteColumn> GetColumns(string tableName) + { + var originalSql = GetOriginalSql(tableName); + + var matches = SchemaRegex.Matches(originalSql); + + return matches.Cast<Match>().ToDictionary( + match => match.Groups["name"].Value.Trim(), + match => new SQLiteColumn + { + Name = match.Groups["name"].Value.Trim(), + Schema = match.Groups["schema"].Value.Trim() + }); + } + + + private static IEnumerable<T> ReadArray<T>(SQLiteDataReader reader) + { + while (reader.Read()) + { + yield return (T)Convert.ChangeType(reader[0], typeof(T)); + } + } + + public List<SQLiteIndex> GetIndexes(string tableName) + { + var command = new SQLiteCommand(string.Format("SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name ='{0}'", tableName)); + command.Connection = _connection; + + var reader = command.ExecuteReader(); + var sqls = ReadArray<string>(reader).ToList(); + + + var indexes = new List<SQLiteIndex>(); + + foreach (var indexSql in sqls) + { + var newIndex = new SQLiteIndex(); + var matches = IndexRegex.Match(indexSql); + + newIndex.Column = matches.Groups["col"].Value; + newIndex.Unique = indexSql.Contains("UNIQUE"); + newIndex.Table = tableName; + + indexes.Add(newIndex); + } + + return indexes; + } + + public void CreateTable(string tableName, IEnumerable<SQLiteColumn> values, IEnumerable<SQLiteIndex> indexes) + { + var columns = String.Join(",", values.Select(c => c.ToString())); + + ExecuteNonQuery("CREATE TABLE [{0}] ({1})", tableName, columns); + + foreach (var index in indexes) + { + ExecuteNonQuery("DROP INDEX {0}", index.IndexName); + ExecuteNonQuery(index.CreateSql(tableName)); + } + } + + public void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteColumn> columns) + { + var originalCount = GetRowCount(sourceTable); + + var columnsToTransfer = String.Join(",", columns.Select(c => c.Name)); + + var transferCommand = BuildCommand("INSERT INTO {0} SELECT {1} FROM {2};", destinationTable, columnsToTransfer, sourceTable); + + transferCommand.ExecuteNonQuery(); + + var transferredRows = GetRowCount(destinationTable); + + + if (transferredRows != originalCount) + { + throw new ApplicationException(string.Format("Expected {0} rows to be copied from [{1}] to [{2}]. But only copied {3}", originalCount, sourceTable, destinationTable, transferredRows)); + } + } + + + public void DropTable(string tableName) + { + var dropCommand = BuildCommand("DROP TABLE {0};", tableName); + dropCommand.ExecuteNonQuery(); + } + + + public void RenameTable(string tableName, string newName) + { + var renameCommand = BuildCommand("ALTER TABLE {0} RENAME TO {1};", tableName, newName); + renameCommand.ExecuteNonQuery(); + } + + public Dictionary<int,T> GetDuplicates<T>(string tableName, string columnName) + { + var dupCommand = BuildCommand("select id, {0} from {1}", columnName, tableName); + + var result = new Dictionary<int, T>(); + using (var reader = dupCommand.ExecuteReader()) + { + while (reader.Read()) + { + + } + } + + + return ReadArray<T>().ToList(); + } + + public int GetRowCount(string tableName) + { + var countCommand = BuildCommand("SELECT COUNT(*) FROM {0};", tableName); + return Convert.ToInt32(countCommand.ExecuteScalar()); + } + + + public SQLiteTransaction BeginTransaction() + { + return _connection.BeginTransaction(); + } + + private SQLiteCommand BuildCommand(string format, params string[] args) + { + var command = new SQLiteCommand(string.Format(format, args)); + command.Connection = _connection; + return command; + } + + + + private void ExecuteNonQuery(string command, params string[] args) + { + var sqLiteCommand = new SQLiteCommand(string.Format(command, args)) + { + Connection = _connection + }; + + sqLiteCommand.ExecuteNonQuery(); + } + + + public class SQLiteColumn + { + public string Name { get; set; } + public string Schema { get; set; } + + public override string ToString() + { + return string.Format("[{0}] {1}", Name, Schema); + } + } + + public class SQLiteIndex + { + public string Column { get; set; } + public string Table { get; set; } + public bool Unique { get; set; } + + public override string ToString() + { + return string.Format("[{0}] Unique: {1}", Column, Unique); + } + + public string IndexName + { + get + { + return string.Format("IX_{0}_{1}", Table, Column); + } + } + + public string CreateSql(string tableName) + { + return string.Format(@"CREATE UNIQUE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName); + } + } + } + + +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs index 80b4e25e8..42f8f8e7b 100644 --- a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs +++ b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs @@ -4,28 +4,35 @@ using System.Data.SQLite; using System.Linq; using System.Text.RegularExpressions; using NLog; +using NzbDrone.Common.Exceptions; namespace NzbDrone.Core.Datastore.Migration.Framework { - public interface ISQLiteMigrationHelper + public interface ISqLiteMigrationHelper { - Dictionary<String, SQLiteMigrationHelper.SQLiteColumn> GetColumns(string tableName); - void CreateTable(string tableName, IEnumerable<SQLiteMigrationHelper.SQLiteColumn> values); - void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteMigrationHelper.SQLiteColumn> columns); - int GetRowCount(string tableName); + Dictionary<String, SQLiteColumn> GetColumns(string tableName); + void CreateTable(string tableName, IEnumerable<SQLiteColumn> values, IEnumerable<SQLiteIndex> indexes); + void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteColumn> columns); void DropTable(string tableName); void RenameTable(string tableName, string newName); + IEnumerable<IGrouping<T, KeyValuePair<int, T>>> GetDuplicates<T>(string tableName, string columnName); SQLiteTransaction BeginTransaction(); + List<SQLiteIndex> GetIndexes(string tableName); + int ExecuteScalar(string command, params string[] args); + void ExecuteNonQuery(string command, params string[] args); } - public class SQLiteMigrationHelper : ISQLiteMigrationHelper + public class SqLiteMigrationHelper : ISqLiteMigrationHelper { private readonly SQLiteConnection _connection; private static readonly Regex SchemaRegex = new Regex(@"['\""\[](?<name>\w+)['\""\]]\s(?<schema>[\w-\s]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline); - public SQLiteMigrationHelper(IConnectionStringFactory connectionStringFactory,Logger logger) + private static readonly Regex IndexRegex = new Regex(@"\(""(?<col>.*)""\s(?<direction>ASC|DESC)\)$", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline); + + public SqLiteMigrationHelper(IConnectionStringFactory connectionStringFactory, Logger logger) { try { @@ -34,7 +41,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework } catch (Exception e) { - logger.ErrorException("Couldn't open databse " + connectionStringFactory.MainDbConnectionString, e); + logger.ErrorException("Couldn't open database " + connectionStringFactory.MainDbConnectionString, e); throw; } @@ -47,7 +54,15 @@ namespace NzbDrone.Core.Datastore.Migration.Framework tableName)); command.Connection = _connection; - return (string)command.ExecuteScalar(); + + var sql = (string)command.ExecuteScalar(); + + if (string.IsNullOrWhiteSpace(sql)) + { + throw new TableNotFoundException(tableName); + } + + return sql; } public Dictionary<String, SQLiteColumn> GetColumns(string tableName) @@ -65,14 +80,52 @@ namespace NzbDrone.Core.Datastore.Migration.Framework }); } - public void CreateTable(string tableName, IEnumerable<SQLiteColumn> values) + + private static IEnumerable<T> ReadArray<T>(SQLiteDataReader reader) { - var columns = String.Join(",", values.Select(c => c.ToString())); + while (reader.Read()) + { + yield return (T)Convert.ChangeType(reader[0], typeof(T)); + } + } - var command = new SQLiteCommand(string.Format("CREATE TABLE [{0}] ({1})", tableName, columns)); + public List<SQLiteIndex> GetIndexes(string tableName) + { + var command = new SQLiteCommand(string.Format("SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name ='{0}'", tableName)); command.Connection = _connection; - command.ExecuteNonQuery(); + var reader = command.ExecuteReader(); + var sqls = ReadArray<string>(reader).ToList(); + + + var indexes = new List<SQLiteIndex>(); + + foreach (var indexSql in sqls) + { + var newIndex = new SQLiteIndex(); + var matches = IndexRegex.Match(indexSql); + + newIndex.Column = matches.Groups["col"].Value; + newIndex.Unique = indexSql.Contains("UNIQUE"); + newIndex.Table = tableName; + + indexes.Add(newIndex); + } + + return indexes; + } + + public void CreateTable(string tableName, IEnumerable<SQLiteColumn> values, IEnumerable<SQLiteIndex> indexes) + { + var columns = String.Join(",", values.Select(c => c.ToString())); + + ExecuteNonQuery("CREATE TABLE [{0}] ({1})", tableName, columns); + + foreach (var index in indexes) + { + ExecuteNonQuery("DROP INDEX IF EXISTS {0}", index.IndexName); + ExecuteNonQuery(index.CreateSql(tableName)); + } } public void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteColumn> columns) @@ -108,6 +161,23 @@ namespace NzbDrone.Core.Datastore.Migration.Framework renameCommand.ExecuteNonQuery(); } + public IEnumerable<IGrouping<T, KeyValuePair<int, T>>> GetDuplicates<T>(string tableName, string columnName) + { + var getDuplicates = BuildCommand("select id, {0} from {1}", columnName, tableName); + + var result = new List<KeyValuePair<int, T>>(); + + using (var reader = getDuplicates.ExecuteReader()) + { + while (reader.Read()) + { + result.Add(new KeyValuePair<int, T>(reader.GetInt32(0), (T)Convert.ChangeType(reader[1], typeof(T)))); + } + } + + return result.GroupBy(c => c.Value).Where(g => g.Count() > 1); + } + public int GetRowCount(string tableName) { var countCommand = BuildCommand("SELECT COUNT(*) FROM {0};", tableName); @@ -128,17 +198,35 @@ namespace NzbDrone.Core.Datastore.Migration.Framework } - public class SQLiteColumn + public void ExecuteNonQuery(string command, params string[] args) { - public string Name { get; set; } - public string Schema { get; set; } + var sqLiteCommand = new SQLiteCommand(string.Format(command, args)) + { + Connection = _connection + }; + + sqLiteCommand.ExecuteNonQuery(); + } + + public int ExecuteScalar(string command, params string[] args) + { + var sqLiteCommand = new SQLiteCommand(string.Format(command, args)) + { + Connection = _connection + }; - public override string ToString() + return (int)sqLiteCommand.ExecuteScalar(); + } + + private class TableNotFoundException : NzbDroneException + { + public TableNotFoundException(string tableName) + : base("Table [{0}] not found", tableName) { - return string.Format("[{0}] {1}", Name, Schema); + } } - } + } } \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs b/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs index ce2372d69..c22670588 100644 --- a/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs +++ b/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs @@ -6,13 +6,14 @@ namespace NzbDrone.Core.Datastore.Migration.Framework public interface ISQLiteAlter { void DropColumns(string tableName, IEnumerable<string> columns); + void AddIndexes(string tableName, params SQLiteIndex[] indexes); } public class SQLiteAlter : ISQLiteAlter { - private readonly ISQLiteMigrationHelper _sqLiteMigrationHelper; + private readonly ISqLiteMigrationHelper _sqLiteMigrationHelper; - public SQLiteAlter(ISQLiteMigrationHelper sqLiteMigrationHelper) + public SQLiteAlter(ISqLiteMigrationHelper sqLiteMigrationHelper) { _sqLiteMigrationHelper = sqLiteMigrationHelper; } @@ -22,21 +23,44 @@ namespace NzbDrone.Core.Datastore.Migration.Framework using (var transaction = _sqLiteMigrationHelper.BeginTransaction()) { var originalColumns = _sqLiteMigrationHelper.GetColumns(tableName); + var originalIndexes = _sqLiteMigrationHelper.GetIndexes(tableName); var newColumns = originalColumns.Where(c => !columns.Contains(c.Key)).Select(c => c.Value).ToList(); + var newIndexes = originalIndexes.Where(c => !columns.Contains(c.Column)); - var tempTableName = tableName + "_temp"; + CreateTable(tableName, newColumns, newIndexes); - _sqLiteMigrationHelper.CreateTable(tempTableName, newColumns); + transaction.Commit(); + } + } + + public void AddIndexes(string tableName, params SQLiteIndex[] indexes) + { + using (var transaction = _sqLiteMigrationHelper.BeginTransaction()) + { + var columns = _sqLiteMigrationHelper.GetColumns(tableName).Select(c => c.Value).ToList(); + var originalIndexes = _sqLiteMigrationHelper.GetIndexes(tableName); - _sqLiteMigrationHelper.CopyData(tableName, tempTableName, newColumns); + var newIndexes = originalIndexes.Union(indexes); - _sqLiteMigrationHelper.DropTable(tableName); - _sqLiteMigrationHelper.RenameTable(tempTableName, tableName); + CreateTable(tableName, columns, newIndexes); transaction.Commit(); } } + + private void CreateTable(string tableName, List<SQLiteColumn> newColumns, IEnumerable<SQLiteIndex> newIndexes) + { + var tempTableName = tableName + "_temp"; + + _sqLiteMigrationHelper.CreateTable(tempTableName, newColumns, newIndexes); + + _sqLiteMigrationHelper.CopyData(tableName, tempTableName, newColumns); + + _sqLiteMigrationHelper.DropTable(tableName); + + _sqLiteMigrationHelper.RenameTable(tempTableName, tableName); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/TableMapping.cs b/NzbDrone.Core/Datastore/TableMapping.cs index 80242f783..533b3ab70 100644 --- a/NzbDrone.Core/Datastore/TableMapping.cs +++ b/NzbDrone.Core/Datastore/TableMapping.cs @@ -45,8 +45,6 @@ namespace NzbDrone.Core.Datastore .Relationship() .HasOne(s => s.QualityProfile, s => s.QualityProfileId); - Mapper.Entity<Season>().RegisterModel("Seasons"); - Mapper.Entity<Episode>().RegisterModel("Episodes") .Ignore(e => e.SeriesTitle) .Ignore(e => e.Series) diff --git a/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index d94036901..f2b1404cf 100644 --- a/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -2,8 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Core.DecisionEngine.Specifications.Search; +using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Common.Serializer; @@ -12,8 +13,8 @@ namespace NzbDrone.Core.DecisionEngine { public interface IMakeDownloadDecision { - List<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports); - List<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchCriteriaBase searchCriteriaBase); + List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports); + List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase); } public class DownloadDecisionMaker : IMakeDownloadDecision @@ -29,21 +30,34 @@ namespace NzbDrone.Core.DecisionEngine _logger = logger; } - public List<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports) + public List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports) { return GetDecisions(reports).ToList(); } - public List<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchCriteriaBase searchCriteriaBase) + public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase) { return GetDecisions(reports, searchCriteriaBase).ToList(); } - private IEnumerable<DownloadDecision> GetDecisions(IEnumerable<ReportInfo> reports, SearchCriteriaBase searchCriteria = null) + private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null) { + if (reports.Any()) + { + _logger.ProgressInfo("Processing {0} reports", reports.Count); + } + + else + { + _logger.ProgressInfo("No reports found"); + } + + var reportNumber = 1; + foreach (var report in reports) { DownloadDecision decision = null; + _logger.ProgressTrace("Processing report {0}/{1}", reportNumber, reports.Count); try { @@ -51,8 +65,8 @@ namespace NzbDrone.Core.DecisionEngine if (parsedEpisodeInfo != null && !string.IsNullOrWhiteSpace(parsedEpisodeInfo.SeriesTitle)) { - var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvRageId); - remoteEpisode.Report = report; + var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvRageId, searchCriteria); + remoteEpisode.Release = report; if (remoteEpisode.Series != null) { @@ -69,6 +83,8 @@ namespace NzbDrone.Core.DecisionEngine _logger.ErrorException("Couldn't process report.", e); } + reportNumber++; + if (decision != null) { yield return decision; @@ -102,13 +118,13 @@ namespace NzbDrone.Core.DecisionEngine } catch (Exception e) { - e.Data.Add("report", remoteEpisode.Report.ToJson()); + e.Data.Add("report", remoteEpisode.Release.ToJson()); e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson()); - _logger.ErrorException("Couldn't evaluate decision on " + remoteEpisode.Report.Title, e); + _logger.ErrorException("Couldn't evaluate decision on " + remoteEpisode.Release.Title, e); return string.Format("{0}: {1}", spec.GetType().Name, e.Message); } return null; } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs b/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs index 8fbd65d1a..8bec31bc1 100644 --- a/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs +++ b/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs @@ -1,5 +1,4 @@ using NLog; -using NzbDrone.Core.Configuration; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; @@ -7,21 +6,21 @@ namespace NzbDrone.Core.DecisionEngine { public interface IQualityUpgradableSpecification { - bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null); + bool IsUpgradable(QualityModel currentQuality, QualityModel newQuality = null); + bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null); + bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality); } public class QualityUpgradableSpecification : IQualityUpgradableSpecification { - private readonly IConfigService _configService; private readonly Logger _logger; - public QualityUpgradableSpecification(IConfigService configService, Logger logger) + public QualityUpgradableSpecification(Logger logger) { - _configService = configService; _logger = logger; } - public bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null) + public bool IsUpgradable(QualityModel currentQuality, QualityModel newQuality = null) { if (newQuality != null) { @@ -31,20 +30,40 @@ namespace NzbDrone.Core.DecisionEngine return false; } - if (currentQuality.Quality == newQuality.Quality && newQuality.Proper && _configService.AutoDownloadPropers) + if (IsProperUpgrade(currentQuality, newQuality)) { - _logger.Trace("Upgrading existing item to proper."); return true; } } + return true; + } + + public bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null) + { if (currentQuality.Quality >= profile.Cutoff) { + if (newQuality != null && IsProperUpgrade(currentQuality, newQuality)) + { + return true; + } + _logger.Trace("Existing item meets cut-off. skipping."); return false; } return true; } + + public bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality) + { + if (currentQuality.Quality == newQuality.Quality && newQuality > currentQuality) + { + _logger.Trace("New quality is a proper for existing quality"); + return true; + } + + return false; + } } } diff --git a/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs index ada1817e5..1d6f5af7f 100644 --- a/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs +++ b/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs @@ -57,17 +57,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications //Multiply maxSize by Series.Runtime maxSize = maxSize * subject.Series.Runtime * subject.Episodes.Count; - //Check if there was only one episode parsed - //and it is the first or last episode of the season - if (subject.Episodes.Count == 1 && _episodeService.IsFirstOrLastEpisodeOfSeason(subject.Episodes.Single().Id)) + //Check if there was only one episode parsed and it is the first + if (subject.Episodes.Count == 1 && subject.Episodes.First().EpisodeNumber == 1) { maxSize = maxSize * 2; } //If the parsed size is greater than maxSize we don't want it - if (subject.Report.Size > maxSize) + if (subject.Release.Size > maxSize) { - _logger.Trace("Item: {0}, Size: {1} is greater than maximum allowed size ({2}), rejecting.", subject, subject.Report.Size, maxSize); + _logger.Trace("Item: {0}, Size: {1} is greater than maximum allowed size ({2}), rejecting.", subject, subject.Release.Size, maxSize); return false; } diff --git a/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs new file mode 100644 index 000000000..f43df2836 --- /dev/null +++ b/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -0,0 +1,43 @@ +using System.Linq; +using NLog; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class CutoffSpecification : IDecisionEngineSpecification + { + private readonly QualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly Logger _logger; + + public CutoffSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger) + { + _qualityUpgradableSpecification = qualityUpgradableSpecification; + _logger = logger; + } + + public string RejectionReason + { + get + { + return "Cutoff has already been met"; + } + } + + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) + { + _logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality); + + + if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality)) + { + return false; + } + } + + return true; + } + } +} diff --git a/NzbDrone.Core/DecisionEngine/DownloadDecision.cs b/NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs similarity index 93% rename from NzbDrone.Core/DecisionEngine/DownloadDecision.cs rename to NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs index b3037395c..22616a735 100644 --- a/NzbDrone.Core/DecisionEngine/DownloadDecision.cs +++ b/NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Parser.Model; -namespace NzbDrone.Core.DecisionEngine +namespace NzbDrone.Core.DecisionEngine.Specifications { public class DownloadDecision { diff --git a/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs index 698c1b32b..628b8183e 100644 --- a/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs +++ b/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { get { - return "Contrains restricted term."; + return "Contains restricted term."; } } @@ -41,7 +41,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications foreach (var restriction in restrictions) { - if (subject.Report.Title.ToLowerInvariant().Contains(restriction.ToLowerInvariant())) + if (subject.Release.Title.ToLowerInvariant().Contains(restriction.ToLowerInvariant())) { _logger.Trace("{0} is restricted: {1}", subject, restriction); return false; diff --git a/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs new file mode 100644 index 000000000..afcb7149a --- /dev/null +++ b/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs @@ -0,0 +1,20 @@ +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class NotSampleSpecification : IDecisionEngineSpecification + { + public string RejectionReason { get { return "Sample"; } } + + public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes()) + { + return false; + } + + return true; + } + } +} diff --git a/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs index 01b692adf..a2f4dee15 100644 --- a/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs +++ b/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs @@ -27,7 +27,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { - var age = subject.Report.Age; + var age = subject.Release.Age; _logger.Trace("Checking if report meets retention requirements. {0}", age); if (_configService.Retention > 0 && age > _configService.Retention) diff --git a/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs new file mode 100644 index 000000000..f0a133409 --- /dev/null +++ b/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using NLog; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync +{ + public class ProperSpecification : IDecisionEngineSpecification + { + private readonly QualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly IConfigService _configService; + private readonly Logger _logger; + + public ProperSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, IConfigService configService, Logger logger) + { + _qualityUpgradableSpecification = qualityUpgradableSpecification; + _configService = configService; + _logger = logger; + } + + public string RejectionReason + { + get + { + return "Proper for old episode"; + } + } + + public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + if (searchCriteria != null) + { + return true; + } + + foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) + { + if (_qualityUpgradableSpecification.IsProperUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality)) + { + if (file.DateAdded < DateTime.Today.AddDays(-7)) + { + _logger.Trace("Proper for old file, skipping: {0}", subject); + return false; + } + + if (!_configService.AutoDownloadPropers) + { + _logger.Trace("Auto downloading of propers is disabled"); + return false; + } + } + } + + return true; + } + } +} diff --git a/NzbDrone.Core/DecisionEngine/Specifications/RssSync/UpgradeHistorySpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/RssSync/UpgradeHistorySpecification.cs index 21611c161..e844cb91d 100644 --- a/NzbDrone.Core/DecisionEngine/Specifications/RssSync/UpgradeHistorySpecification.cs +++ b/NzbDrone.Core/DecisionEngine/Specifications/RssSync/UpgradeHistorySpecification.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync { get { - return "Higher quality report exists in history"; + return "Existing file in history is of equal or higher quality"; } } @@ -40,7 +40,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync if (bestQualityInHistory != null) { _logger.Trace("Comparing history quality with report. History is {0}", bestQualityInHistory); - if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality)) + if (!_qualityUpgradableSpecification.IsUpgradable(bestQualityInHistory, subject.ParsedEpisodeInfo.Quality)) return false; } } diff --git a/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs index df4bb37b0..daaf3146c 100644 --- a/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs +++ b/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search if (dailySearchSpec == null) return true; - var episode = _episodeService.GetEpisode(dailySearchSpec.SeriesId, dailySearchSpec.Airtime); + var episode = _episodeService.GetEpisode(dailySearchSpec.Series.Id, dailySearchSpec.Airtime); if (!remoteEpisode.ParsedEpisodeInfo.AirDate.HasValue || remoteEpisode.ParsedEpisodeInfo.AirDate.Value.ToString(Episode.AIR_DATE_FORMAT) != episode.AirDate) { diff --git a/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index 8c0f07450..004f20134 100644 --- a/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; @@ -21,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { get { - return "Higher quality exists on disk"; + return "Existing file on disk is of equal or higher quality"; } } @@ -31,19 +30,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { _logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality); - if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality)) + if (!_qualityUpgradableSpecification.IsUpgradable(file.Quality, subject.ParsedEpisodeInfo.Quality)) { return false; } - - if (searchCriteria == null && - subject.ParsedEpisodeInfo.Quality.Quality == file.Quality.Quality && - subject.ParsedEpisodeInfo.Quality.Proper && - file.DateAdded < DateTime.Today.AddDays(-7)) - { - _logger.Trace("Proper for old file, skipping: {0}", subject); - return false; - } } return true; diff --git a/NzbDrone.Core/Download/Clients/BlackholeProvider.cs b/NzbDrone.Core/Download/Clients/BlackholeProvider.cs index 520e06943..1598ee647 100644 --- a/NzbDrone.Core/Download/Clients/BlackholeProvider.cs +++ b/NzbDrone.Core/Download/Clients/BlackholeProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using NLog; using NzbDrone.Common; @@ -13,29 +12,20 @@ namespace NzbDrone.Core.Download.Clients { private readonly IConfigService _configService; private readonly IHttpProvider _httpProvider; - private readonly IDiskProvider _diskProvider; private readonly Logger _logger; - public BlackholeProvider(IConfigService configService, IHttpProvider httpProvider, - IDiskProvider diskProvider, Logger logger) + public BlackholeProvider(IConfigService configService, IHttpProvider httpProvider, Logger logger) { _configService = configService; _httpProvider = httpProvider; - _diskProvider = diskProvider; _logger = logger; } - - public bool IsInQueue(RemoteEpisode newEpisode) - { - throw new NotImplementedException(); - } - public void DownloadNzb(RemoteEpisode remoteEpisode) { - var url = remoteEpisode.Report.NzbUrl; - var title = remoteEpisode.Report.Title; + var url = remoteEpisode.Release.DownloadUrl; + var title = remoteEpisode.Release.Title; title = FileNameBuilder.CleanFilename(title); diff --git a/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs b/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs index 6fd7a9061..144805113 100644 --- a/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs +++ b/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Net; using Newtonsoft.Json; using NLog; using NzbDrone.Common; @@ -24,8 +23,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget public void DownloadNzb(RemoteEpisode remoteEpisode) { - var url = remoteEpisode.Report.NzbUrl; - var title = remoteEpisode.Report.Title + ".nzb"; + var url = remoteEpisode.Release.DownloadUrl; + var title = remoteEpisode.Release.Title + ".nzb"; string cat = _configService.NzbgetTvCategory; int priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.NzbgetRecentTvPriority : (int)_configService.NzbgetOlderTvPriority; diff --git a/NzbDrone.Core/Download/Clients/PneumaticClient.cs b/NzbDrone.Core/Download/Clients/PneumaticClient.cs index a7e59b6a4..537683243 100644 --- a/NzbDrone.Core/Download/Clients/PneumaticClient.cs +++ b/NzbDrone.Core/Download/Clients/PneumaticClient.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Configuration; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; @@ -15,7 +16,7 @@ namespace NzbDrone.Core.Download.Clients private readonly IHttpProvider _httpProvider; private readonly IDiskProvider _diskProvider; - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger logger = NzbDroneLogger.GetLogger(); public PneumaticClient(IConfigService configService, IHttpProvider httpProvider, IDiskProvider diskProvider) @@ -27,8 +28,8 @@ namespace NzbDrone.Core.Download.Clients public void DownloadNzb(RemoteEpisode remoteEpisode) { - var url = remoteEpisode.Report.NzbUrl; - var title = remoteEpisode.Report.Title; + var url = remoteEpisode.Release.DownloadUrl; + var title = remoteEpisode.Release.Title; if (remoteEpisode.ParsedEpisodeInfo.FullSeason) { diff --git a/NzbDrone.Core/Download/Clients/Sabnzbd/SabAutoConfigureService.cs b/NzbDrone.Core/Download/Clients/Sabnzbd/SabAutoConfigureService.cs index 6ff5a666a..224cae5c5 100644 --- a/NzbDrone.Core/Download/Clients/Sabnzbd/SabAutoConfigureService.cs +++ b/NzbDrone.Core/Download/Clients/Sabnzbd/SabAutoConfigureService.cs @@ -6,12 +6,13 @@ using System.Net; using System.Net.NetworkInformation; using System.Text.RegularExpressions; using NLog; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Core.Download.Clients.Sabnzbd { public class SabAutoConfigureService { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public SabModel AutoConfigureSab() { diff --git a/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs b/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs index 281096945..40a241a1c 100644 --- a/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs +++ b/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; -using System.Net; using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Cache; using NzbDrone.Core.Configuration; using NzbDrone.Core.Parser.Model; using RestSharp; @@ -26,8 +26,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd string cat = _configService.SabTvCategory; int priority = (int)_configService.SabRecentTvPriority; - string name = remoteEpisode.Report.NzbUrl.Replace("&", "%26"); - string nzbName = HttpUtility.UrlEncode(remoteEpisode.Report.Title); + string name = remoteEpisode.Release.DownloadUrl.Replace("&", "%26"); + string nzbName = HttpUtility.UrlEncode(remoteEpisode.Release.Title); string action = string.Format("mode=addurl&name={0}&priority={1}&pp=3&cat={2}&nzbname={3}&output=json", name, priority, cat, nzbName); @@ -53,19 +53,21 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { private readonly IConfigService _configService; private readonly IHttpProvider _httpProvider; + private readonly ICached<IEnumerable<QueueItem>> _queueCache; private readonly Logger _logger; - public SabnzbdClient(IConfigService configService, IHttpProvider httpProvider, Logger logger) + public SabnzbdClient(IConfigService configService, IHttpProvider httpProvider, ICacheManger cacheManger, Logger logger) { _configService = configService; _httpProvider = httpProvider; + _queueCache = cacheManger.GetCache<IEnumerable<QueueItem>>(GetType(), "queue"); _logger = logger; } public void DownloadNzb(RemoteEpisode remoteEpisode) { - var url = remoteEpisode.Report.NzbUrl; - var title = remoteEpisode.Report.Title; + var url = remoteEpisode.Release.DownloadUrl; + var title = remoteEpisode.Release.Title; string cat = _configService.SabTvCategory; int priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.SabRecentTvPriority : (int)_configService.SabOlderTvPriority; @@ -97,24 +99,31 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd public IEnumerable<QueueItem> GetQueue() { - string action = String.Format("mode=queue&output=json&start={0}&limit={1}", 0, 0); - string request = GetSabRequest(action); - string response = _httpProvider.DownloadString(request); + return _queueCache.Get("queue", () => + { + string action = String.Format("mode=queue&output=json&start={0}&limit={1}", 0, 0); + string request = GetSabRequest(action); + string response = _httpProvider.DownloadString(request); - CheckForError(response); + CheckForError(response); - var sabQueue = JsonConvert.DeserializeObject<SabQueue>(JObject.Parse(response).SelectToken("queue").ToString()).Items; + var sabQueue = JsonConvert.DeserializeObject<SabQueue>(JObject.Parse(response).SelectToken("queue").ToString()).Items; - foreach (var sabQueueItem in sabQueue) - { - var queueItem = new QueueItem(); - queueItem.Id = sabQueueItem.Id; - queueItem.Title = sabQueueItem.Title; - queueItem.Size = sabQueueItem.Size; - queueItem.SizeLeft = sabQueueItem.Size; + var queueItems = new List<QueueItem>(); - yield return queueItem; - } + foreach (var sabQueueItem in sabQueue) + { + var queueItem = new QueueItem(); + queueItem.Id = sabQueueItem.Id; + queueItem.Title = sabQueueItem.Title; + queueItem.Size = sabQueueItem.Size; + queueItem.SizeLeft = sabQueueItem.Size; + + queueItems.Add( queueItem); + } + + return queueItems; + }, TimeSpan.FromSeconds(10)); } public virtual List<SabHistoryItem> GetHistory(int start = 0, int limit = 0) diff --git a/NzbDrone.Core/Download/DownloadApprovedReports.cs b/NzbDrone.Core/Download/DownloadApprovedReports.cs index 0da8ff3d9..46f54d1aa 100644 --- a/NzbDrone.Core/Download/DownloadApprovedReports.cs +++ b/NzbDrone.Core/Download/DownloadApprovedReports.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Core.DecisionEngine; -using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.DecisionEngine.Specifications; namespace NzbDrone.Core.Download { @@ -60,8 +59,8 @@ namespace NzbDrone.Core.Download return decisions.Where(c => c.Approved && c.RemoteEpisode.Episodes.Any()) .OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality) .ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault()) - .ThenBy(c => c.RemoteEpisode.Report.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count) - .ThenBy(c => c.RemoteEpisode.Report.Age) + .ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count) + .ThenBy(c => c.RemoteEpisode.Release.Age) .ToList(); } } diff --git a/NzbDrone.Core/Download/DownloadService.cs b/NzbDrone.Core/Download/DownloadService.cs index 583fdfa1f..a1c4f3bf1 100644 --- a/NzbDrone.Core/Download/DownloadService.cs +++ b/NzbDrone.Core/Download/DownloadService.cs @@ -1,5 +1,7 @@ using NLog; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Download @@ -12,21 +14,21 @@ namespace NzbDrone.Core.Download public class DownloadService : IDownloadService { private readonly IProvideDownloadClient _downloadClientProvider; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; public DownloadService(IProvideDownloadClient downloadClientProvider, - IMessageAggregator messageAggregator, Logger logger) + IEventAggregator eventAggregator, Logger logger) { _downloadClientProvider = downloadClientProvider; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _logger = logger; } public void DownloadReport(RemoteEpisode remoteEpisode) { - var downloadTitle = remoteEpisode.Report.Title; + var downloadTitle = remoteEpisode.Release.Title; var downloadClient = _downloadClientProvider.GetDownloadClient(); if (!downloadClient.IsConfigured) @@ -37,8 +39,8 @@ namespace NzbDrone.Core.Download downloadClient.DownloadNzb(remoteEpisode); - _logger.Info("Report sent to download client. {0}", downloadTitle); - _messageAggregator.PublishEvent(new EpisodeGrabbedEvent(remoteEpisode)); + _logger.ProgressInfo("Report sent to download client. {0}", downloadTitle); + _eventAggregator.PublishEvent(new EpisodeGrabbedEvent(remoteEpisode)); } } } \ No newline at end of file diff --git a/NzbDrone.Core/History/HistoryRepository.cs b/NzbDrone.Core/History/HistoryRepository.cs index ca436d153..096397248 100644 --- a/NzbDrone.Core/History/HistoryRepository.cs +++ b/NzbDrone.Core/History/HistoryRepository.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Linq; using Marr.Data.QGen; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; namespace NzbDrone.Core.History @@ -16,8 +17,8 @@ namespace NzbDrone.Core.History public class HistoryRepository : BasicRepository<History>, IHistoryRepository { - public HistoryRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public HistoryRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/History/HistoryService.cs b/NzbDrone.Core/History/HistoryService.cs index c0752eb2a..6053541d0 100644 --- a/NzbDrone.Core/History/HistoryService.cs +++ b/NzbDrone.Core/History/HistoryService.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; namespace NzbDrone.Core.History @@ -65,15 +65,15 @@ namespace NzbDrone.Core.History EventType = HistoryEventType.Grabbed, Date = DateTime.UtcNow, Quality = message.Episode.ParsedEpisodeInfo.Quality, - SourceTitle = message.Episode.Report.Title, + SourceTitle = message.Episode.Release.Title, SeriesId = episode.SeriesId, EpisodeId = episode.Id, }; - history.Data.Add("Indexer", message.Episode.Report.Indexer); - history.Data.Add("NzbInfoUrl", message.Episode.Report.NzbInfoUrl); - history.Data.Add("ReleaseGroup", message.Episode.Report.ReleaseGroup); - history.Data.Add("Age", message.Episode.Report.Age.ToString()); + history.Data.Add("Indexer", message.Episode.Release.Indexer); + history.Data.Add("NzbInfoUrl", message.Episode.Release.InfoUrl); + history.Data.Add("ReleaseGroup", message.Episode.Release.ReleaseGroup); + history.Data.Add("Age", message.Episode.Release.Age.ToString()); _historyRepository.Insert(history); } diff --git a/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs index 11d3d4d79..20a6e8a39 100644 --- a/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs +++ b/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs @@ -1,17 +1,19 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using NzbDrone.Common.EnsureThat; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.IndexerSearch.Definitions { public abstract class SearchCriteriaBase { - private static readonly Regex NoneWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex NonWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public int SeriesId { get; set; } - public int SeriesTvRageId { get; set; } + public Series Series { get; set; } public string SceneTitle { get; set; } + public List<Episode> Episodes { get; set; } public string QueryTitle { @@ -32,7 +34,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions .Replace("`", "") .Replace("'", ""); - cleanTitle = NoneWord.Replace(cleanTitle, "+"); + cleanTitle = NonWord.Replace(cleanTitle, "+"); //remove any repeating +s cleanTitle = Regex.Replace(cleanTitle, @"\+{2,}", "+"); diff --git a/NzbDrone.Core/IndexerSearch/Definitions/SingleEpisodeSearchCriteria.cs b/NzbDrone.Core/IndexerSearch/Definitions/SingleEpisodeSearchCriteria.cs index 371f66bc6..56d110079 100644 --- a/NzbDrone.Core/IndexerSearch/Definitions/SingleEpisodeSearchCriteria.cs +++ b/NzbDrone.Core/IndexerSearch/Definitions/SingleEpisodeSearchCriteria.cs @@ -7,7 +7,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions public override string ToString() { - return string.Format("[{0} : S{1:00}E{2:00} ]", SceneTitle, SeasonNumber, EpisodeNumber); + return string.Format("[{0} : S{1:00}E{2:00}]", SceneTitle, SeasonNumber, EpisodeNumber); } } } \ No newline at end of file diff --git a/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs b/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs index b0dce2fd1..b69659b77 100644 --- a/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.IndexerSearch { - public class EpisodeSearchCommand : ICommand + public class EpisodeSearchCommand : Command { public int EpisodeId { get; set; } + + public override bool SendUpdatesToClient + { + get + { + return true; + } + } } } \ No newline at end of file diff --git a/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs b/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs index e62a99836..4f2860eb4 100644 --- a/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs +++ b/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs @@ -1,5 +1,8 @@ -using NzbDrone.Common.Messaging; +using NLog; using NzbDrone.Core.Download; +using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.IndexerSearch { @@ -7,17 +10,23 @@ namespace NzbDrone.Core.IndexerSearch { private readonly ISearchForNzb _nzbSearchService; private readonly IDownloadApprovedReports _downloadApprovedReports; + private readonly Logger _logger; - public EpisodeSearchService(ISearchForNzb nzbSearchService, IDownloadApprovedReports downloadApprovedReports) + public EpisodeSearchService(ISearchForNzb nzbSearchService, + IDownloadApprovedReports downloadApprovedReports, + Logger logger) { _nzbSearchService = nzbSearchService; _downloadApprovedReports = downloadApprovedReports; + _logger = logger; } public void Execute(EpisodeSearchCommand message) { var decisions = _nzbSearchService.EpisodeSearch(message.EpisodeId); - _downloadApprovedReports.DownloadApproved(decisions); + var downloaded = _downloadApprovedReports.DownloadApproved(decisions); + + _logger.ProgressInfo("Episode search completed. {0} reports downloaded.", downloaded.Count); } } } diff --git a/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index 7386341d2..5770687b8 100644 --- a/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -5,8 +5,10 @@ using System.Threading.Tasks; using NLog; using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; using System.Linq; @@ -59,7 +61,7 @@ namespace NzbDrone.Core.IndexerSearch throw new InvalidOperationException("Daily episode is missing AirDate. Try to refresh series info."); } - return SearchDaily(series, DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture)); + return SearchDaily(series, episode); } return SearchSingle(series, episode); @@ -67,7 +69,7 @@ namespace NzbDrone.Core.IndexerSearch private List<DownloadDecision> SearchSingle(Series series, Episode episode) { - var searchSpec = Get<SingleEpisodeSearchCriteria>(series, episode.SeasonNumber); + var searchSpec = Get<SingleEpisodeSearchCriteria>(series, new List<Episode>{episode}); if (series.UseSceneNumbering) { @@ -92,9 +94,10 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); } - private List<DownloadDecision> SearchDaily(Series series, DateTime airDate) + private List<DownloadDecision> SearchDaily(Series series, Episode episode) { - var searchSpec = Get<DailyEpisodeSearchCriteria>(series); + var airDate = DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture); + var searchSpec = Get<DailyEpisodeSearchCriteria>(series, new List<Episode>{ episode }); searchSpec.Airtime = airDate; return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); @@ -103,20 +106,21 @@ namespace NzbDrone.Core.IndexerSearch public List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber) { var series = _seriesService.GetSeries(seriesId); + var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber); - var searchSpec = Get<SeasonSearchCriteria>(series, seasonNumber); + var searchSpec = Get<SeasonSearchCriteria>(series, episodes); searchSpec.SeasonNumber = seasonNumber; return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); } - private TSpec Get<TSpec>(Series series, int seasonNumber = -1) where TSpec : SearchCriteriaBase, new() + private TSpec Get<TSpec>(Series series, List<Episode> episodes) where TSpec : SearchCriteriaBase, new() { var spec = new TSpec(); - spec.SeriesId = series.Id; - spec.SeriesTvRageId = series.TvRageId; + spec.Series = series; spec.SceneTitle = _sceneMapping.GetSceneName(series.TvdbId); + spec.Episodes = episodes; if (string.IsNullOrWhiteSpace(spec.SceneTitle)) { @@ -126,11 +130,12 @@ namespace NzbDrone.Core.IndexerSearch return spec; } - private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReportInfo>> searchAction, SearchCriteriaBase criteriaBase) + private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase) { var indexers = _indexerService.GetAvailableIndexers().ToList(); - var reports = new List<ReportInfo>(); + var reports = new List<ReleaseInfo>(); + _logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase); var taskList = new List<Task>(); var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None); @@ -159,9 +164,9 @@ namespace NzbDrone.Core.IndexerSearch Task.WaitAll(taskList.ToArray()); - _logger.Debug("Total of {0} reports were found for {1} in {2} indexers", reports.Count, criteriaBase, indexers.Count); + _logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count); return _makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList(); } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs b/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs index cf9a8ba3e..56be0e0d9 100644 --- a/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs @@ -1,10 +1,18 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.IndexerSearch { - public class SeasonSearchCommand : ICommand + public class SeasonSearchCommand : Command { public int SeriesId { get; set; } public int SeasonNumber { get; set; } + + public override bool SendUpdatesToClient + { + get + { + return true; + } + } } } \ No newline at end of file diff --git a/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs b/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs index 7572b5b6d..0e4b67eab 100644 --- a/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs +++ b/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs @@ -1,5 +1,8 @@ -using NzbDrone.Common.Messaging; +using NLog; using NzbDrone.Core.Download; +using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.IndexerSearch { @@ -7,17 +10,23 @@ namespace NzbDrone.Core.IndexerSearch { private readonly ISearchForNzb _nzbSearchService; private readonly IDownloadApprovedReports _downloadApprovedReports; + private readonly Logger _logger; - public SeasonSearchService(ISearchForNzb nzbSearchService, IDownloadApprovedReports downloadApprovedReports) + public SeasonSearchService(ISearchForNzb nzbSearchService, + IDownloadApprovedReports downloadApprovedReports, + Logger logger) { _nzbSearchService = nzbSearchService; _downloadApprovedReports = downloadApprovedReports; + _logger = logger; } public void Execute(SeasonSearchCommand message) { var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber); - _downloadApprovedReports.DownloadApproved(decisions); + var downloaded = _downloadApprovedReports.DownloadApproved(decisions); + + _logger.ProgressInfo("Season search completed. {0} reports downloaded.", downloaded.Count); } } } diff --git a/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs b/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs index 4e309fde8..36b75f316 100644 --- a/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.IndexerSearch { - public class SeriesSearchCommand : ICommand + public class SeriesSearchCommand : Command { public int SeriesId { get; set; } + + public override bool SendUpdatesToClient + { + get + { + return true; + } + } } } \ No newline at end of file diff --git a/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs b/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs index 89dd7c6c2..c87275648 100644 --- a/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs +++ b/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs @@ -1,37 +1,44 @@ using System.Linq; -using NzbDrone.Common.Messaging; +using NLog; using NzbDrone.Core.Download; +using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Tv; namespace NzbDrone.Core.IndexerSearch { public class SeriesSearchService : IExecute<SeriesSearchCommand> { - private readonly ISeasonService _seasonService; + private readonly ISeriesService _seriesService; private readonly ISearchForNzb _nzbSearchService; private readonly IDownloadApprovedReports _downloadApprovedReports; + private readonly Logger _logger; - public SeriesSearchService(ISeasonService seasonService, + public SeriesSearchService(ISeriesService seriesService, ISearchForNzb nzbSearchService, - IDownloadApprovedReports downloadApprovedReports) + IDownloadApprovedReports downloadApprovedReports, + Logger logger) { - _seasonService = seasonService; + _seriesService = seriesService; _nzbSearchService = nzbSearchService; _downloadApprovedReports = downloadApprovedReports; + _logger = logger; } public void Execute(SeriesSearchCommand message) { - var seasons = _seasonService.GetSeasonsBySeries(message.SeriesId) - .Where(s => s.SeasonNumber > 0) - .OrderBy(s => s.SeasonNumber) - .ToList(); + var series = _seriesService.GetSeries(message.SeriesId); - foreach (var season in seasons) + var downloadedCount = 0; + + foreach (var season in series.Seasons) { var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, season.SeasonNumber); - _downloadApprovedReports.DownloadApproved(decisions); + downloadedCount += _downloadApprovedReports.DownloadApproved(decisions).Count; } + + _logger.ProgressInfo("Series search completed. {0} reports downloaded.", downloadedCount); } } } diff --git a/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs b/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs new file mode 100644 index 000000000..d3c8ab15a --- /dev/null +++ b/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs @@ -0,0 +1,48 @@ +using System; +using System.Xml.Linq; +using NLog; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers +{ + public class BasicTorrentRssParser : RssParserBase + { + protected override ReleaseInfo CreateNewReleaseInfo() + { + return new TorrentInfo(); + } + + protected override ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult) + { + var torrentInfo = (TorrentInfo)currentResult; + + torrentInfo.MagnetUrl = MagnetUrl(item); + torrentInfo.InfoHash = InfoHash(item); + + return torrentInfo; + } + + protected override long GetSize(XElement item) + { + var elementLength = GetTorrentElement(item).Element("contentLength"); + return Convert.ToInt64(elementLength.Value); + } + + protected virtual string MagnetUrl(XElement item) + { + var elementLength = GetTorrentElement(item).Element("magnetURI"); + return elementLength.Value; + } + + protected virtual string InfoHash(XElement item) + { + var elementLength = GetTorrentElement(item).Element("infoHash"); + return elementLength.Value; + } + + private static XElement GetTorrentElement(XElement item) + { + return item.Element("torrent"); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/DownloadProtocols.cs b/NzbDrone.Core/Indexers/DownloadProtocols.cs new file mode 100644 index 000000000..4fff5e07d --- /dev/null +++ b/NzbDrone.Core/Indexers/DownloadProtocols.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.Indexers +{ + public enum DownloadProtocols + { + Nzb = 0, + Torrent =1 + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/Eztv/Eztv.cs b/NzbDrone.Core/Indexers/Eztv/Eztv.cs new file mode 100644 index 000000000..541f3a078 --- /dev/null +++ b/NzbDrone.Core/Indexers/Eztv/Eztv.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace NzbDrone.Core.Indexers.Eztv +{ + public class Eztv : IndexerBase + { + public override string Name + { + get { return "Eztv"; } + } + + public override IndexerKind Kind + { + get + { + return IndexerKind.Torrent; + } + } + + public override bool EnableByDefault + { + get { return false; } + } + + public override IParseFeed Parser + { + get + { + return new BasicTorrentRssParser(); + } + } + + public override IEnumerable<string> RecentFeed + { + get + { + return new[] + { + "http://www.ezrss.it/feed/" + }; + } + } + + public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber) + { + yield return string.Format("http://www.ezrss.it/search/index.php?show_name={0}&season={1}&episode={2}&mode=rss", seriesTitle, seasonNumber, episodeNumber); + } + + public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset) + { + yield return string.Format("http://www.ezrss.it/search/index.php?show_name={0}&season={1}&mode=rss", seriesTitle, seasonNumber); + + } + + public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date) + { + //EZTV doesn't support searching based on actual epidose airdate. they only support release date. + return new string[0]; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/FetchAndParseRssService.cs b/NzbDrone.Core/Indexers/FetchAndParseRssService.cs index 9ee704910..c9633c8d0 100644 --- a/NzbDrone.Core/Indexers/FetchAndParseRssService.cs +++ b/NzbDrone.Core/Indexers/FetchAndParseRssService.cs @@ -9,7 +9,7 @@ namespace NzbDrone.Core.Indexers { public interface IFetchAndParseRss { - List<ReportInfo> Fetch(); + List<ReleaseInfo> Fetch(); } public class FetchAndParseRssService : IFetchAndParseRss @@ -25,9 +25,9 @@ namespace NzbDrone.Core.Indexers _logger = logger; } - public List<ReportInfo> Fetch() + public List<ReleaseInfo> Fetch() { - var result = new List<ReportInfo>(); + var result = new List<ReleaseInfo>(); var indexers = _indexerService.GetAvailableIndexers().ToList(); diff --git a/NzbDrone.Core/Indexers/IIndexer.cs b/NzbDrone.Core/Indexers/IIndexer.cs index c8e8e950f..a681ae3df 100644 --- a/NzbDrone.Core/Indexers/IIndexer.cs +++ b/NzbDrone.Core/Indexers/IIndexer.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Indexers public interface IIndexer { string Name { get; } - + bool EnableByDefault { get; } IEnumerable<IndexerDefinition> DefaultDefinitions { get; } @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Indexers IEnumerable<string> RecentFeed { get; } IParseFeed Parser { get; } + IndexerKind Kind { get; } IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber); IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date); diff --git a/NzbDrone.Core/Indexers/IParseFeed.cs b/NzbDrone.Core/Indexers/IParseFeed.cs new file mode 100644 index 000000000..a101f0a07 --- /dev/null +++ b/NzbDrone.Core/Indexers/IParseFeed.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.IO; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers +{ + public interface IParseFeed + { + IEnumerable<ReleaseInfo> Process(string source, string url); + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/IndexerBase.cs b/NzbDrone.Core/Indexers/IndexerBase.cs index 4b2cd9f99..6f544a2be 100644 --- a/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/NzbDrone.Core/Indexers/IndexerBase.cs @@ -7,6 +7,8 @@ namespace NzbDrone.Core.Indexers { public abstract string Name { get; } + public abstract IndexerKind Kind { get; } + public virtual bool EnableByDefault { get { return true; } } public IndexerDefinition InstanceDefinition { get; set; } @@ -25,17 +27,17 @@ namespace NzbDrone.Core.Indexers } } - public virtual IParseFeed Parser - { - get - { - return new BasicRssParser(); - } - } + public virtual IParseFeed Parser { get; private set; } public abstract IEnumerable<string> RecentFeed { get; } public abstract IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber); public abstract IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date); public abstract IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset); } + + public enum IndexerKind + { + Usenet, + Torrent + } } \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/IndexerFetchService.cs b/NzbDrone.Core/Indexers/IndexerFetchService.cs index 2e585f862..9814adc0d 100644 --- a/NzbDrone.Core/Indexers/IndexerFetchService.cs +++ b/NzbDrone.Core/Indexers/IndexerFetchService.cs @@ -11,11 +11,11 @@ namespace NzbDrone.Core.Indexers { public interface IFetchFeedFromIndexers { - IList<ReportInfo> FetchRss(IIndexer indexer); + IList<ReleaseInfo> FetchRss(IIndexer indexer); - IList<ReportInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria); - IList<ReportInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria); - IList<ReportInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria); + IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria); + IList<ReleaseInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria); + IList<ReleaseInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria); } public class FetchFeedService : IFetchFeedFromIndexers @@ -31,7 +31,7 @@ namespace NzbDrone.Core.Indexers } - public virtual IList<ReportInfo> FetchRss(IIndexer indexer) + public virtual IList<ReleaseInfo> FetchRss(IIndexer indexer) { _logger.Debug("Fetching feeds from " + indexer.Name); @@ -42,23 +42,23 @@ namespace NzbDrone.Core.Indexers return result; } - public IList<ReportInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria) + public IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria) { _logger.Debug("Searching for {0}", searchCriteria); - var result = Fetch(indexer, searchCriteria, 0).DistinctBy(c => c.NzbUrl).ToList(); + var result = Fetch(indexer, searchCriteria, 0).DistinctBy(c => c.DownloadUrl).ToList(); - _logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchCriteria, result.Count); + _logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count); return result; } - private IList<ReportInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria, int offset) + private IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria, int offset) { _logger.Debug("Searching for {0} offset: {1}", searchCriteria, offset); - var searchUrls = indexer.GetSeasonSearchUrls(searchCriteria.QueryTitle, searchCriteria.SeriesTvRageId, searchCriteria.SeasonNumber, offset); + var searchUrls = indexer.GetSeasonSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, offset); var result = Fetch(indexer, searchUrls); @@ -72,34 +72,32 @@ namespace NzbDrone.Core.Indexers return result; } - public IList<ReportInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria) + public IList<ReleaseInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria) { _logger.Debug("Searching for {0}", searchCriteria); - var searchUrls = indexer.GetEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.SeriesTvRageId, searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber); + var searchUrls = indexer.GetEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber); var result = Fetch(indexer, searchUrls); - _logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchCriteria, result.Count); + _logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count); return result; - } - - public IList<ReportInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria) + public IList<ReleaseInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria) { _logger.Debug("Searching for {0}", searchCriteria); - var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.SeriesTvRageId, searchCriteria.Airtime); + var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.Airtime); var result = Fetch(indexer, searchUrls); - _logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchCriteria, result.Count); + _logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count); return result; } - private List<ReportInfo> Fetch(IIndexer indexer, IEnumerable<string> urls) + private List<ReleaseInfo> Fetch(IIndexer indexer, IEnumerable<string> urls) { - var result = new List<ReportInfo>(); + var result = new List<ReleaseInfo>(); foreach (var url in urls) { @@ -140,4 +138,4 @@ namespace NzbDrone.Core.Indexers return result; } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core/Indexers/IndexerRepository.cs b/NzbDrone.Core/Indexers/IndexerRepository.cs index 678c26fd6..fd66a3910 100644 --- a/NzbDrone.Core/Indexers/IndexerRepository.cs +++ b/NzbDrone.Core/Indexers/IndexerRepository.cs @@ -1,7 +1,9 @@ using System; using System.Linq; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Indexers { @@ -13,8 +15,8 @@ namespace NzbDrone.Core.Indexers public class IndexerRepository : BasicRepository<IndexerDefinition>, IIndexerRepository { - public IndexerRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public IndexerRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/Indexers/IndexerService.cs b/NzbDrone.Core/Indexers/IndexerService.cs index 168835ffa..d9fc3ba27 100644 --- a/NzbDrone.Core/Indexers/IndexerService.cs +++ b/NzbDrone.Core/Indexers/IndexerService.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Common.Serializer; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using Omu.ValueInjecter; namespace NzbDrone.Core.Indexers @@ -35,15 +37,26 @@ namespace NzbDrone.Core.Indexers public class IndexerService : IIndexerService, IHandle<ApplicationStartedEvent> { private readonly IIndexerRepository _indexerRepository; + private readonly IConfigFileProvider _configFileProvider; private readonly Logger _logger; private readonly List<IIndexer> _indexers; - public IndexerService(IIndexerRepository indexerRepository, IEnumerable<IIndexer> indexers, Logger logger) + public IndexerService(IIndexerRepository indexerRepository, IEnumerable<IIndexer> indexers, IConfigFileProvider configFileProvider, Logger logger) { _indexerRepository = indexerRepository; + _configFileProvider = configFileProvider; _logger = logger; - _indexers = indexers.ToList(); + + + if (!configFileProvider.Torrent) + { + _indexers = indexers.Where(c => c.Kind != IndexerKind.Torrent).ToList(); + } + else + { + _indexers = indexers.ToList(); + } } public List<Indexer> All() @@ -153,10 +166,16 @@ namespace NzbDrone.Core.Indexers RemoveMissingImplementations(); - if (!All().Any()) + var definitions = _indexers.SelectMany(indexer => indexer.DefaultDefinitions); + + var currentIndexer = All(); + + var newIndexers = definitions.Where(def => currentIndexer.All(c => c.Implementation != def.Implementation)).ToList(); + + + if (newIndexers.Any()) { - var definitions = _indexers.SelectMany(indexer => indexer.DefaultDefinitions); - _indexerRepository.InsertMany(definitions.ToList()); + _indexerRepository.InsertMany(newIndexers); } } diff --git a/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/NzbDrone.Core/Indexers/Newznab/Newznab.cs index 51c49e26d..4341c22f7 100644 --- a/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -123,6 +123,15 @@ namespace NzbDrone.Core.Indexers.Newznab } } + public override IndexerKind Kind + { + get + { + return IndexerKind.Usenet; + } + } + + private static string NewsnabifyTitle(string title) { return title.Replace("+", "%20"); diff --git a/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs b/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs index 1e1376569..06c45ea1d 100644 --- a/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs +++ b/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs @@ -1,35 +1,37 @@ using System; -using System.Drawing; using System.Linq; using System.Xml.Linq; -using NLog; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Indexers.Newznab { - public class NewznabParser : BasicRssParser + public class NewznabParser : RssParserBase { - private static readonly XNamespace NewznabNamespace = "http://www.newznab.com/DTD/2010/feeds/attributes/"; - protected override string GetNzbInfoUrl(XElement item) { return item.Comments().Replace("#comments", ""); } - protected override ReportInfo PostProcessor(XElement item, ReportInfo currentResult) + protected override long GetSize(XElement item) { - if (currentResult != null) + var attributes = item.Elements("attr").ToList(); + var sizeElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("size", StringComparison.CurrentCultureIgnoreCase)); + + if (sizeElement == null) { - var attributes = item.Elements(NewznabNamespace + "attr").ToList(); - var sizeElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("size", StringComparison.CurrentCultureIgnoreCase)); - var rageIdElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("rageid", StringComparison.CurrentCultureIgnoreCase)); - if (sizeElement == null) - { - throw new SizeParsingException("Unable to parse size from: {0} [{1}]", currentResult.Title, currentResult.Indexer); - } + } - currentResult.Size = Convert.ToInt64(sizeElement.Attribute("value").Value); + return Convert.ToInt64(sizeElement.Attribute("value").Value); + } + + protected override ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult) + { + if (currentResult != null) + { + var attributes = item.Elements("attr").ToList(); + + var rageIdElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("rageid", StringComparison.CurrentCultureIgnoreCase)); if (rageIdElement != null) { diff --git a/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs b/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs index 84c96535b..549e52f92 100644 --- a/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs +++ b/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using NzbDrone.Common.Exceptions; +using NzbDrone.Common.Exceptions; namespace NzbDrone.Core.Indexers.Newznab { diff --git a/NzbDrone.Core/Indexers/NzbClub/NzbClub.cs b/NzbDrone.Core/Indexers/NzbClub/NzbClub.cs deleted file mode 100644 index cb2bc8a5f..000000000 --- a/NzbDrone.Core/Indexers/NzbClub/NzbClub.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace NzbDrone.Core.Indexers.NzbClub -{ - public class NzbClub : IndexerBase - { - public override string Name - { - get { return "NzbClub"; } - } - - public override bool EnableByDefault - { - get { return false; } - } - - public override IParseFeed Parser - { - get - { - return new NzbClubParser(); - } - } - - public override IEnumerable<string> RecentFeed - { - get - { - return new[] - { - String.Format("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=102952&st=1&ns=1&q=%23a.b.teevee"), - String.Format("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=5542&st=1&ns=1&q=") - }; - } - } - - public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber) - { - var searchUrls = new List<string>(); - - foreach (var url in RecentFeed) - { - searchUrls.Add(String.Format("{0}+{1}+s{2:00}e{3:00}", url, seriesTitle, seasonNumber, episodeNumber)); - } - - return searchUrls; - } - - public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset) - { - var searchUrls = new List<string>(); - - foreach (var url in RecentFeed) - { - searchUrls.Add(String.Format("{0}+{1}+s{2:00}", url, seriesTitle, seasonNumber)); - } - - return searchUrls; - } - - public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date) - { - var searchUrls = new List<String>(); - - foreach (var url in RecentFeed) - { - searchUrls.Add(String.Format("{0}+{1}+{2:yyyy MM dd}", url, seriesTitle, date)); - } - - return searchUrls; - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/NzbClub/NzbClubParser.cs b/NzbDrone.Core/Indexers/NzbClub/NzbClubParser.cs deleted file mode 100644 index 9c17b0ffa..000000000 --- a/NzbDrone.Core/Indexers/NzbClub/NzbClubParser.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using NLog; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.Indexers.NzbClub -{ - public class NzbClubParser : BasicRssParser - { - - private static readonly Regex SizeRegex = new Regex(@"(?:Size:)\s(?<size>\d+.\d+\s[g|m]i?[b])", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private readonly Logger logger; - - public NzbClubParser() - { - logger = LogManager.GetCurrentClassLogger(); - } - - - protected override ReportInfo PostProcessor(XElement item, ReportInfo currentResult) - { - if (currentResult != null) - { - var match = SizeRegex.Match(item.Description()); - - if (match.Success && match.Groups["size"].Success) - { - currentResult.Size = GetReportSize(match.Groups["size"].Value); - } - else - { - logger.Warn("Couldn't parse size from {0}", item.Description()); - } - } - - return currentResult; - } - - protected override string GetTitle(XElement item) - { - var title = ParseHeader(item.Title()); - - if (String.IsNullOrWhiteSpace(title)) - return item.Title(); - - return title; - } - - protected override string GetNzbInfoUrl(XElement item) - { - return item.Links().First(); - } - - protected override string GetNzbUrl(XElement item) - { - var enclosure = item.Element("enclosure"); - - return enclosure.Attribute("url").Value; - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs b/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs index 8d8e9b4a9..15f6dbbe5 100644 --- a/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs +++ b/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs @@ -10,6 +10,14 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs get { return "omgwtfnzbs"; } } + public override IndexerKind Kind + { + get + { + return IndexerKind.Usenet; + } + } + public override IParseFeed Parser { get diff --git a/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs b/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs index a2369f81a..afaeb3890 100644 --- a/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs +++ b/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs @@ -1,11 +1,10 @@ using System; using System.Text.RegularExpressions; using System.Xml.Linq; -using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Indexers.Omgwtfnzbs { - public class OmgwtfnzbsParser : BasicRssParser + public class OmgwtfnzbsParser : RssParserBase { protected override string GetNzbInfoUrl(XElement item) { @@ -21,15 +20,10 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs return String.Empty; } - protected override ReportInfo PostProcessor(XElement item, ReportInfo currentResult) + protected override long GetSize(XElement item) { - if (currentResult != null) - { - var sizeString = Regex.Match(item.Description(), @"(?:Size:\<\/b\>\s\d+\.)\d{1,2}\s\w{2}(?:\<br \/\>)", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value; - currentResult.Size = GetReportSize(sizeString); - } - - return currentResult; + var sizeString = Regex.Match(item.Description(), @"(?:Size:\<\/b\>\s\d+\.)\d{1,2}\s\w{2}(?:\<br \/\>)", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value; + return ParseSize(sizeString); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/BasicRssParser.cs b/NzbDrone.Core/Indexers/RssParserBase.cs similarity index 63% rename from NzbDrone.Core/Indexers/BasicRssParser.cs rename to NzbDrone.Core/Indexers/RssParserBase.cs index 84c61f296..485bc0140 100644 --- a/NzbDrone.Core/Indexers/BasicRssParser.cs +++ b/NzbDrone.Core/Indexers/RssParserBase.cs @@ -7,43 +7,45 @@ using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; using NLog; +using NzbDrone.Common.Instrumentation; +using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Indexers { - public interface IParseFeed - { - IEnumerable<ReportInfo> Process(string xml, string url); - } - - public class BasicRssParser : IParseFeed + public abstract class RssParserBase : IParseFeed { private readonly Logger _logger; - public BasicRssParser() + protected virtual ReleaseInfo CreateNewReleaseInfo() + { + return new ReleaseInfo(); + } + + protected RssParserBase() { - _logger = LogManager.GetCurrentClassLogger(); + _logger = NzbDroneLogger.GetLogger(this); } - public IEnumerable<ReportInfo> Process(string xml, string url) + public IEnumerable<ReleaseInfo> Process(string xml, string url) { using (var xmlTextReader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { ProhibitDtd = false, IgnoreComments = true })) { + var document = XDocument.Load(xmlTextReader); var items = document.Descendants("item"); - var result = new List<ReportInfo>(); + var result = new List<ReleaseInfo>(); foreach (var item in items) { try { - var reportInfo = ParseFeedItem(item); + var reportInfo = ParseFeedItem(item.StripNameSpace(), url); if (reportInfo != null) { - reportInfo.NzbUrl = GetNzbUrl(item); - reportInfo.NzbInfoUrl = GetNzbInfoUrl(item); - + reportInfo.DownloadUrl = GetNzbUrl(item); + reportInfo.InfoUrl = GetNzbInfoUrl(item); result.Add(reportInfo); } } @@ -58,6 +60,31 @@ namespace NzbDrone.Core.Indexers } } + private ReleaseInfo ParseFeedItem(XElement item, string url) + { + var title = GetTitle(item); + + var reportInfo = CreateNewReleaseInfo(); + + reportInfo.Title = title; + reportInfo.PublishDate = item.PublishDate(); + reportInfo.ReleaseGroup = ParseReleaseGroup(title); + reportInfo.DownloadUrl = GetNzbUrl(item); + reportInfo.InfoUrl = GetNzbInfoUrl(item); + + try + { + reportInfo.Size = GetSize(item); + } + catch (Exception) + { + throw new SizeParsingException("Unable to parse size from: {0} [{1}]", reportInfo.Title, url); + } + + _logger.Trace("Parsed: {0} from: {1}", reportInfo, item.Title()); + + return PostProcessor(item, reportInfo); + } protected virtual string GetTitle(XElement item) { @@ -74,25 +101,14 @@ namespace NzbDrone.Core.Indexers return String.Empty; } - protected virtual ReportInfo PostProcessor(XElement item, ReportInfo currentResult) + protected abstract long GetSize(XElement item); + + protected virtual ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult) { return currentResult; } - private ReportInfo ParseFeedItem(XElement item) - { - var title = GetTitle(item); - var reportInfo = new ReportInfo(); - - reportInfo.Title = title; - reportInfo.Age = DateTime.Now.Date.Subtract(item.PublishDate().Date).Days; - reportInfo.ReleaseGroup = ParseReleaseGroup(title); - - _logger.Trace("Parsed: {0} from: {1}", reportInfo, item.Title()); - - return PostProcessor(item, reportInfo); - } public static string ParseReleaseGroup(string title) { @@ -110,39 +126,15 @@ namespace NzbDrone.Core.Indexers if (@group.Length == title.Length) return String.Empty; - return @group; - } - - private static readonly Regex[] HeaderRegex = new[] - { - new Regex(@"(?:\[.+\]\-\[.+\]\-\[.+\]\-\[)(?<nzbTitle>.+)(?:\]\-.+)", - RegexOptions.IgnoreCase), - - new Regex(@"(?:\[.+\]\W+\[.+\]\W+\[.+\]\W+\"")(?<nzbTitle>.+)(?:\"".+)", - RegexOptions.IgnoreCase), - - new Regex(@"(?:\[)(?<nzbTitle>.+)(?:\]\-.+)", - RegexOptions.IgnoreCase), - }; - - public static string ParseHeader(string header) - { - foreach (var regex in HeaderRegex) - { - var match = regex.Matches(header); - - if (match.Count != 0) - return match[0].Groups["nzbTitle"].Value.Trim(); - } - - return header; + return @group.Trim('-', ' ', '[', ']'); } private static readonly Regex ReportSizeRegex = new Regex(@"(?<value>\d+\.\d{1,2}|\d+\,\d+\.\d{1,2}|\d+)\W?(?<unit>GB|MB|GiB|MiB)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static long GetReportSize(string sizeString) + + public static long ParseSize(string sizeString) { var match = ReportSizeRegex.Matches(sizeString); @@ -176,4 +168,4 @@ namespace NzbDrone.Core.Indexers return Convert.ToInt64(result); } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core/Indexers/RssSyncCommand.cs b/NzbDrone.Core/Indexers/RssSyncCommand.cs index 04a7a123b..56b4532bf 100644 --- a/NzbDrone.Core/Indexers/RssSyncCommand.cs +++ b/NzbDrone.Core/Indexers/RssSyncCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Indexers { - public class RssSyncCommand : ICommand + public class RssSyncCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } + } } \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/RssSyncService.cs b/NzbDrone.Core/Indexers/RssSyncService.cs index baf414fac..5f9be30a9 100644 --- a/NzbDrone.Core/Indexers/RssSyncService.cs +++ b/NzbDrone.Core/Indexers/RssSyncService.cs @@ -1,8 +1,10 @@ using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; +using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Indexers { @@ -32,13 +34,13 @@ namespace NzbDrone.Core.Indexers public void Sync() { - _logger.Info("Starting RSS Sync"); + _logger.ProgressInfo("Starting RSS Sync"); var reports = _rssFetcherAndParser.Fetch(); var decisions = _downloadDecisionMaker.GetRssDecision(reports); - var qualifiedReports = _downloadApprovedReports.DownloadApproved(decisions); + var downloaded = _downloadApprovedReports.DownloadApproved(decisions); - _logger.Info("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, qualifiedReports.Count()); + _logger.ProgressInfo("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, downloaded.Count()); } public void Execute(RssSyncCommand message) diff --git a/NzbDrone.Core/Indexers/Wombles/Wombles.cs b/NzbDrone.Core/Indexers/Wombles/Wombles.cs index adff820ec..95cd559de 100644 --- a/NzbDrone.Core/Indexers/Wombles/Wombles.cs +++ b/NzbDrone.Core/Indexers/Wombles/Wombles.cs @@ -10,6 +10,14 @@ namespace NzbDrone.Core.Indexers.Wombles get { return "WomblesIndex"; } } + public override IndexerKind Kind + { + get + { + return IndexerKind.Usenet; + } + } + public override IParseFeed Parser { get diff --git a/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs b/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs index de2aa0a99..2b67dfe97 100644 --- a/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs +++ b/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs @@ -1,23 +1,17 @@ using System.Xml.Linq; -using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Indexers.Wombles { - public class WomblesParser : BasicRssParser + public class WomblesParser : RssParserBase { protected override string GetNzbInfoUrl(XElement item) { return null; } - protected override ReportInfo PostProcessor(XElement item, ReportInfo currentResult) + protected override long GetSize(XElement item) { - if (currentResult != null) - { - currentResult.Size = 0; - } - - return currentResult; + return 0; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/XElementExtensions.cs b/NzbDrone.Core/Indexers/XElementExtensions.cs index 8460c34d6..254c9ae6f 100644 --- a/NzbDrone.Core/Indexers/XElementExtensions.cs +++ b/NzbDrone.Core/Indexers/XElementExtensions.cs @@ -5,13 +5,13 @@ using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using NLog; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Core.Indexers { public static class XElementExtensions { - - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); private static readonly Regex RemoveTimeZoneRegex = new Regex(@"\s[A-Z]{2,4}$", RegexOptions.Compiled); @@ -20,6 +20,21 @@ namespace NzbDrone.Core.Indexers return item.TryGetValue("title", "Unknown"); } + public static XElement StripNameSpace(this XElement root) + { + var res = new XElement( + root.Name.LocalName, + root.HasElements ? + root.Elements().Select(StripNameSpace) : + (object)root.Value + ); + + res.ReplaceAttributes( + root.Attributes().Where(attr => (!attr.IsNamespaceDeclaration))); + + return res; + } + public static DateTime PublishDate(this XElement item) { string dateString = item.TryGetValue("pubDate"); @@ -32,7 +47,7 @@ namespace NzbDrone.Core.Indexers dateString = RemoveTimeZoneRegex.Replace(dateString, ""); result = DateTime.Parse(dateString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal); } - return result.ToUniversalTime(); + return result.ToUniversalTime().Date; } catch (FormatException e) { @@ -58,6 +73,11 @@ namespace NzbDrone.Core.Indexers return item.TryGetValue("comments"); } + public static long Length(this XElement item) + { + return long.Parse(item.TryGetValue("length")); + } + private static string TryGetValue(this XElement item, string elementName, string defaultValue = "") { var element = item.Element(elementName); diff --git a/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs b/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs index 19776e76d..986331002 100644 --- a/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs @@ -1,8 +1,15 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Instrumentation.Commands { - public class ClearLogCommand : ICommand + public class ClearLogCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs index 5d3228afb..1a331247e 100644 --- a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs @@ -1,8 +1,15 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Instrumentation.Commands { - public class DeleteLogFilesCommand : ICommand + public class DeleteLogFilesCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs b/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs index c00b27020..c85427fc8 100644 --- a/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs @@ -1,8 +1,8 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Instrumentation.Commands { - public class TrimLogCommand : ICommand + public class TrimLogCommand : Command { } } \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/DatabaseTarget.cs b/NzbDrone.Core/Instrumentation/DatabaseTarget.cs index 779643240..673f4f9d6 100644 --- a/NzbDrone.Core/Instrumentation/DatabaseTarget.cs +++ b/NzbDrone.Core/Instrumentation/DatabaseTarget.cs @@ -3,8 +3,9 @@ using NLog.Config; using NLog; using NLog.Layouts; using NLog.Targets; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Instrumentation { diff --git a/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs b/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs index 5c98646ad..3a12cd057 100644 --- a/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs +++ b/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.IO; using NLog; using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Instrumentation.Commands; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Instrumentation { diff --git a/NzbDrone.Core/Instrumentation/LogRepository.cs b/NzbDrone.Core/Instrumentation/LogRepository.cs index 0a00aa27b..8259b5cf7 100644 --- a/NzbDrone.Core/Instrumentation/LogRepository.cs +++ b/NzbDrone.Core/Instrumentation/LogRepository.cs @@ -1,6 +1,8 @@ using System; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Instrumentation { @@ -11,8 +13,8 @@ namespace NzbDrone.Core.Instrumentation public class LogRepository : BasicRepository<Log>, ILogRepository { - public LogRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public LogRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/Instrumentation/LogService.cs b/NzbDrone.Core/Instrumentation/LogService.cs index 5e5717571..f46dbf5dd 100644 --- a/NzbDrone.Core/Instrumentation/LogService.cs +++ b/NzbDrone.Core/Instrumentation/LogService.cs @@ -1,7 +1,7 @@ -using System; -using NzbDrone.Common.Messaging; -using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Instrumentation.Commands; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Instrumentation { diff --git a/NzbDrone.Core/Instrumentation/LoggerExtensions.cs b/NzbDrone.Core/Instrumentation/LoggerExtensions.cs new file mode 100644 index 000000000..82ece28d2 --- /dev/null +++ b/NzbDrone.Core/Instrumentation/LoggerExtensions.cs @@ -0,0 +1,34 @@ +using System; +using NLog; + +namespace NzbDrone.Core.Instrumentation +{ + public static class LoggerExtensions + { + public static void ProgressInfo(this Logger logger, string message, params object[] args) + { + var formattedMessage = String.Format(message, args); + LogProgressMessage(logger, LogLevel.Info, formattedMessage); + } + + public static void ProgressDebug(this Logger logger, string message, params object[] args) + { + var formattedMessage = String.Format(message, args); + LogProgressMessage(logger, LogLevel.Debug, formattedMessage); + } + + public static void ProgressTrace(this Logger logger, string message, params object[] args) + { + var formattedMessage = String.Format(message, args); + LogProgressMessage(logger, LogLevel.Trace, formattedMessage); + } + + private static void LogProgressMessage(Logger logger, LogLevel level, string message) + { + var logEvent = new LogEventInfo(level, logger.Name, message); + logEvent.Properties.Add("Status", ""); + + logger.Log(logEvent); + } + } +} diff --git a/NzbDrone.Core/Instrumentation/SetLoggingLevel.cs b/NzbDrone.Core/Instrumentation/SetLoggingLevel.cs index 251ef6f38..61412e8fb 100644 --- a/NzbDrone.Core/Instrumentation/SetLoggingLevel.cs +++ b/NzbDrone.Core/Instrumentation/SetLoggingLevel.cs @@ -2,10 +2,12 @@ using System.Linq; using NLog; using NLog.Config; -using NzbDrone.Common.Messaging; +using NLog.Targets; using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Instrumentation { @@ -28,10 +30,12 @@ namespace NzbDrone.Core.Instrumentation var minimumLogLevel = LogLevel.FromString(_configFileProvider.LogLevel); var rules = LogManager.Configuration.LoggingRules; - var rollingFileLogger = rules.Single(s => s.Targets.Any(t => t.Name == "rollingFileLogger")); + var rollingFileLogger = rules.Single(s => s.Targets.Any(t => t is FileTarget)); rollingFileLogger.EnableLoggingForLevel(LogLevel.Trace); SetMinimumLogLevel(rollingFileLogger, minimumLogLevel); + + LogManager.ReconfigExistingLoggers(); } private void SetMinimumLogLevel(LoggingRule rule, LogLevel minimumLogLevel) diff --git a/NzbDrone.Core/Jobs/JobRepository.cs b/NzbDrone.Core/Jobs/JobRepository.cs index 700b8674e..8e2aa5858 100644 --- a/NzbDrone.Core/Jobs/JobRepository.cs +++ b/NzbDrone.Core/Jobs/JobRepository.cs @@ -1,7 +1,8 @@ using System; using System.Linq; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Jobs { @@ -15,8 +16,8 @@ namespace NzbDrone.Core.Jobs public class ScheduledTaskRepository : BasicRepository<ScheduledTask>, IScheduledTaskRepository { - public ScheduledTaskRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public ScheduledTaskRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/Jobs/Scheduler.cs b/NzbDrone.Core/Jobs/Scheduler.cs index 20ed73489..2b8b42086 100644 --- a/NzbDrone.Core/Jobs/Scheduler.cs +++ b/NzbDrone.Core/Jobs/Scheduler.cs @@ -2,8 +2,10 @@ using System.Threading; using System.Threading.Tasks; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using Timer = System.Timers.Timer; using NzbDrone.Common.TPL; @@ -14,15 +16,15 @@ namespace NzbDrone.Core.Jobs IHandle<ApplicationShutdownRequested> { private readonly ITaskManager _taskManager; - private readonly IMessageAggregator _messageAggregator; + private readonly ICommandExecutor _commandExecutor; private readonly Logger _logger; private static readonly Timer Timer = new Timer(); private static CancellationTokenSource _cancellationTokenSource; - public Scheduler(ITaskManager taskManager, IMessageAggregator messageAggregator, Logger logger) + public Scheduler(ITaskManager taskManager, ICommandExecutor commandExecutor, Logger logger) { _taskManager = taskManager; - _messageAggregator = messageAggregator; + _commandExecutor = commandExecutor; _logger = logger; } @@ -52,7 +54,7 @@ namespace NzbDrone.Core.Jobs try { - _messageAggregator.PublishCommand(task.TypeName); + _commandExecutor.PublishCommand(task.TypeName); } catch (Exception e) { diff --git a/NzbDrone.Core/Jobs/TaskManager.cs b/NzbDrone.Core/Jobs/TaskManager.cs index d77c8b0aa..af16df6f9 100644 --- a/NzbDrone.Core/Jobs/TaskManager.cs +++ b/NzbDrone.Core/Jobs/TaskManager.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.DataAugmentation.Scene; @@ -10,6 +9,9 @@ using NzbDrone.Core.Indexers; using NzbDrone.Core.Instrumentation.Commands; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands.Tracking; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Providers; using NzbDrone.Core.Tv.Commands; using NzbDrone.Core.Update.Commands; @@ -49,7 +51,8 @@ namespace NzbDrone.Core.Jobs new ScheduledTask{ Interval = 1, TypeName = typeof(DownloadedEpisodesScanCommand).FullName}, new ScheduledTask{ Interval = 60, TypeName = typeof(ApplicationUpdateCommand).FullName}, new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName}, - new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName} + new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName}, + new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName} }; var currentTasks = _scheduledTaskRepository.All(); @@ -82,6 +85,7 @@ namespace NzbDrone.Core.Jobs if (scheduledTask != null) { + _logger.Trace("Updating last run time for: {0}", scheduledTask.TypeName); _scheduledTaskRepository.SetLastExecutionTime(scheduledTask.Id, DateTime.UtcNow); } } @@ -93,4 +97,4 @@ namespace NzbDrone.Core.Jobs _scheduledTaskRepository.Update(rss); } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs b/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs index 985986b05..f66622dd3 100644 --- a/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs +++ b/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs @@ -6,5 +6,4 @@ namespace NzbDrone.Core.Lifecycle { } - } \ No newline at end of file diff --git a/NzbDrone.Core/MediaCover/MediaCoverService.cs b/NzbDrone.Core/MediaCover/MediaCoverService.cs index e3d247bfa..67b0d0b21 100644 --- a/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -5,7 +5,8 @@ using System.Net; using NLog; using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; diff --git a/NzbDrone.Core/MediaFiles/Commands/BackendCommandAttribute.cs b/NzbDrone.Core/MediaFiles/Commands/BackendCommandAttribute.cs new file mode 100644 index 000000000..7e8347897 --- /dev/null +++ b/NzbDrone.Core/MediaFiles/Commands/BackendCommandAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace NzbDrone.Core.MediaFiles.Commands +{ + public class BackendCommandAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs b/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs index b873dcc5f..a06cb29d7 100644 --- a/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs +++ b/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs @@ -1,8 +1,8 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands { - public class CleanMediaFileDb : ICommand + public class CleanMediaFileDb : Command { public int SeriesId { get; private set; } diff --git a/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs b/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs index b2d16f231..cb8d8c3a6 100644 --- a/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs @@ -1,8 +1,8 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands { - public class CleanUpRecycleBinCommand : ICommand + public class CleanUpRecycleBinCommand : Command { } } \ No newline at end of file diff --git a/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs b/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs index 0f03f2083..c9abe533a 100644 --- a/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs @@ -1,11 +1,9 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands { - public class DownloadedEpisodesScanCommand : ICommand + public class DownloadedEpisodesScanCommand : Command { - public DownloadedEpisodesScanCommand() - { - } + } } \ No newline at end of file diff --git a/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs b/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs index 723c5d74b..b9a917ca3 100644 --- a/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs @@ -1,11 +1,19 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands { - public class RenameSeasonCommand : ICommand + public class RenameSeasonCommand : Command { - public int SeriesId { get; private set; } - public int SeasonNumber { get; private set; } + public int SeriesId { get; set; } + public int SeasonNumber { get; set; } + + public override bool SendUpdatesToClient + { + get + { + return true; + } + } public RenameSeasonCommand(int seriesId, int seasonNumber) { diff --git a/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs b/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs index 7716c43c0..eb7e578d4 100644 --- a/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs @@ -1,10 +1,22 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands { - public class RenameSeriesCommand : ICommand + public class RenameSeriesCommand : Command { - public int SeriesId { get; private set; } + public int SeriesId { get; set; } + + public override bool SendUpdatesToClient + { + get + { + return true; + } + } + + public RenameSeriesCommand() + { + } public RenameSeriesCommand(int seriesId) { diff --git a/NzbDrone.Core/MediaFiles/DiskScanService.cs b/NzbDrone.Core/MediaFiles/DiskScanService.cs index 0fb6901fe..16b64789f 100644 --- a/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -1,11 +1,13 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; using System.Linq; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Instrumentation; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; @@ -20,39 +22,28 @@ namespace NzbDrone.Core.MediaFiles IDiskScanService, IHandle<SeriesUpdatedEvent> { - private readonly HashSet<string> _mediaExtensions; - - private const string EXTENSIONS = - //XBMC - ".m4v .3gp .nsv .ts .ty .strm .rm .rmvb .m3u .ifo .mov .qt .divx .xvid .bivx .vob .nrg .img " + - ".iso .pva .wmv .asf .asx .ogm .m2v .avi .bin .dat .dvr-ms .mpg .mpeg .mp4 .mkv .avc .vp3 " + - ".svq3 .nuv .viv .dv .fli .flv .wpl " + - //Other - ".m2ts"; - private readonly IDiskProvider _diskProvider; private readonly IMakeImportDecision _importDecisionMaker; private readonly IImportApprovedEpisodes _importApprovedEpisodes; - private readonly IMessageAggregator _messageAggregator; + private readonly ICommandExecutor _commandExecutor; private readonly Logger _logger; public DiskScanService(IDiskProvider diskProvider, IMakeImportDecision importDecisionMaker, IImportApprovedEpisodes importApprovedEpisodes, - IMessageAggregator messageAggregator, Logger logger) + ICommandExecutor commandExecutor, Logger logger) { _diskProvider = diskProvider; _importDecisionMaker = importDecisionMaker; _importApprovedEpisodes = importApprovedEpisodes; - _messageAggregator = messageAggregator; + _commandExecutor = commandExecutor; _logger = logger; - - _mediaExtensions = new HashSet<string>(EXTENSIONS.Split(' ').Select(c => c.ToLower())); } private void Scan(Series series) { - _messageAggregator.PublishCommand(new CleanMediaFileDb(series.Id)); + _logger.ProgressInfo("Scanning disk for {0}", series.Title); + _commandExecutor.PublishCommand(new CleanMediaFileDb(series.Id)); if (!_diskProvider.FolderExists(series.Path)) { @@ -73,7 +64,7 @@ namespace NzbDrone.Core.MediaFiles var searchOption = allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var filesOnDisk = _diskProvider.GetFiles(path, searchOption); - var mediaFileList = filesOnDisk.Where(c => _mediaExtensions.Contains(Path.GetExtension(c).ToLower())).ToList(); + var mediaFileList = filesOnDisk.Where(c => MediaFileExtensions.Extensions.Contains(Path.GetExtension(c).ToLower())).ToList(); _logger.Trace("{0} video files were found in {1}", mediaFileList.Count, path); return mediaFileList.ToArray(); diff --git a/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs index f5bf03915..55c6f4638 100644 --- a/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs +++ b/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs @@ -4,11 +4,12 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Parser; using NzbDrone.Core.Tv; @@ -51,13 +52,13 @@ namespace NzbDrone.Core.MediaFiles if (String.IsNullOrEmpty(downloadedEpisodesFolder)) { - _logger.Warn("Downloaded Episodes Folder is not configured"); + _logger.Warn("Drone Factory folder is not configured"); return; } if (!_diskProvider.FolderExists(downloadedEpisodesFolder)) { - _logger.Warn("Downloaded Episodes Folder [{0}] doesn't exist.", downloadedEpisodesFolder); + _logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder); return; } @@ -112,7 +113,7 @@ namespace NzbDrone.Core.MediaFiles var videoFiles = _diskScanService.GetVideoFiles(subfolderInfo.FullName); - return ProcessFiles(videoFiles, series); + return ProcessFiles(series, videoFiles); } private void ProcessVideoFile(string videoFile) @@ -125,16 +126,16 @@ namespace NzbDrone.Core.MediaFiles return; } - if (_diskProvider.IsFileLocked(new FileInfo(videoFile))) + if (_diskProvider.IsFileLocked(videoFile)) { _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); return; } - ProcessFiles(new[] { videoFile }, series); + ProcessFiles(series, videoFile); } - private List<ImportDecision> ProcessFiles(IEnumerable<string> videoFiles, Series series) + private List<ImportDecision> ProcessFiles(Series series, params string[] videoFiles) { var decisions = _importDecisionMaker.GetImportDecisions(videoFiles, series, true); return _importApprovedEpisodes.Import(decisions, true); diff --git a/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index ef230443b..91157d26a 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -3,8 +3,8 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Messaging; -using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; @@ -21,19 +21,19 @@ namespace NzbDrone.Core.MediaFiles { private readonly IEpisodeService _episodeService; private readonly IBuildFileNames _buildFileNames; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly IDiskProvider _diskProvider; private readonly Logger _logger; public MoveEpisodeFiles(IEpisodeService episodeService, IBuildFileNames buildFileNames, - IMessageAggregator messageAggregator, + IEventAggregator eventAggregator, IDiskProvider diskProvider, Logger logger) { _episodeService = episodeService; _buildFileNames = buildFileNames; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _diskProvider = diskProvider; _logger = logger; } diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 733b8580d..a9a92bb87 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -4,8 +4,10 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Messaging; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.MediaFiles.EpisodeImport { @@ -19,19 +21,19 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport private readonly IUpgradeMediaFiles _episodeFileUpgrader; private readonly IMediaFileService _mediaFileService; private readonly IDiskProvider _diskProvider; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; public ImportApprovedEpisodes(IUpgradeMediaFiles episodeFileUpgrader, IMediaFileService mediaFileService, IDiskProvider diskProvider, - IMessageAggregator messageAggregator, + IEventAggregator eventAggregator, Logger logger) { _episodeFileUpgrader = episodeFileUpgrader; _mediaFileService = mediaFileService; _diskProvider = diskProvider; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _logger = logger; } @@ -69,8 +71,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath()); episodeFile.Path = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode); - _messageAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile)); - _messageAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode)); + _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile)); + _eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode)); } _mediaFileService.Add(episodeFile); diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs index 9c499d8fb..609a962cd 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs @@ -23,22 +23,33 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications { try { + if (localEpisode.ExistingFile) + { + _logger.Trace("Skipping free space check for existing episode"); + return true; + } + var path = Directory.GetParent(localEpisode.Series.Path); var freeSpace = _diskProvider.GetAvailableSpace(path.FullName); + if (!freeSpace.HasValue) + { + _logger.Trace("Free space check returned an invalid result for: {0}", path); + return true; + } + if (freeSpace < localEpisode.Size + 100.Megabytes()) { _logger.Warn("Not enough free space to import: {0}", localEpisode); return false; } - - return true; } catch (Exception ex) { _logger.ErrorException("Unable to check free disk space while importing: " + localEpisode.Path, ex); - throw; } + + return true; } } } diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotInUseSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotInUseSpecification.cs index 5fd2f453b..1da911a60 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotInUseSpecification.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotInUseSpecification.cs @@ -1,5 +1,4 @@ -using System.IO; -using NLog; +using NLog; using NzbDrone.Common; using NzbDrone.Core.Parser.Model; @@ -20,13 +19,13 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications public bool IsSatisfiedBy(LocalEpisode localEpisode) { - if (_diskProvider.IsParent(localEpisode.Series.Path, localEpisode.Path)) + if (localEpisode.ExistingFile) { _logger.Trace("{0} is in series folder, skipping in use check", localEpisode.Path); return true; } - if (_diskProvider.IsFileLocked(new FileInfo(localEpisode.Path))) + if (_diskProvider.IsFileLocked(localEpisode.Path)) { _logger.Trace("{0} is in use"); return false; diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs index 51561b005..3d2901974 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs @@ -1,6 +1,7 @@ using System; using System.IO; using NLog; +using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Providers; using NzbDrone.Core.Tv; diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs index 0f1e66d3a..b8212e12c 100644 --- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs +++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs @@ -24,7 +24,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications public bool IsSatisfiedBy(LocalEpisode localEpisode) { - if (_diskProvider.IsParent(localEpisode.Series.Path, localEpisode.Path)) + if (localEpisode.ExistingFile) { _logger.Trace("{0} is in series folder, unpacking check", localEpisode.Path); return true; diff --git a/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs b/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs new file mode 100644 index 000000000..9416e4d4d --- /dev/null +++ b/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Core.Qualities; + + +namespace NzbDrone.Core.MediaFiles +{ + public static class MediaFileExtensions + { + private static Dictionary<String, Quality> _fileExtensions; + + static MediaFileExtensions() + { + _fileExtensions = new Dictionary<String, Quality> + { + { ".m4v", Quality.SDTV }, + { ".3gp", Quality.SDTV }, + { ".nsv", Quality.SDTV }, + { ".ty", Quality.SDTV }, + { ".strm", Quality.SDTV }, + { ".rm", Quality.SDTV }, + { ".rmvb", Quality.SDTV }, + { ".m3u", Quality.SDTV }, + { ".ifo", Quality.SDTV }, + { ".mov", Quality.SDTV }, + { ".qt", Quality.SDTV }, + { ".divx", Quality.SDTV }, + { ".xvid", Quality.SDTV }, + { ".bivx", Quality.SDTV }, + { ".nrg", Quality.SDTV }, + { ".pva", Quality.SDTV }, + { ".wmv", Quality.SDTV }, + { ".asf", Quality.SDTV }, + { ".asx", Quality.SDTV }, + { ".ogm", Quality.SDTV }, + { ".m2v", Quality.SDTV }, + { ".avi", Quality.SDTV }, + { ".bin", Quality.SDTV }, + { ".dat", Quality.SDTV }, + { ".dvr-ms", Quality.SDTV }, + { ".mpg", Quality.SDTV }, + { ".mpeg", Quality.SDTV }, + { ".mp4", Quality.SDTV }, + { ".avc", Quality.SDTV }, + { ".vp3", Quality.SDTV }, + { ".svq3", Quality.SDTV }, + { ".nuv", Quality.SDTV }, + { ".viv", Quality.SDTV }, + { ".dv", Quality.SDTV }, + { ".fli", Quality.SDTV }, + { ".flv", Quality.SDTV }, + { ".wpl", Quality.SDTV }, + + //DVD + { ".img", Quality.DVD }, + { ".iso", Quality.DVD }, + { ".vob", Quality.DVD }, + + //HD + { ".mkv", Quality.HDTV720p }, + { ".ts", Quality.HDTV720p }, + + //Bluray + { ".m2ts", Quality.Bluray720p } + }; + } + + public static HashSet<String> Extensions + { + get { return new HashSet<String>(_fileExtensions.Keys); } + } + + public static Quality GetQualityForExtension(string extension) + { + if (_fileExtensions.ContainsKey(extension)) + { + return _fileExtensions[extension]; + } + + return Quality.Unknown; + } + } +} diff --git a/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index 406bd9669..7a0839594 100644 --- a/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Linq; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.MediaFiles { @@ -16,8 +18,8 @@ namespace NzbDrone.Core.MediaFiles public class MediaFileRepository : BasicRepository<EpisodeFile>, IMediaFileRepository { - public MediaFileRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public MediaFileRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/MediaFiles/MediaFileService.cs b/NzbDrone.Core/MediaFiles/MediaFileService.cs index 54501c859..b963b408e 100644 --- a/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -1,9 +1,9 @@ -using System; using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv.Events; using NzbDrone.Common; @@ -24,21 +24,21 @@ namespace NzbDrone.Core.MediaFiles public class MediaFileService : IMediaFileService, IHandleAsync<SeriesDeletedEvent> { - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly IMediaFileRepository _mediaFileRepository; private readonly Logger _logger; - public MediaFileService(IMediaFileRepository mediaFileRepository, IMessageAggregator messageAggregator, Logger logger) + public MediaFileService(IMediaFileRepository mediaFileRepository, IEventAggregator eventAggregator, Logger logger) { _mediaFileRepository = mediaFileRepository; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _logger = logger; } public EpisodeFile Add(EpisodeFile episodeFile) { var addedFile = _mediaFileRepository.Insert(episodeFile); - _messageAggregator.PublishEvent(new EpisodeFileAddedEvent(addedFile)); + _eventAggregator.PublishEvent(new EpisodeFileAddedEvent(addedFile)); return addedFile; } @@ -51,7 +51,7 @@ namespace NzbDrone.Core.MediaFiles { _mediaFileRepository.Delete(episodeFile); - _messageAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, forUpgrade)); + _eventAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, forUpgrade)); } public bool Exists(string path) diff --git a/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs b/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs index b72f03b4f..26c4d1905 100644 --- a/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs +++ b/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs @@ -2,8 +2,9 @@ using System; using System.Linq; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Messaging; using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Tv; namespace NzbDrone.Core.MediaFiles diff --git a/NzbDrone.Core/Providers/VideoFileInfoReader.cs b/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs similarity index 93% rename from NzbDrone.Core/Providers/VideoFileInfoReader.cs rename to NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs index 1aa8c121e..a2f1143e2 100644 --- a/NzbDrone.Core/Providers/VideoFileInfoReader.cs +++ b/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs @@ -5,7 +5,7 @@ using NLog; using NzbDrone.Common; using NzbDrone.Core.Model; -namespace NzbDrone.Core.Providers +namespace NzbDrone.Core.MediaFiles.MediaInfo { public interface IVideoFileInfoReader { @@ -31,7 +31,7 @@ namespace NzbDrone.Core.Providers if (!_diskProvider.FileExists(filename)) throw new FileNotFoundException("Media file does not exist: " + filename); - var mediaInfo = new MediaInfo(); + var mediaInfo = new MediaInfoLib.MediaInfo(); try { @@ -112,10 +112,10 @@ namespace NzbDrone.Core.Providers public TimeSpan GetRunTime(string filename) { - var mediaInfo = new MediaInfo(); - + MediaInfoLib.MediaInfo mediaInfo = null; try { + mediaInfo = new MediaInfoLib.MediaInfo(); _logger.Trace("Getting media info from {0}", filename); mediaInfo.Option("ParseSpeed", "0.2"); @@ -133,7 +133,13 @@ namespace NzbDrone.Core.Providers catch (Exception ex) { _logger.ErrorException("Unable to parse media info from file: " + filename, ex); - mediaInfo.Close(); + } + finally + { + if (mediaInfo != null) + { + mediaInfo.Close(); + } } return new TimeSpan(); diff --git a/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs b/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs index ff3866b39..00ff120e2 100644 --- a/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs +++ b/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs @@ -3,9 +3,12 @@ using System.IO; using NLog; using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Messaging; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv.Events; namespace NzbDrone.Core.MediaFiles @@ -23,7 +26,7 @@ namespace NzbDrone.Core.MediaFiles private readonly IDiskProvider _diskProvider; private readonly IConfigService _configService; - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger logger = NzbDroneLogger.GetLogger(); public RecycleBinProvider(IDiskProvider diskProvider, IConfigService configService) { diff --git a/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs b/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs index 7912039e7..501963445 100644 --- a/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs +++ b/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs @@ -2,9 +2,12 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Instrumentation; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; namespace NzbDrone.Core.MediaFiles @@ -14,19 +17,19 @@ namespace NzbDrone.Core.MediaFiles private readonly ISeriesService _seriesService; private readonly IMediaFileService _mediaFileService; private readonly IMoveEpisodeFiles _episodeFileMover; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; public RenameEpisodeFileService(ISeriesService seriesService, IMediaFileService mediaFileService, IMoveEpisodeFiles episodeFileMover, - IMessageAggregator messageAggregator, + IEventAggregator eventAggregator, Logger logger) { _seriesService = seriesService; _mediaFileService = mediaFileService; _episodeFileMover = episodeFileMover; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _logger = logger; } @@ -58,7 +61,7 @@ namespace NzbDrone.Core.MediaFiles if (renamed.Any()) { - _messageAggregator.PublishEvent(new SeriesRenamedEvent(series)); + _eventAggregator.PublishEvent(new SeriesRenamedEvent(series)); } } @@ -67,9 +70,9 @@ namespace NzbDrone.Core.MediaFiles var series = _seriesService.GetSeries(message.SeriesId); var episodeFiles = _mediaFileService.GetFilesBySeason(message.SeriesId, message.SeasonNumber); - _logger.Info("Renaming {0} files for {1} season {2}", episodeFiles.Count, series.Title, message.SeasonNumber); + _logger.ProgressInfo("Renaming {0} files for {1} season {2}", episodeFiles.Count, series.Title, message.SeasonNumber); RenameFiles(episodeFiles, series); - _logger.Debug("Episode Fies renamed for {0} season {1}", series.Title, message.SeasonNumber); + _logger.ProgressInfo("Episode Fies renamed for {0} season {1}", series.Title, message.SeasonNumber); } public void Execute(RenameSeriesCommand message) @@ -77,9 +80,9 @@ namespace NzbDrone.Core.MediaFiles var series = _seriesService.GetSeries(message.SeriesId); var episodeFiles = _mediaFileService.GetFilesBySeries(message.SeriesId); - _logger.Info("Renaming {0} files for {1}", episodeFiles.Count, series.Title); + _logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title); RenameFiles(episodeFiles, series); - _logger.Debug("Episode Fies renamed for {0}", series.Title); + _logger.ProgressInfo("Episode Fies renamed for {0}", series.Title); } } } diff --git a/NzbDrone.Core/Messaging/Commands/BackendCommandAttribute.cs b/NzbDrone.Core/Messaging/Commands/BackendCommandAttribute.cs new file mode 100644 index 000000000..b40f64f9c --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/BackendCommandAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace NzbDrone.Core.Messaging.Commands +{ + public class BackendCommandAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Messaging/Commands/Command.cs b/NzbDrone.Core/Messaging/Commands/Command.cs new file mode 100644 index 000000000..516464381 --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/Command.cs @@ -0,0 +1,82 @@ +using System; +using FluentMigrator.Runner; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Commands.Tracking; + +namespace NzbDrone.Core.Messaging.Commands +{ + public abstract class Command : ModelBase, IMessage + { + private static readonly object Mutex = new object(); + private static int _idCounter; + private readonly StopWatch _stopWatch; + + public CommandStatus State { get; private set; } + public DateTime StateChangeTime { get; private set; } + + public virtual bool SendUpdatesToClient + { + get + { + return false; + } + } + + public TimeSpan Runtime + { + get + { + return _stopWatch.ElapsedTime(); + } + } + + public Exception Exception { get; private set; } + public string Message { get; private set; } + + public string Name { get; private set; } + + protected Command() + { + Name = GetType().Name.Replace("Command", ""); + StateChangeTime = DateTime.UtcNow; + State = CommandStatus.Pending; + _stopWatch = new StopWatch(); + + lock (Mutex) + { + Id = ++_idCounter; + } + } + + public void Start() + { + _stopWatch.Start(); + StateChangeTime = DateTime.UtcNow; + State = CommandStatus.Running; + SetMessage("Starting"); + } + + public void Failed(Exception exception) + { + _stopWatch.Stop(); + StateChangeTime = DateTime.UtcNow; + State = CommandStatus.Failed; + Exception = exception; + SetMessage("Failed"); + } + + public void Completed() + { + _stopWatch.Stop(); + StateChangeTime = DateTime.UtcNow; + State = CommandStatus.Completed; + SetMessage("Completed"); + } + + public void SetMessage(string message) + { + Message = message; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Messaging/Commands/CommandEqualityComparer.cs b/NzbDrone.Core/Messaging/Commands/CommandEqualityComparer.cs new file mode 100644 index 000000000..2b24895f1 --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/CommandEqualityComparer.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.Messaging.Commands +{ + public class CommandEqualityComparer : IEqualityComparer<Command> + { + public static readonly CommandEqualityComparer Instance = new CommandEqualityComparer(); + + private CommandEqualityComparer() + { + + } + + public bool Equals(Command x, Command y) + { + if(x.GetType() != y.GetType()) return false; + + var xProperties = x.GetType().GetProperties(); + var yProperties = y.GetType().GetProperties(); + + foreach (var xProperty in xProperties) + { + if (xProperty.Name == "Id") + { + continue; + } + + var yProperty = yProperties.Single(p => p.Name == xProperty.Name); + + var xValue = xProperty.GetValue(x, null); + var yValue = yProperty.GetValue(y, null); + + if (xValue == null && yValue == null) + { + return true; + } + + if (xValue == null || yValue == null) + { + return false; + } + + if (!xValue.Equals(yValue)) + { + return false; + } + } + + return true; + } + + public int GetHashCode(Command obj) + { + return obj.Id.GetHashCode(); + } + } +} diff --git a/NzbDrone.Core/Messaging/Commands/CommandExecutor.cs b/NzbDrone.Core/Messaging/Commands/CommandExecutor.cs new file mode 100644 index 000000000..dd3175304 --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/CommandExecutor.cs @@ -0,0 +1,142 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Common.Serializer; +using NzbDrone.Common.TPL; +using NzbDrone.Core.Messaging.Commands.Tracking; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.ProgressMessaging; + +namespace NzbDrone.Core.Messaging.Commands +{ + public class CommandExecutor : ICommandExecutor + { + private readonly Logger _logger; + private readonly IServiceFactory _serviceFactory; + private readonly ITrackCommands _trackCommands; + private readonly IEventAggregator _eventAggregator; + private readonly TaskFactory _taskFactory; + + public CommandExecutor(Logger logger, IServiceFactory serviceFactory, ITrackCommands trackCommands, IEventAggregator eventAggregator) + { + var scheduler = new LimitedConcurrencyLevelTaskScheduler(3); + + _logger = logger; + _serviceFactory = serviceFactory; + _trackCommands = trackCommands; + _eventAggregator = eventAggregator; + _taskFactory = new TaskFactory(scheduler); + } + + public void PublishCommand<TCommand>(TCommand command) where TCommand : Command + { + Ensure.That(() => command).IsNotNull(); + + _logger.Trace("Publishing {0}", command.GetType().Name); + + if (_trackCommands.FindExisting(command) != null) + { + _logger.Debug("Command is already in progress: {0}", command.GetType().Name); + return; + } + + _trackCommands.Store(command); + + ExecuteCommand<TCommand>(command); + } + + public void PublishCommand(string commandTypeName) + { + dynamic command = GetCommand(commandTypeName); + PublishCommand(command); + } + + public Command PublishCommandAsync<TCommand>(TCommand command) where TCommand : Command + { + Ensure.That(() => command).IsNotNull(); + + _logger.Trace("Publishing {0}", command.GetType().Name); + + var existingCommand = _trackCommands.FindExisting(command); + + if (existingCommand != null) + { + _logger.Debug("Command is already in progress: {0}", command.GetType().Name); + return existingCommand; + } + + _trackCommands.Store(command); + + _taskFactory.StartNew(() => ExecuteCommand<TCommand>(command) + , TaskCreationOptions.PreferFairness) + .LogExceptions(); + + return command; + } + + public Command PublishCommandAsync(string commandTypeName) + { + dynamic command = GetCommand(commandTypeName); + return PublishCommandAsync(command); + } + + private dynamic GetCommand(string commandTypeName) + { + var commandType = _serviceFactory.GetImplementations(typeof(Command)) + .Single(c => c.FullName.Equals(commandTypeName, StringComparison.InvariantCultureIgnoreCase)); + + return Json.Deserialize("{}", commandType); + } + + private void ExecuteCommand<TCommand>(Command command) where TCommand : Command + { + var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType()); + var handler = (IExecute<TCommand>)_serviceFactory.Build(handlerContract); + + _logger.Trace("{0} -> {1}", command.GetType().Name, handler.GetType().Name); + + try + { + _trackCommands.Start(command); + BroadcastCommandUpdate(command); + + if (!MappedDiagnosticsContext.Contains("CommandId") && command.SendUpdatesToClient) + { + MappedDiagnosticsContext.Set("CommandId", command.Id.ToString()); + } + + handler.Execute((TCommand)command); + _trackCommands.Completed(command); + } + catch (Exception e) + { + _trackCommands.Failed(command, e); + throw; + } + finally + { + BroadcastCommandUpdate(command); + _eventAggregator.PublishEvent(new CommandExecutedEvent(command)); + + if (MappedDiagnosticsContext.Get("CommandId") == command.Id.ToString()) + { + MappedDiagnosticsContext.Remove("CommandId"); + } + } + + _logger.Trace("{0} <- {1} [{2}]", command.GetType().Name, handler.GetType().Name, command.Runtime.ToString("")); + } + + + private void BroadcastCommandUpdate(Command command) + { + if (command.SendUpdatesToClient) + { + _eventAggregator.PublishEvent(new CommandUpdatedEvent(command)); + } + } + } +} diff --git a/NzbDrone.Core/Messaging/Commands/ICommandExecutor.cs b/NzbDrone.Core/Messaging/Commands/ICommandExecutor.cs new file mode 100644 index 000000000..45d300fcd --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/ICommandExecutor.cs @@ -0,0 +1,10 @@ +namespace NzbDrone.Core.Messaging.Commands +{ + public interface ICommandExecutor + { + void PublishCommand<TCommand>(TCommand command) where TCommand : Command; + void PublishCommand(string commandTypeName); + Command PublishCommandAsync<TCommand>(TCommand command) where TCommand : Command; + Command PublishCommandAsync(string commandTypeName); + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/IExecute.cs b/NzbDrone.Core/Messaging/Commands/IExecute.cs similarity index 61% rename from NzbDrone.Common/Messaging/IExecute.cs rename to NzbDrone.Core/Messaging/Commands/IExecute.cs index a065e0828..4e058e4d0 100644 --- a/NzbDrone.Common/Messaging/IExecute.cs +++ b/NzbDrone.Core/Messaging/Commands/IExecute.cs @@ -1,6 +1,6 @@ -namespace NzbDrone.Common.Messaging +namespace NzbDrone.Core.Messaging.Commands { - public interface IExecute<TCommand> : IProcessMessage<TCommand> where TCommand : ICommand + public interface IExecute<TCommand> : IProcessMessage<TCommand> where TCommand : Command { void Execute(TCommand message); } diff --git a/NzbDrone.Core/Messaging/Commands/TestCommand.cs b/NzbDrone.Core/Messaging/Commands/TestCommand.cs new file mode 100644 index 000000000..616c99ee7 --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/TestCommand.cs @@ -0,0 +1,20 @@ +namespace NzbDrone.Core.Messaging.Commands +{ + public class TestCommand : Command + { + public int Duration { get; set; } + + public override bool SendUpdatesToClient + { + get + { + return true; + } + } + + public TestCommand() + { + Duration = 4000; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Messaging/Commands/TestCommandExecutor.cs b/NzbDrone.Core/Messaging/Commands/TestCommandExecutor.cs new file mode 100644 index 000000000..8e0ebfb71 --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/TestCommandExecutor.cs @@ -0,0 +1,23 @@ +using System.Threading; +using NLog; +using NzbDrone.Core.Instrumentation; + +namespace NzbDrone.Core.Messaging.Commands +{ + public class TestCommandExecutor : IExecute<TestCommand> + { + private readonly Logger _logger; + + public TestCommandExecutor(Logger logger) + { + _logger = logger; + } + + public void Execute(TestCommand message) + { + _logger.ProgressInfo("Starting Test command. duration {0}", message.Duration); + Thread.Sleep(message.Duration); + _logger.ProgressInfo("Completed Test command"); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Messaging/Commands/Tracking/CommandStatus.cs b/NzbDrone.Core/Messaging/Commands/Tracking/CommandStatus.cs new file mode 100644 index 000000000..26208e9dd --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/Tracking/CommandStatus.cs @@ -0,0 +1,10 @@ +namespace NzbDrone.Core.Messaging.Commands.Tracking +{ + public enum CommandStatus + { + Pending, + Running, + Completed, + Failed + } +} diff --git a/NzbDrone.Core/Messaging/Commands/Tracking/CommandTrackingService.cs b/NzbDrone.Core/Messaging/Commands/Tracking/CommandTrackingService.cs new file mode 100644 index 000000000..3e5a6c619 --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/Tracking/CommandTrackingService.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Cache; + +namespace NzbDrone.Core.Messaging.Commands.Tracking +{ + public interface ITrackCommands + { + Command GetById(int id); + Command GetById(string id); + void Completed(Command trackedCommand); + void Failed(Command trackedCommand, Exception e); + IEnumerable<Command> RunningCommands(); + Command FindExisting(Command command); + void Store(Command command); + void Start(Command command); + } + + public class CommandTrackingService : ITrackCommands, IExecute<TrackedCommandCleanupCommand> + { + private readonly ICached<Command> _cache; + + public CommandTrackingService(ICacheManger cacheManger) + { + _cache = cacheManger.GetCache<Command>(GetType()); + } + + public Command GetById(int id) + { + return _cache.Find(id.ToString()); + } + + public Command GetById(string id) + { + return _cache.Find(id); + } + + public void Start(Command command) + { + command.Start(); + } + + public void Completed(Command trackedCommand) + { + trackedCommand.Completed(); + } + + public void Failed(Command trackedCommand, Exception e) + { + trackedCommand.Failed(e); + } + + public IEnumerable<Command> RunningCommands() + { + return _cache.Values.Where(c => c.State == CommandStatus.Running); + } + + public Command FindExisting(Command command) + { + return RunningCommands().Where(c => c.GetType() == command.GetType()) + .SingleOrDefault(t => CommandEqualityComparer.Instance.Equals(t, command)); + } + + public void Store(Command command) + { + if (command.GetType() == typeof(TrackedCommandCleanupCommand)) + { + return; + } + + _cache.Set(command.Id.ToString(), command); + } + + public void Execute(TrackedCommandCleanupCommand message) + { + var old = _cache.Values.Where(c => c.State != CommandStatus.Running && c.StateChangeTime < DateTime.UtcNow.AddMinutes(-5)); + + foreach (var trackedCommand in old) + { + _cache.Remove(trackedCommand.Id.ToString()); + } + } + } +} diff --git a/NzbDrone.Core/Messaging/Commands/Tracking/ExistingCommand.cs b/NzbDrone.Core/Messaging/Commands/Tracking/ExistingCommand.cs new file mode 100644 index 000000000..4fa174d3c --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/Tracking/ExistingCommand.cs @@ -0,0 +1,16 @@ +using System; + +namespace NzbDrone.Core.Messaging.Commands.Tracking +{ + public class ExistingCommand + { + public Boolean Existing { get; set; } + public Command Command { get; set; } + + public ExistingCommand(Boolean exisitng, Command trackedCommand) + { + Existing = exisitng; + Command = trackedCommand; + } + } +} diff --git a/NzbDrone.Core/Messaging/Commands/Tracking/TrackedCommandCleanupCommand.cs b/NzbDrone.Core/Messaging/Commands/Tracking/TrackedCommandCleanupCommand.cs new file mode 100644 index 000000000..744b8a60f --- /dev/null +++ b/NzbDrone.Core/Messaging/Commands/Tracking/TrackedCommandCleanupCommand.cs @@ -0,0 +1,7 @@ +namespace NzbDrone.Core.Messaging.Commands.Tracking +{ + public class TrackedCommandCleanupCommand : Command + { + + } +} diff --git a/NzbDrone.Core/Messaging/Events/CommandCreatedEvent.cs b/NzbDrone.Core/Messaging/Events/CommandCreatedEvent.cs new file mode 100644 index 000000000..5224e2671 --- /dev/null +++ b/NzbDrone.Core/Messaging/Events/CommandCreatedEvent.cs @@ -0,0 +1,15 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.Messaging.Events +{ + public class CommandCreatedEvent : IEvent + { + public Command Command { get; private set; } + + public CommandCreatedEvent(Command trackedCommand) + { + Command = trackedCommand; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Messaging/Events/CommandExecutedEvent.cs b/NzbDrone.Core/Messaging/Events/CommandExecutedEvent.cs new file mode 100644 index 000000000..83fb2abdb --- /dev/null +++ b/NzbDrone.Core/Messaging/Events/CommandExecutedEvent.cs @@ -0,0 +1,15 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.Messaging.Events +{ + public class CommandExecutedEvent : IEvent + { + public Command Command { get; private set; } + + public CommandExecutedEvent(Command trackedCommand) + { + Command = trackedCommand; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/MessageAggregator.cs b/NzbDrone.Core/Messaging/Events/EventAggregator.cs similarity index 50% rename from NzbDrone.Common/Messaging/MessageAggregator.cs rename to NzbDrone.Core/Messaging/Events/EventAggregator.cs index 725f84ccf..7dbe1a331 100644 --- a/NzbDrone.Common/Messaging/MessageAggregator.cs +++ b/NzbDrone.Core/Messaging/Events/EventAggregator.cs @@ -1,25 +1,25 @@ -using System; -using System.Diagnostics; -using System.Linq; +using System; using System.Threading.Tasks; using NLog; +using NzbDrone.Common; using NzbDrone.Common.EnsureThat; -using NzbDrone.Common.Serializer; +using NzbDrone.Common.Messaging; using NzbDrone.Common.TPL; -namespace NzbDrone.Common.Messaging +namespace NzbDrone.Core.Messaging.Events { - public class MessageAggregator : IMessageAggregator + public class EventAggregator : IEventAggregator { private readonly Logger _logger; private readonly IServiceFactory _serviceFactory; private readonly TaskFactory _taskFactory; - public MessageAggregator(Logger logger, IServiceFactory serviceFactory) + public EventAggregator(Logger logger, IServiceFactory serviceFactory) { + var scheduler = new LimitedConcurrencyLevelTaskScheduler(3); + _logger = logger; _serviceFactory = serviceFactory; - var scheduler = new LimitedConcurrencyLevelTaskScheduler(2); _taskFactory = new TaskFactory(scheduler); } @@ -36,9 +36,9 @@ namespace NzbDrone.Common.Messaging { try { - _logger.Debug("{0} -> {1}", eventName, handler.GetType().Name); + _logger.Trace("{0} -> {1}", eventName, handler.GetType().Name); handler.Handle(@event); - _logger.Debug("{0} <- {1}", eventName, handler.GetType().Name); + _logger.Trace("{0} <- {1}", eventName, handler.GetType().Name); } catch (Exception e) { @@ -52,15 +52,14 @@ namespace NzbDrone.Common.Messaging _taskFactory.StartNew(() => { - _logger.Debug("{0} ~> {1}", eventName, handlerLocal.GetType().Name); + _logger.Trace("{0} ~> {1}", eventName, handlerLocal.GetType().Name); handlerLocal.HandleAsync(@event); - _logger.Debug("{0} <~ {1}", eventName, handlerLocal.GetType().Name); + _logger.Trace("{0} <~ {1}", eventName, handlerLocal.GetType().Name); }, TaskCreationOptions.PreferFairness) .LogExceptions(); } } - private static string GetEventName(Type eventType) { if (!eventType.IsGenericType) @@ -70,48 +69,5 @@ namespace NzbDrone.Common.Messaging return string.Format("{0}<{1}>", eventType.Name.Remove(eventType.Name.IndexOf('`')), eventType.GetGenericArguments()[0].Name); } - - - public void PublishCommand<TCommand>(TCommand command) where TCommand : class, ICommand - { - Ensure.That(() => command).IsNotNull(); - - var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType()); - - _logger.Trace("Publishing {0}", command.GetType().Name); - - var handler = (IExecute<TCommand>)_serviceFactory.Build(handlerContract); - - _logger.Debug("{0} -> {1}", command.GetType().Name, handler.GetType().Name); - - var sw = Stopwatch.StartNew(); - - try - { - handler.Execute(command); - sw.Stop(); - PublishEvent(new CommandCompletedEvent(command)); - } - catch (Exception e) - { - PublishEvent(new CommandFailedEvent(command, e)); - throw; - } - finally - { - PublishEvent(new CommandExecutedEvent(command)); - } - - _logger.Debug("{0} <- {1} [{2}]", command.GetType().Name, handler.GetType().Name, sw.Elapsed.ToString("")); - } - - public void PublishCommand(string commandTypeName) - { - var commandType = _serviceFactory.GetImplementations(typeof(ICommand)) - .Single(c => c.FullName.Equals(commandTypeName, StringComparison.InvariantCultureIgnoreCase)); - - dynamic command = Json.Deserialize("{}", commandType); - PublishCommand(command); - } } } diff --git a/NzbDrone.Core/Messaging/Events/IEventAggregator.cs b/NzbDrone.Core/Messaging/Events/IEventAggregator.cs new file mode 100644 index 000000000..9df32d66d --- /dev/null +++ b/NzbDrone.Core/Messaging/Events/IEventAggregator.cs @@ -0,0 +1,9 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Messaging.Events +{ + public interface IEventAggregator + { + void PublishEvent<TEvent>(TEvent @event) where TEvent : class, IEvent; + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/IHandle.cs b/NzbDrone.Core/Messaging/Events/IHandle.cs similarity index 79% rename from NzbDrone.Common/Messaging/IHandle.cs rename to NzbDrone.Core/Messaging/Events/IHandle.cs index ae40448d4..291e099d2 100644 --- a/NzbDrone.Common/Messaging/IHandle.cs +++ b/NzbDrone.Core/Messaging/Events/IHandle.cs @@ -1,4 +1,6 @@ -namespace NzbDrone.Common.Messaging +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Messaging.Events { public interface IHandle<TEvent> : IProcessMessage<TEvent> where TEvent : IEvent { diff --git a/NzbDrone.Common/Messaging/IProcessMessage.cs b/NzbDrone.Core/Messaging/IProcessMessage.cs similarity index 86% rename from NzbDrone.Common/Messaging/IProcessMessage.cs rename to NzbDrone.Core/Messaging/IProcessMessage.cs index b008d9e9b..2207e803d 100644 --- a/NzbDrone.Common/Messaging/IProcessMessage.cs +++ b/NzbDrone.Core/Messaging/IProcessMessage.cs @@ -1,10 +1,9 @@ -namespace NzbDrone.Common.Messaging +namespace NzbDrone.Core.Messaging { public interface IProcessMessage { } public interface IProcessMessageAsync : IProcessMessage { } - public interface IProcessMessage<TMessage> : IProcessMessage { } public interface IProcessMessageAsync<TMessage> : IProcessMessageAsync { } diff --git a/NzbDrone.Core/MetadataSource/Trakt/TraktCommunicationException.cs b/NzbDrone.Core/MetadataSource/Trakt/TraktCommunicationException.cs deleted file mode 100644 index d09dad66c..000000000 --- a/NzbDrone.Core/MetadataSource/Trakt/TraktCommunicationException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace NzbDrone.Core.MetadataSource.Trakt -{ - public class TraktCommunicationException : Exception - { - public TraktCommunicationException(string message, Exception innerException) : base(message, innerException) - { - } - } -} diff --git a/NzbDrone.Core/MetadataSource/Trakt/TraktException.cs b/NzbDrone.Core/MetadataSource/Trakt/TraktException.cs new file mode 100644 index 000000000..999122301 --- /dev/null +++ b/NzbDrone.Core/MetadataSource/Trakt/TraktException.cs @@ -0,0 +1,15 @@ +using System.Net; + +namespace NzbDrone.Core.MetadataSource.Trakt +{ + public class TraktException : NzbDroneClientException + { + public TraktException(string message) : base(HttpStatusCode.ServiceUnavailable, message) + { + } + + public TraktException(string message, params object[] args) : base(HttpStatusCode.ServiceUnavailable, message, args) + { + } + } +} diff --git a/NzbDrone.Core/MetadataSource/TraktProxy.cs b/NzbDrone.Core/MetadataSource/TraktProxy.cs index 2c18b5594..f37cc8d6b 100644 --- a/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Text.RegularExpressions; +using NLog; using NzbDrone.Common; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MetadataSource.Trakt; @@ -15,26 +17,33 @@ namespace NzbDrone.Core.MetadataSource { public class TraktProxy : ISearchForNewSeries, IProvideSeriesInfo { - public List<Series> SearchForNewSeries(string title) - { - var client = BuildClient("search", "shows"); - var restRequest = new RestRequest(GetSearchTerm(title)); - var response = client.ExecuteAndValidate<List<SearchShow>>(restRequest); - - return response.Select(MapSearchSeries).ToList(); - } - - + private readonly Logger _logger; private static readonly Regex InvalidSearchCharRegex = new Regex(@"[^a-zA-Z0-9\s-\.]", RegexOptions.Compiled); - private static string GetSearchTerm(string phrase) + public TraktProxy(Logger logger) { - phrase = phrase.RemoveAccent().ToLower(); - phrase = phrase.Replace("&", "and"); - phrase = InvalidSearchCharRegex.Replace(phrase, string.Empty); - phrase = phrase.CleanSpaces().Replace(" ", "+"); + _logger = logger; + } - return phrase; + public List<Series> SearchForNewSeries(string title) + { + try + { + var client = BuildClient("search", "shows"); + var restRequest = new RestRequest(GetSearchTerm(title) +"/30/seasons"); + var response = client.ExecuteAndValidate<List<Show>>(restRequest); + + return response.Select(MapSeries).ToList(); + } + catch (WebException ex) + { + throw new TraktException("Search for '{0}' failed. Unable to communicate with Trakt.", title); + } + catch (Exception ex) + { + _logger.WarnException(ex.Message, ex); + throw new TraktException("Search for '{0}' failed. Invalid response received from Trakt.", title); + } } public Tuple<Series, List<Episode>> GetSeriesInfo(int tvDbSeriesId) @@ -43,7 +52,6 @@ namespace NzbDrone.Core.MetadataSource var restRequest = new RestRequest(tvDbSeriesId.ToString() + "/extended"); var response = client.ExecuteAndValidate<Show>(restRequest); - var episodes = response.seasons.SelectMany(c => c.episodes).Select(MapEpisode).ToList(); var series = MapSeries(response); @@ -63,6 +71,7 @@ namespace NzbDrone.Core.MetadataSource series.ImdbId = show.imdb_id; series.Title = show.title; series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title); + series.Year = show.year; series.FirstAired = FromIso(show.first_aired_iso); series.Overview = show.overview; series.Runtime = show.runtime; @@ -71,6 +80,10 @@ namespace NzbDrone.Core.MetadataSource series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", ""); series.Status = GetSeriesStatus(show.status); + series.Seasons = show.seasons.Select(s => new Tv.Season + { + SeasonNumber = s.season + }).OrderByDescending(s => s.SeasonNumber).ToList(); series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.images.banner }); series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Poster, Url = GetPosterThumbnailUrl(show.images.poster) }); series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Fanart, Url = show.images.fanart }); @@ -141,6 +154,12 @@ namespace NzbDrone.Core.MetadataSource { DateTime result; + //Todo: Remove this when DST ends and/or trakt fixes DST airings in EST/EDT + if (iso != null && iso.EndsWith("-05:00")) + { + iso = iso.Replace("-05:00", "-04:00"); + } + if (!DateTime.TryParse(iso, out result)) return null; @@ -158,6 +177,14 @@ namespace NzbDrone.Core.MetadataSource return match.Captures[0].Value; } + private static string GetSearchTerm(string phrase) + { + phrase = phrase.RemoveAccent().ToLower(); + phrase = phrase.Replace("&", "and"); + phrase = InvalidSearchCharRegex.Replace(phrase, string.Empty); + phrase = phrase.CleanSpaces().Replace(" ", "+"); + return phrase; + } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core/Notifications/Email/EmailService.cs b/NzbDrone.Core/Notifications/Email/EmailService.cs index 38c37086d..5625a9785 100644 --- a/NzbDrone.Core/Notifications/Email/EmailService.cs +++ b/NzbDrone.Core/Notifications/Email/EmailService.cs @@ -2,7 +2,8 @@ using System.Net; using System.Net.Mail; using NLog; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using Omu.ValueInjecter; namespace NzbDrone.Core.Notifications.Email diff --git a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs index 258884788..3d9ec3797 100644 --- a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs +++ b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Email { - public class TestEmailCommand : ICommand + public class TestEmailCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } + public string Server { get; set; } public int Port { get; set; } public bool Ssl { get; set; } diff --git a/NzbDrone.Core/Notifications/Growl/GrowlService.cs b/NzbDrone.Core/Notifications/Growl/GrowlService.cs index deed4b369..5ec00df7c 100644 --- a/NzbDrone.Core/Notifications/Growl/GrowlService.cs +++ b/NzbDrone.Core/Notifications/Growl/GrowlService.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Linq; using Growl.Connector; using NLog; -using NzbDrone.Common.Messaging; +using NzbDrone.Common.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using GrowlNotification = Growl.Connector.Notification; namespace NzbDrone.Core.Notifications.Growl @@ -15,7 +17,7 @@ namespace NzbDrone.Core.Notifications.Growl public class GrowlService : IGrowlService, IExecute<TestGrowlCommand> { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); private readonly Application _growlApplication = new Application("NzbDrone"); private GrowlConnector _growlConnector; @@ -24,7 +26,7 @@ namespace NzbDrone.Core.Notifications.Growl public GrowlService() { _notificationTypes = GetNotificationTypes(); - _growlApplication.Icon = "https://github.com/NzbDrone/NzbDrone/raw/master/NzbDrone.Core/NzbDrone.jpg"; + _growlApplication.Icon = "https://raw.github.com/NzbDrone/NzbDrone/master/Logo/64.png"; } public void SendNotification(string title, string message, string notificationTypeName, string hostname, int port, string password) diff --git a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs index 35890fff9..c1d880541 100644 --- a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs +++ b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs @@ -1,9 +1,16 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Growl { - public class TestGrowlCommand : ICommand + public class TestGrowlCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } public string Host { get; set; } public int Port { get; set; } public string Password { get; set; } diff --git a/NzbDrone.Core/Notifications/NotificationRepository.cs b/NzbDrone.Core/Notifications/NotificationRepository.cs index f9f9feb0d..79dedcf34 100644 --- a/NzbDrone.Core/Notifications/NotificationRepository.cs +++ b/NzbDrone.Core/Notifications/NotificationRepository.cs @@ -1,7 +1,9 @@ using System; using System.Linq; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Notifications { @@ -13,8 +15,8 @@ namespace NzbDrone.Core.Notifications public class NotificationRepository : BasicRepository<NotificationDefinition>, INotificationRepository { - public NotificationRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public NotificationRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/Notifications/NotificationService.cs b/NzbDrone.Core/Notifications/NotificationService.cs index d48398898..366129433 100644 --- a/NzbDrone.Core/Notifications/NotificationService.cs +++ b/NzbDrone.Core/Notifications/NotificationService.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Composition; -using NzbDrone.Common.Messaging; using NzbDrone.Common.Serializer; using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using Omu.ValueInjecter; diff --git a/NzbDrone.Core/Notifications/Plex/PlexService.cs b/NzbDrone.Core/Notifications/Plex/PlexService.cs index 15e01420c..5a5bc82e3 100644 --- a/NzbDrone.Core/Notifications/Plex/PlexService.cs +++ b/NzbDrone.Core/Notifications/Plex/PlexService.cs @@ -4,7 +4,8 @@ using System.Linq; using System.Xml.Linq; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Plex { diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs index 6df162ab4..6de213789 100644 --- a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs +++ b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs @@ -1,9 +1,16 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Plex { - public class TestPlexClientCommand : ICommand + public class TestPlexClientCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } public string Host { get; set; } public int Port { get; set; } public string Username { get; set; } diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs index 49089afea..19fac0641 100644 --- a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs +++ b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Plex { - public class TestPlexServerCommand : ICommand + public class TestPlexServerCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } + public string Host { get; set; } public int Port { get; set; } } diff --git a/NzbDrone.Core/Notifications/Prowl/ProwlService.cs b/NzbDrone.Core/Notifications/Prowl/ProwlService.cs index bf850215f..e8c080f75 100644 --- a/NzbDrone.Core/Notifications/Prowl/ProwlService.cs +++ b/NzbDrone.Core/Notifications/Prowl/ProwlService.cs @@ -1,6 +1,7 @@ using System; using NLog; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using Prowlin; namespace NzbDrone.Core.Notifications.Prowl diff --git a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs index e58bf5a9c..50d02677e 100644 --- a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs +++ b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs @@ -1,9 +1,16 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Prowl { - public class TestProwlCommand : ICommand + public class TestProwlCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } public string ApiKey { get; set; } public int Priority { get; set; } } diff --git a/NzbDrone.Core/Notifications/Pushover/PushoverService.cs b/NzbDrone.Core/Notifications/Pushover/PushoverService.cs index cab9639bb..7e5725711 100644 --- a/NzbDrone.Core/Notifications/Pushover/PushoverService.cs +++ b/NzbDrone.Core/Notifications/Pushover/PushoverService.cs @@ -1,4 +1,5 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using RestSharp; using NzbDrone.Core.Rest; diff --git a/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs b/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs index 31bc034f9..13e232401 100644 --- a/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs +++ b/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Pushover { - public class TestPushoverCommand : ICommand + public class TestPushoverCommand : Command { + + public override bool SendUpdatesToClient + { + get + { + return true; + } + } public string UserKey { get; set; } public int Priority { get; set; } } diff --git a/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs b/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs index af5fe2366..1d7cb5377 100644 --- a/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs +++ b/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs @@ -26,7 +26,7 @@ namespace NzbDrone.Core.Notifications.Xbmc var parameters = new JObject( new JProperty("title", title), new JProperty("message", message), - new JProperty("image", "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png"), + new JProperty("image", "https://raw.github.com/NzbDrone/NzbDrone/master/Logo/64.png"), new JProperty("displaytime", settings.DisplayTime * 1000)); var postJson = BuildJsonRequest("GUI.ShowNotification", parameters); diff --git a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs index 56eb75ee8..cb8acb361 100644 --- a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs +++ b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Xbmc { - public class TestXbmcCommand : ICommand + public class TestXbmcCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } + public string Host { get; set; } public int Port { get; set; } public string Username { get; set; } diff --git a/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs b/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs index cc0f9bf3c..bbc8a237a 100644 --- a/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs +++ b/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs @@ -5,7 +5,9 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Messaging; +using NzbDrone.Common.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Tv; using NzbDrone.Core.Model.Xbmc; @@ -21,7 +23,7 @@ namespace NzbDrone.Core.Notifications.Xbmc public class XbmcService : IXbmcService, IExecute<TestXbmcCommand> { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); private readonly IHttpProvider _httpProvider; private readonly IEnumerable<IApiProvider> _apiProviders; diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 793db0f0a..dae681ba9 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -123,6 +123,7 @@ <Compile Include="Configuration\Events\ConfigFileSavedEvent.cs" /> <Compile Include="Configuration\Events\ConfigSavedEvent.cs" /> <Compile Include="Configuration\IConfigService.cs" /> + <Compile Include="Configuration\InvalidConfigFileException.cs" /> <Compile Include="DataAugmentation\DailySeries\DailySeriesDataProxy.cs" /> <Compile Include="DataAugmentation\DailySeries\DailySeriesService.cs" /> <Compile Include="DataAugmentation\Scene\SceneMapping.cs" /> @@ -130,7 +131,6 @@ <Compile Include="DataAugmentation\Scene\SceneMappingProxy.cs" /> <Compile Include="DataAugmentation\Scene\SceneMappingRepository.cs" /> <Compile Include="DataAugmentation\Scene\UpdateSceneMappingCommand.cs" /> - <Compile Include="Datastore\CachedBasicRepository.cs" /> <Compile Include="Datastore\ConnectionStringFactory.cs" /> <Compile Include="Datastore\Converters\BooleanIntConverter.cs" /> <Compile Include="Datastore\Converters\QualityIntConverter.cs" /> @@ -160,6 +160,10 @@ <Compile Include="Datastore\Migration\015_add_air_date_as_string.cs" /> <Compile Include="Datastore\Migration\016_updated_imported_history_item.cs" /> <Compile Include="Datastore\Migration\017_reset_scene_names.cs" /> + <Compile Include="Datastore\Migration\018_remove_duplicates.cs" /> + <Compile Include="Datastore\Migration\019_restore_unique_constraints.cs" /> + <Compile Include="Datastore\Migration\020_add_year_and_seasons_to_series.cs" /> + <Compile Include="Datastore\Migration\021_drop_seasons_table.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationContext.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationController.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" /> @@ -169,6 +173,8 @@ <Compile Include="Datastore\Migration\Framework\NzbDroneMigrationBase.cs" /> <Compile Include="Datastore\MigrationType.cs" /> <Compile Include="Datastore\Migration\Framework\SQLiteAlter.cs" /> + <Compile Include="Datastore\Migration\Framework\SQLiteColumn.cs" /> + <Compile Include="Datastore\Migration\Framework\SQLiteIndex.cs" /> <Compile Include="Datastore\Migration\Framework\SQLiteMigrationHelper.cs" /> <Compile Include="Datastore\ModelBase.cs" /> <Compile Include="Datastore\BasicRepository.cs" /> @@ -177,10 +183,13 @@ <Compile Include="Datastore\PagingSpecExtensions.cs" /> <Compile Include="Datastore\RelationshipExtensions.cs" /> <Compile Include="Datastore\TableMapping.cs" /> - <Compile Include="DecisionEngine\DownloadDecision.cs" /> + <Compile Include="DecisionEngine\Specifications\DownloadDecision.cs" /> <Compile Include="DecisionEngine\IRejectWithReason.cs" /> <Compile Include="DecisionEngine\IDecisionEngineSpecification.cs" /> + <Compile Include="DecisionEngine\Specifications\CutoffSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\NotRestrictedReleaseSpecification.cs" /> + <Compile Include="DecisionEngine\Specifications\NotSampleSpecification.cs" /> + <Compile Include="DecisionEngine\Specifications\RssSync\ProperSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\Search\SeasonMatchSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\Search\DailyEpisodeMatchSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\AcceptableSizeSpecification.cs" /> @@ -207,10 +216,14 @@ <Compile Include="IndexerSearch\EpisodeSearchCommand.cs" /> <Compile Include="IndexerSearch\SeasonSearchCommand.cs" /> <Compile Include="IndexerSearch\SeasonSearchService.cs" /> + <Compile Include="Indexers\BasicTorrentRssParser.cs" /> + <Compile Include="Indexers\DownloadProtocols.cs" /> + <Compile Include="Indexers\Eztv\Eztv.cs" /> <Compile Include="Indexers\FetchAndParseRssService.cs" /> <Compile Include="Indexers\IIndexer.cs" /> <Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" /> <Compile Include="Indexers\IndexerWithSetting.cs" /> + <Compile Include="Indexers\IParseFeed.cs" /> <Compile Include="Indexers\Newznab\SizeParsingException.cs" /> <Compile Include="Indexers\NullSetting.cs" /> <Compile Include="Indexers\RssSyncCommand.cs" /> @@ -219,6 +232,32 @@ <Compile Include="Instrumentation\Commands\DeleteLogFilesCommand.cs" /> <Compile Include="Instrumentation\Commands\TrimLogCommand.cs" /> <Compile Include="Instrumentation\DeleteLogFilesService.cs" /> + <Compile Include="MediaFiles\MediaFileExtensions.cs" /> + <Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" /> + <Compile Include="Messaging\Commands\CommandExecutor.cs" /> + <Compile Include="Messaging\Commands\ICommandExecutor.cs" /> + <Compile Include="Messaging\Commands\IExecute.cs" /> + <Compile Include="Messaging\Commands\Tracking\CommandStatus.cs" /> + <Compile Include="Messaging\Commands\Tracking\CommandTrackingService.cs" /> + <Compile Include="Messaging\Commands\Tracking\ExistingCommand.cs" /> + <Compile Include="Messaging\Commands\Tracking\TrackedCommandCleanupCommand.cs" /> + <Compile Include="Messaging\Events\EventAggregator.cs" /> + <Compile Include="Messaging\Events\IEventAggregator.cs" /> + <Compile Include="Messaging\Events\IHandle.cs" /> + <Compile Include="MetadataSource\Trakt\TraktException.cs" /> + <Compile Include="NzbDroneClientException.cs" /> + <Compile Include="Instrumentation\LoggerExtensions.cs" /> + <Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" /> + <Compile Include="Messaging\Commands\BackendCommandAttribute.cs" /> + <Compile Include="Messaging\Commands\Command.cs" /> + <Compile Include="Messaging\Commands\CommandEqualityComparer.cs" /> + <Compile Include="Messaging\Commands\TestCommand.cs" /> + <Compile Include="Messaging\Commands\TestCommandExecutor.cs" /> + <Compile Include="Messaging\Events\CommandCreatedEvent.cs" /> + <Compile Include="Messaging\Events\CommandExecutedEvent.cs" /> + <Compile Include="Messaging\IProcessMessage.cs" /> + <Compile Include="ProgressMessaging\CommandUpdatedEvent.cs" /> + <Compile Include="ProgressMessaging\ProgressMessageTarget.cs" /> <Compile Include="Instrumentation\SetLoggingLevel.cs" /> <Compile Include="Jobs\TaskManager.cs" /> <Compile Include="Lifecycle\ApplicationShutdownRequested.cs" /> @@ -244,7 +283,6 @@ <Compile Include="MediaFiles\RenameEpisodeFileService.cs" /> <Compile Include="MediaFiles\SameFilenameException.cs" /> <Compile Include="MediaFiles\UpgradeMediaFileService.cs" /> - <Compile Include="MetadataSource\Trakt\TraktCommunicationException.cs" /> <Compile Include="Notifications\Email\TestEmailCommand.cs" /> <Compile Include="Notifications\Growl\GrowlSettings.cs" /> <Compile Include="Notifications\Growl\TestGrowlCommand.cs" /> @@ -262,7 +300,7 @@ <Compile Include="IndexerSearch\Definitions\SingleEpisodeSearchCriteria.cs" /> <Compile Include="IndexerSearch\NzbSearchService.cs" /> <Compile Include="IndexerSearch\SearchAndDownloadService.cs" /> - <Compile Include="Indexers\BasicRssParser.cs" /> + <Compile Include="Indexers\RssParserBase.cs" /> <Compile Include="Indexers\RssSyncService.cs" /> <Compile Include="Indexers\IndexerBase.cs" /> <Compile Include="Indexers\IndexerDefinition.cs" /> @@ -271,8 +309,6 @@ <Compile Include="Indexers\Newznab\Newznab.cs" /> <Compile Include="Indexers\Newznab\NewznabSettings.cs" /> <Compile Include="Indexers\Newznab\NewznabParser.cs" /> - <Compile Include="Indexers\NzbClub\NzbClub.cs" /> - <Compile Include="Indexers\NzbClub\NzbClubParser.cs" /> <Compile Include="Indexers\Omgwtfnzbs\Omgwtfnzbs.cs" /> <Compile Include="Indexers\Omgwtfnzbs\OmgwtfnzbsParser.cs" /> <Compile Include="Indexers\IIndexerSetting.cs" /> @@ -341,7 +377,8 @@ <Compile Include="Parser\Model\LocalEpisode.cs" /> <Compile Include="Parser\Model\ParsedEpisodeInfo.cs" /> <Compile Include="Parser\Model\RemoteEpisode.cs" /> - <Compile Include="Parser\Model\ReportInfo.cs" /> + <Compile Include="Parser\Model\ReleaseInfo.cs" /> + <Compile Include="Parser\Model\TorrentInfo.cs" /> <Compile Include="Parser\Parser.cs" /> <Compile Include="Parser\ParsingService.cs" /> <Compile Include="Parser\QualityParser.cs" /> @@ -385,10 +422,8 @@ <Compile Include="Model\Xem\XemValues.cs" /> <Compile Include="MediaCover\MediaCoverService.cs" /> <Compile Include="Download\Clients\Nzbget\NzbgetClient.cs" /> - <Compile Include="Providers\VideoFileInfoReader.cs" /> <Compile Include="Download\Clients\PneumaticClient.cs" /> <Compile Include="MediaFiles\RecycleBinProvider.cs" /> - <Compile Include="Tv\SeasonService.cs" /> <Compile Include="SeriesStats\SeriesStatistics.cs" /> <Compile Include="SeriesStats\SeriesStatisticsRepository.cs" /> <Compile Include="Tv\SeriesTypes.cs" /> @@ -533,9 +568,6 @@ <ItemGroup> <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" /> </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="NzbDrone.jpg" /> - </ItemGroup> <ItemGroup> <ProjectReference Include="..\Marr.Data\Marr.Data.csproj"> <Project>{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}</Project> diff --git a/NzbDrone.Core/NzbDrone.Core.ncrunchproject b/NzbDrone.Core/NzbDrone.Core.ncrunchproject index 73e1ebbb9..b2eed192e 100644 --- a/NzbDrone.Core/NzbDrone.Core.ncrunchproject +++ b/NzbDrone.Core/NzbDrone.Core.ncrunchproject @@ -1,8 +1,8 @@ <ProjectConfiguration> <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> - <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> + <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,10 +12,12 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration></UseBuildConfiguration> - <UseBuildPlatform /> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> <ProxyProcessPath></ProxyProcessPath> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <UseCPUArchitecture>x86</UseCPUArchitecture> + <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> + <BuildProcessArchitecture>x86</BuildProcessArchitecture> <HiddenWarnings>PostBuildEventDisabled</HiddenWarnings> </ProjectConfiguration> \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.jpg b/NzbDrone.Core/NzbDrone.jpg deleted file mode 100644 index 4e544b00f..000000000 Binary files a/NzbDrone.Core/NzbDrone.jpg and /dev/null differ diff --git a/NzbDrone.Core/NzbDroneClientException.cs b/NzbDrone.Core/NzbDroneClientException.cs new file mode 100644 index 000000000..cc94caffc --- /dev/null +++ b/NzbDrone.Core/NzbDroneClientException.cs @@ -0,0 +1,21 @@ +using System.Net; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core +{ + public class NzbDroneClientException : NzbDroneException + { + public HttpStatusCode StatusCode { get; private set; } + + public NzbDroneClientException(HttpStatusCode statusCode, string message, params object[] args) : base(message, args) + { + StatusCode = statusCode; + } + + public NzbDroneClientException(HttpStatusCode statusCode, string message) + : base(message) + { + StatusCode = statusCode; + } + } +} diff --git a/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/NzbDrone.Core/Parser/Model/LocalEpisode.cs index 3936ca4b7..06f620a96 100644 --- a/NzbDrone.Core/Parser/Model/LocalEpisode.cs +++ b/NzbDrone.Core/Parser/Model/LocalEpisode.cs @@ -7,12 +7,13 @@ namespace NzbDrone.Core.Parser.Model { public class LocalEpisode { - public string Path { get; set; } + public String Path { get; set; } public Int64 Size { get; set; } public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } public Series Series { get; set; } public List<Episode> Episodes { get; set; } public QualityModel Quality { get; set; } + public Boolean ExistingFile { get; set; } public int SeasonNumber { diff --git a/NzbDrone.Core/Parser/Model/ReleaseInfo.cs b/NzbDrone.Core/Parser/Model/ReleaseInfo.cs new file mode 100644 index 000000000..e5b3d9ed8 --- /dev/null +++ b/NzbDrone.Core/Parser/Model/ReleaseInfo.cs @@ -0,0 +1,27 @@ +using System; + +namespace NzbDrone.Core.Parser.Model +{ + public class ReleaseInfo + { + public string Title { get; set; } + public long Size { get; set; } + public string DownloadUrl { get; set; } + public string InfoUrl { get; set; } + public string CommentUrl { get; set; } + public String Indexer { get; set; } + + public DateTime PublishDate { get; set; } + + public int Age + { + get + { + return DateTime.UtcNow.Subtract(PublishDate).Days; + } + } + + public string ReleaseGroup { get; set; } + public int TvRageId { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Parser/Model/RemoteEpisode.cs b/NzbDrone.Core/Parser/Model/RemoteEpisode.cs index 70b001511..ac394d0d8 100644 --- a/NzbDrone.Core/Parser/Model/RemoteEpisode.cs +++ b/NzbDrone.Core/Parser/Model/RemoteEpisode.cs @@ -7,7 +7,7 @@ namespace NzbDrone.Core.Parser.Model { public class RemoteEpisode { - public ReportInfo Report { get; set; } + public ReleaseInfo Release { get; set; } public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } @@ -22,7 +22,7 @@ namespace NzbDrone.Core.Parser.Model public override string ToString() { - return Report.Title; + return Release.Title; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Parser/Model/ReportInfo.cs b/NzbDrone.Core/Parser/Model/ReportInfo.cs deleted file mode 100644 index d357d7d66..000000000 --- a/NzbDrone.Core/Parser/Model/ReportInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace NzbDrone.Core.Parser.Model -{ - public class ReportInfo - { - public string Title { get; set; } - public long Size { get; set; } - public string NzbUrl { get; set; } - public string NzbInfoUrl { get; set; } - public String Indexer { get; set; } - public int Age { get; set; } - public string ReleaseGroup { get; set; } - public int TvRageId { get; set; } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Parser/Model/TorrentInfo.cs b/NzbDrone.Core/Parser/Model/TorrentInfo.cs new file mode 100644 index 000000000..434967b2b --- /dev/null +++ b/NzbDrone.Core/Parser/Model/TorrentInfo.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.Parser.Model +{ + public class TorrentInfo : ReleaseInfo + { + public string MagnetUrl { get; set; } + public string InfoHash { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Parser/Parser.cs b/NzbDrone.Core/Parser/Parser.cs index 3178b5084..85ef1be7c 100644 --- a/NzbDrone.Core/Parser/Parser.cs +++ b/NzbDrone.Core/Parser/Parser.cs @@ -4,16 +4,14 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using NLog; -using NzbDrone.Common; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; namespace NzbDrone.Core.Parser { public static class Parser { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); private static readonly Regex[] ReportTitleRegex = new[] { diff --git a/NzbDrone.Core/Parser/ParsingService.cs b/NzbDrone.Core/Parser/ParsingService.cs index 44113b8a4..b678c5eeb 100644 --- a/NzbDrone.Core/Parser/ParsingService.cs +++ b/NzbDrone.Core/Parser/ParsingService.cs @@ -1,6 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using NLog; +using NzbDrone.Common; +using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; @@ -8,30 +12,33 @@ namespace NzbDrone.Core.Parser { public interface IParsingService { - LocalEpisode GetEpisodes(string filename, Series series); LocalEpisode GetEpisodes(string filename, Series series, bool sceneSource); Series GetSeries(string title); - RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId); + RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null); + List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null); } public class ParsingService : IParsingService { private readonly IEpisodeService _episodeService; private readonly ISeriesService _seriesService; + private readonly IDiskProvider _diskProvider; + private readonly ISceneMappingService _sceneMappingService; private readonly Logger _logger; - public ParsingService(IEpisodeService episodeService, ISeriesService seriesService, Logger logger) + public ParsingService(IEpisodeService episodeService, + ISeriesService seriesService, + IDiskProvider diskProvider, + ISceneMappingService sceneMappingService, + Logger logger) { _episodeService = episodeService; _seriesService = seriesService; + _diskProvider = diskProvider; + _sceneMappingService = sceneMappingService; _logger = logger; } - public LocalEpisode GetEpisodes(string filename, Series series) - { - return GetEpisodes(filename, series, false); - } - public LocalEpisode GetEpisodes(string filename, Series series, bool sceneSource) { var parsedEpisodeInfo = Parser.ParsePath(filename); @@ -54,14 +61,14 @@ namespace NzbDrone.Core.Parser Quality = parsedEpisodeInfo.Quality, Episodes = episodes, Path = filename, - ParsedEpisodeInfo = parsedEpisodeInfo + ParsedEpisodeInfo = parsedEpisodeInfo, + ExistingFile = _diskProvider.IsParent(series.Path, filename) }; } public Series GetSeries(string title) { var searchTitle = title; - var parsedEpisodeInfo = Parser.ParseTitle(title); if (parsedEpisodeInfo != null) @@ -72,34 +79,28 @@ namespace NzbDrone.Core.Parser return _seriesService.FindByTitle(searchTitle); } - public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId) + public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null) { var remoteEpisode = new RemoteEpisode { ParsedEpisodeInfo = parsedEpisodeInfo, }; - var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle); - - if (series == null && tvRageId > 0) - { - series = _seriesService.FindByTvRageId(tvRageId); - //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import - } + var series = searchCriteria == null ? GetSeries(parsedEpisodeInfo, tvRageId) : + GetSeries(parsedEpisodeInfo, tvRageId, searchCriteria); if (series == null) { - _logger.Trace("No matching series {0}", parsedEpisodeInfo.SeriesTitle); return remoteEpisode; } remoteEpisode.Series = series; - remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, true); + remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, true, searchCriteria); return remoteEpisode; } - private List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource) + public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null) { var result = new List<Episode>(); @@ -108,10 +109,10 @@ namespace NzbDrone.Core.Parser if (series.SeriesType == SeriesTypes.Standard) { _logger.Warn("Found daily-style episode for non-daily series: {0}.", series); - return new List<Episode>(); + return null; } - - var episodeInfo = _episodeService.GetEpisode(series.Id, parsedEpisodeInfo.AirDate.Value); + + var episodeInfo = GetDailyEpisode(series, parsedEpisodeInfo.AirDate.Value, searchCriteria); if (episodeInfo != null) { @@ -130,7 +131,16 @@ namespace NzbDrone.Core.Parser if (series.UseSceneNumbering && sceneSource) { - episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber, true); + if (searchCriteria != null) + { + episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SceneSeasonNumber == parsedEpisodeInfo.SeasonNumber && + e.SceneEpisodeNumber == episodeNumber); + } + + if (episodeInfo == null) + { + episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber, true); + } if (episodeInfo != null) { @@ -143,15 +153,22 @@ namespace NzbDrone.Core.Parser } } + if (episodeInfo == null && searchCriteria != null) + { + episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == parsedEpisodeInfo.SeasonNumber && + e.EpisodeNumber == episodeNumber); + } + if (episodeInfo == null) { - episodeInfo = _episodeService.GetEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber); + episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber); } if (episodeInfo != null) { result.Add(episodeInfo); } + else { _logger.Debug("Unable to find {0}", parsedEpisodeInfo); @@ -160,5 +177,68 @@ namespace NzbDrone.Core.Parser return result; } + + private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria) + { + var tvdbId = _sceneMappingService.GetTvDbId(parsedEpisodeInfo.SeriesTitle); + + if (tvdbId.HasValue) + { + if (searchCriteria.Series.TvdbId == tvdbId) + { + return searchCriteria.Series; + } + } + + if (parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle() == searchCriteria.Series.CleanTitle) + { + return searchCriteria.Series; + } + + if (tvRageId == searchCriteria.Series.TvRageId) + { + //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import + return searchCriteria.Series; + } + + return GetSeries(parsedEpisodeInfo, tvRageId); + } + + private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId) + { + var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle); + + if (series == null && tvRageId > 0) + { + //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import + series = _seriesService.FindByTvRageId(tvRageId); + } + + if (series == null) + { + _logger.Trace("No matching series {0}", parsedEpisodeInfo.SeriesTitle); + return null; + } + + return series; + } + + private Episode GetDailyEpisode(Series series, DateTime airDate, SearchCriteriaBase searchCriteria) + { + Episode episodeInfo = null; + + if (searchCriteria != null) + { + episodeInfo = searchCriteria.Episodes.SingleOrDefault( + e => e.AirDate == airDate.ToString(Episode.AIR_DATE_FORMAT)); + } + + if (episodeInfo == null) + { + episodeInfo = _episodeService.FindEpisode(series.Id, airDate); + } + + return episodeInfo; + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Parser/QualityParser.cs b/NzbDrone.Core/Parser/QualityParser.cs index 8cd3077e2..bbfd7ed3b 100644 --- a/NzbDrone.Core/Parser/QualityParser.cs +++ b/NzbDrone.Core/Parser/QualityParser.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; using NLog; using NzbDrone.Common; +using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; @@ -152,33 +150,7 @@ namespace NzbDrone.Core.Parser { try { - switch (Path.GetExtension(name).ToLower()) - { - case ".avi": - case ".xvid": - case ".divx": - case ".wmv": - case ".mp4": - case ".mpg": - case ".mpeg": - case ".mov": - case ".rm": - case ".rmvb": - case ".flv": - case ".dvr-ms": - case ".ogm": - case ".strm": - { - result.Quality = Quality.SDTV; - break; - } - case ".mkv": - case ".ts": - { - result.Quality = Quality.HDTV720p; - break; - } - } + result.Quality = MediaFileExtensions.GetQualityForExtension(Path.GetExtension(name)); } catch (ArgumentException) { diff --git a/NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs b/NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs new file mode 100644 index 000000000..49c20b0b2 --- /dev/null +++ b/NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs @@ -0,0 +1,68 @@ +using NLog.Config; +using NLog; +using NLog.Targets; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Commands.Tracking; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.ProgressMessaging +{ + + public class ProgressMessageTarget : Target, IHandle<ApplicationStartedEvent> + { + private readonly IEventAggregator _eventAggregator; + private readonly ITrackCommands _trackCommands; + private static LoggingRule _rule; + + public ProgressMessageTarget(IEventAggregator eventAggregator, ITrackCommands trackCommands) + { + _eventAggregator = eventAggregator; + _trackCommands = trackCommands; + } + + protected override void Write(LogEventInfo logEvent) + { + var command = GetCurrentCommand(); + + if (IsClientMessage(logEvent, command)) + { + command.SetMessage(logEvent.FormattedMessage); + _eventAggregator.PublishEvent(new CommandUpdatedEvent(command)); + } + } + + + private Command GetCurrentCommand() + { + var commandId = MappedDiagnosticsContext.Get("CommandId"); + + if (string.IsNullOrWhiteSpace(commandId)) + { + return null; + } + + return _trackCommands.GetById(commandId); + } + + private bool IsClientMessage(LogEventInfo logEvent, Command command) + { + if (command == null || !command.SendUpdatesToClient) + { + return false; + } + + return logEvent.Properties.ContainsKey("Status"); + } + + public void Handle(ApplicationStartedEvent message) + { + _rule = new LoggingRule("*", LogLevel.Trace, this); + + LogManager.Configuration.AddTarget("ProgressMessagingLogger", this); + LogManager.Configuration.LoggingRules.Add(_rule); + LogManager.ReconfigExistingLoggers(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs b/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs index da4b4b53f..a30c0c70d 100644 --- a/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs +++ b/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs @@ -1,10 +1,10 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Providers { - public class UpdateXemMappingsCommand : ICommand + public class UpdateXemMappingsCommand : Command { - public int? SeriesId { get; private set; } + public int? SeriesId { get; set; } public UpdateXemMappingsCommand(int? seriesId) { diff --git a/NzbDrone.Core/Providers/XemCommunicationProvider.cs b/NzbDrone.Core/Providers/XemCommunicationProvider.cs index 803b74e65..5c0e07132 100644 --- a/NzbDrone.Core/Providers/XemCommunicationProvider.cs +++ b/NzbDrone.Core/Providers/XemCommunicationProvider.cs @@ -5,6 +5,7 @@ using NLog; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NzbDrone.Common; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Model.Xem; namespace NzbDrone.Core.Providers @@ -20,7 +21,7 @@ namespace NzbDrone.Core.Providers { private readonly IHttpProvider _httpProvider; - private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger _logger = NzbDroneLogger.GetLogger(); private const string XEM_BASE_URL = "http://thexem.de/map/"; diff --git a/NzbDrone.Core/Providers/XemProvider.cs b/NzbDrone.Core/Providers/XemProvider.cs index 931793dfb..e66000afc 100644 --- a/NzbDrone.Core/Providers/XemProvider.cs +++ b/NzbDrone.Core/Providers/XemProvider.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Cache; -using NzbDrone.Common.Messaging; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; @@ -25,7 +28,7 @@ namespace NzbDrone.Core.Providers private readonly ISeriesService _seriesService; private readonly ICached<bool> _cache; - private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger _logger = NzbDroneLogger.GetLogger(); public XemProvider(IEpisodeService episodeService, IXemCommunicationProvider xemCommunicationProvider, @@ -131,8 +134,7 @@ namespace NzbDrone.Core.Providers catch (Exception ex) { - //TODO: We should increase this back to warn when caching is in place - _logger.TraceException("Error updating scene numbering mappings for: " + series, ex); + _logger.ErrorException("Error updating scene numbering mappings for: " + series, ex); } } diff --git a/NzbDrone.Core/Qualities/QualityProfileRepository.cs b/NzbDrone.Core/Qualities/QualityProfileRepository.cs index fcb70f3ff..64e36b70b 100644 --- a/NzbDrone.Core/Qualities/QualityProfileRepository.cs +++ b/NzbDrone.Core/Qualities/QualityProfileRepository.cs @@ -1,5 +1,7 @@ -using NzbDrone.Common.Messaging; -using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Qualities { @@ -10,8 +12,8 @@ namespace NzbDrone.Core.Qualities public class QualityProfileRepository : BasicRepository<QualityProfile>, IQualityProfileRepository { - public QualityProfileRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public QualityProfileRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } } diff --git a/NzbDrone.Core/Qualities/QualityProfileService.cs b/NzbDrone.Core/Qualities/QualityProfileService.cs index fb96cee99..de79e36e9 100644 --- a/NzbDrone.Core/Qualities/QualityProfileService.cs +++ b/NzbDrone.Core/Qualities/QualityProfileService.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; diff --git a/NzbDrone.Core/Qualities/QualitySizeRepository.cs b/NzbDrone.Core/Qualities/QualitySizeRepository.cs index ef4bb4138..84a21355d 100644 --- a/NzbDrone.Core/Qualities/QualitySizeRepository.cs +++ b/NzbDrone.Core/Qualities/QualitySizeRepository.cs @@ -1,7 +1,9 @@ using System; using System.Linq; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Qualities { @@ -12,8 +14,8 @@ namespace NzbDrone.Core.Qualities public class QualitySizeRepository : BasicRepository<QualitySize>, IQualitySizeRepository { - public QualitySizeRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public QualitySizeRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/Qualities/QualitySizeService.cs b/NzbDrone.Core/Qualities/QualitySizeService.cs index d3dce3199..c7719ef8b 100644 --- a/NzbDrone.Core/Qualities/QualitySizeService.cs +++ b/NzbDrone.Core/Qualities/QualitySizeService.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Qualities { diff --git a/NzbDrone.Core/Rest/RestSharpExtensions.cs b/NzbDrone.Core/Rest/RestSharpExtensions.cs index b0c09368c..33b5e681b 100644 --- a/NzbDrone.Core/Rest/RestSharpExtensions.cs +++ b/NzbDrone.Core/Rest/RestSharpExtensions.cs @@ -1,6 +1,7 @@ using System.Net; using NLog; using NzbDrone.Common.EnsureThat; +using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Serializer; using RestSharp; @@ -8,7 +9,7 @@ namespace NzbDrone.Core.Rest { public static class RestSharpExtensions { - private static readonly Logger Logger = LogManager.GetLogger("RestExtensions"); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public static IRestResponse ValidateResponse(this IRestResponse response, IRestClient restClient) { @@ -22,7 +23,7 @@ namespace NzbDrone.Core.Rest Ensure.That(() => response.Request).IsNotNull(); Ensure.That(() => restClient).IsNotNull(); - Logger.Trace("Validating Responses from [{0}] [{1}] status: [{2}] body:[{3}]", response.Request.Method, restClient.BuildUri(response.Request), response.StatusCode, response.Content); + Logger.Trace("Validating Responses from [{0}] [{1}] status: [{2}]", response.Request.Method, restClient.BuildUri(response.Request), response.StatusCode); if (response.ResponseUri == null) { diff --git a/NzbDrone.Core/RootFolders/RootFolderService.cs b/NzbDrone.Core/RootFolders/RootFolderService.cs index 160cd3c9f..9e441d91d 100644 --- a/NzbDrone.Core/RootFolders/RootFolderService.cs +++ b/NzbDrone.Core/RootFolders/RootFolderService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore; using NzbDrone.Core.Tv; @@ -23,12 +24,15 @@ namespace NzbDrone.Core.RootFolders public class RootFolderService : IRootFolderService { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); private readonly IBasicRepository<RootFolder> _rootFolderRepository; private readonly IDiskProvider _diskProvider; private readonly ISeriesRepository _seriesRepository; private readonly IConfigService _configService; + private static readonly HashSet<string> SpecialFolders = new HashSet<string> { "$recycle.bin", "system volume information", "recycler" }; + + public RootFolderService(IBasicRepository<RootFolder> rootFolderRepository, IDiskProvider diskProvider, ISeriesRepository seriesRepository, @@ -118,7 +122,7 @@ namespace NzbDrone.Core.RootFolders if (Path.GetPathRoot(path).Equals(path, StringComparison.InvariantCultureIgnoreCase)) { - var setToRemove = _diskProvider.SpecialFolders; + var setToRemove = SpecialFolders; results.RemoveAll(x => setToRemove.Contains(new DirectoryInfo(x.Path.ToLowerInvariant()).Name)); } diff --git a/NzbDrone.Core/SeriesStats/SeriesStatistics.cs b/NzbDrone.Core/SeriesStats/SeriesStatistics.cs index 24893eb26..170985718 100644 --- a/NzbDrone.Core/SeriesStats/SeriesStatistics.cs +++ b/NzbDrone.Core/SeriesStats/SeriesStatistics.cs @@ -6,7 +6,6 @@ namespace NzbDrone.Core.SeriesStats public class SeriesStatistics : ResultSet { public int SeriesId { get; set; } - public int SeasonCount { get; set; } public string NextAiringString { get; set; } public int EpisodeFileCount { get; set; } public int EpisodeCount { get; set; } diff --git a/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index eece02c35..09244dc06 100644 --- a/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -54,10 +54,9 @@ namespace NzbDrone.Core.SeriesStats { return @"SELECT SeriesId, - SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR Episodes.EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount, - SUM(CASE WHEN Episodes.EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount, - MAX(Episodes.SeasonNumber) as SeasonCount, - MIN(CASE WHEN AirDateUtc < @currentDate THEN NULL ELSE AirDateUtc END) AS NextAiringString + SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount, + SUM(CASE WHEN EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount, + MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString FROM Episodes"; } diff --git a/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs b/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs index f38af963c..8ccc3a2b4 100644 --- a/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs +++ b/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs @@ -1,14 +1,26 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Tv.Commands { - public class RefreshSeriesCommand : ICommand + public class RefreshSeriesCommand : Command { - public int? SeriesId { get; private set; } + public int? SeriesId { get; set; } + + public RefreshSeriesCommand() + { + } public RefreshSeriesCommand(int? seriesId) { SeriesId = seriesId; } + + public override bool SendUpdatesToClient + { + get + { + return true; + } + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Tv/EpisodeRepository.cs b/NzbDrone.Core/Tv/EpisodeRepository.cs index 280dec96e..cd12016ed 100644 --- a/NzbDrone.Core/Tv/EpisodeRepository.cs +++ b/NzbDrone.Core/Tv/EpisodeRepository.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Linq; using Marr.Data.QGen; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Tv @@ -31,8 +32,8 @@ namespace NzbDrone.Core.Tv { private readonly IDatabase _database; - public EpisodeRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public EpisodeRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { _database = database; } diff --git a/NzbDrone.Core/Tv/EpisodeService.cs b/NzbDrone.Core/Tv/EpisodeService.cs index dce584fa4..8d3f3ccd2 100644 --- a/NzbDrone.Core/Tv/EpisodeService.cs +++ b/NzbDrone.Core/Tv/EpisodeService.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv.Events; namespace NzbDrone.Core.Tv @@ -40,7 +42,7 @@ namespace NzbDrone.Core.Tv IHandleAsync<SeriesDeletedEvent> { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger logger = NzbDroneLogger.GetLogger(); private readonly IEpisodeRepository _episodeRepository; private readonly IConfigService _configService; @@ -128,7 +130,7 @@ namespace NzbDrone.Core.Tv var episode = _episodeRepository.Get(episodeId); _episodeRepository.SetMonitoredFlat(episode, monitored); - logger.Info("Monitored flag for Episode:{0} was set to {1}", episodeId, monitored); + logger.Debug("Monitored flag for Episode:{0} was set to {1}", episodeId, monitored); } public void SetEpisodeMonitoredBySeason(int seriesId, int seasonNumber, bool monitored) diff --git a/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/NzbDrone.Core/Tv/RefreshEpisodeService.cs index c62d8a5d2..5c4beb1b0 100644 --- a/NzbDrone.Core/Tv/RefreshEpisodeService.cs +++ b/NzbDrone.Core/Tv/RefreshEpisodeService.cs @@ -2,7 +2,8 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv.Events; namespace NzbDrone.Core.Tv @@ -15,27 +16,24 @@ namespace NzbDrone.Core.Tv public class RefreshEpisodeService : IRefreshEpisodeService { private readonly IEpisodeService _episodeService; - private readonly ISeasonService _seasonService; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; - public RefreshEpisodeService(IEpisodeService episodeService, - ISeasonService seasonService, IMessageAggregator messageAggregator, Logger logger) + public RefreshEpisodeService(IEpisodeService episodeService, IEventAggregator eventAggregator, Logger logger) { _episodeService = episodeService; - _seasonService = seasonService; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _logger = logger; } public void RefreshEpisodeInfo(Series series, IEnumerable<Episode> remoteEpisodes) { - _logger.Info("Starting series info refresh for: {0}", series); + _logger.Info("Starting episode info refresh for: {0}", series); var successCount = 0; var failCount = 0; var existingEpisodes = _episodeService.GetEpisodeBySeries(series.Id); - var seasons = _seasonService.GetSeasonsBySeries(series.Id); + var seasons = series.Seasons; var updateList = new List<Episode>(); var newList = new List<Episode>(); @@ -44,7 +42,7 @@ namespace NzbDrone.Core.Tv { try { - var episodeToUpdate = existingEpisodes.SingleOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); + var episodeToUpdate = existingEpisodes.FirstOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); if (episodeToUpdate != null) { @@ -88,17 +86,17 @@ namespace NzbDrone.Core.Tv if (newList.Any()) { - _messageAggregator.PublishEvent(new EpisodeInfoAddedEvent(newList, series)); + _eventAggregator.PublishEvent(new EpisodeInfoAddedEvent(newList, series)); } if (updateList.Any()) { - _messageAggregator.PublishEvent(new EpisodeInfoUpdatedEvent(updateList)); + _eventAggregator.PublishEvent(new EpisodeInfoUpdatedEvent(updateList)); } if (existingEpisodes.Any()) { - _messageAggregator.PublishEvent(new EpisodeInfoDeletedEvent(updateList)); + _eventAggregator.PublishEvent(new EpisodeInfoDeletedEvent(updateList)); } if (failCount != 0) @@ -114,11 +112,6 @@ namespace NzbDrone.Core.Tv private static bool GetMonitoredStatus(Episode episode, IEnumerable<Season> seasons) { - if (episode.SeasonNumber == 0) - { - return false; - } - if (episode.EpisodeNumber == 0 && episode.SeasonNumber != 1) { return false; @@ -132,7 +125,7 @@ namespace NzbDrone.Core.Tv { var groups = allEpisodes.Where(c => c.AirDateUtc.HasValue) - .GroupBy(e => new { e.SeriesId, e.AirDate }) + .GroupBy(e => new { e.SeasonNumber, e.AirDate }) .Where(g => g.Count() > 1) .ToList(); diff --git a/NzbDrone.Core/Tv/RefreshSeriesService.cs b/NzbDrone.Core/Tv/RefreshSeriesService.cs index bbdb8e472..d6607dda5 100644 --- a/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -1,9 +1,13 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using NLog; -using NzbDrone.Common.Messaging; using NzbDrone.Core.DataAugmentation.DailySeries; +using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Tv.Commands; using NzbDrone.Core.Tv.Events; @@ -16,53 +20,23 @@ namespace NzbDrone.Core.Tv private readonly IProvideSeriesInfo _seriesInfo; private readonly ISeriesService _seriesService; private readonly IRefreshEpisodeService _refreshEpisodeService; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly IDailySeriesService _dailySeriesService; private readonly Logger _logger; - public RefreshSeriesService(IProvideSeriesInfo seriesInfo, ISeriesService seriesService, IRefreshEpisodeService refreshEpisodeService, IMessageAggregator messageAggregator, IDailySeriesService dailySeriesService, Logger logger) + public RefreshSeriesService(IProvideSeriesInfo seriesInfo, ISeriesService seriesService, IRefreshEpisodeService refreshEpisodeService, IEventAggregator eventAggregator, IDailySeriesService dailySeriesService, Logger logger) { _seriesInfo = seriesInfo; _seriesService = seriesService; _refreshEpisodeService = refreshEpisodeService; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _dailySeriesService = dailySeriesService; _logger = logger; } - - public void Execute(RefreshSeriesCommand message) - { - if (message.SeriesId.HasValue) - { - var series = _seriesService.GetSeries(message.SeriesId.Value); - RefreshSeriesInfo(series); - } - else - { - var allSeries = _seriesService.GetAllSeries().OrderBy(c => c.LastInfoSync).ToList(); - - foreach (var series in allSeries) - { - try - { - RefreshSeriesInfo(series); - } - catch (Exception e) - { - _logger.ErrorException("Couldn't refresh info for {0}".Inject(series), e); - } - } - } - } - - public void HandleAsync(SeriesAddedEvent message) - { - RefreshSeriesInfo(message.Series); - } - private void RefreshSeriesInfo(Series series) { + _logger.ProgressInfo("Updating Info for {0}", series.Title); var tuple = _seriesInfo.GetSeriesInfo(series.TvdbId); var seriesInfo = tuple.Item1; @@ -93,10 +67,65 @@ namespace NzbDrone.Core.Tv _logger.WarnException("Couldn't update series path for " + series.Path, e); } + series.Seasons = UpdateSeasons(series, seriesInfo); + _seriesService.UpdateSeries(series); _refreshEpisodeService.RefreshEpisodeInfo(series, tuple.Item2); - _messageAggregator.PublishEvent(new SeriesUpdatedEvent(series)); + _logger.Debug("Finished series refresh for {0}", series.Title); + _eventAggregator.PublishEvent(new SeriesUpdatedEvent(series)); + } + + private List<Season> UpdateSeasons(Series series, Series seriesInfo) + { + foreach (var season in seriesInfo.Seasons) + { + var existingSeason = series.Seasons.SingleOrDefault(s => s.SeasonNumber == season.SeasonNumber); + + //Todo: Should this should use the previous season's monitored state? + if (existingSeason == null) + { + _logger.Trace("New season ({0}) for series: [{1}] {2}, setting monitored to true", season.SeasonNumber, series.TvdbId, series.Title); + season.Monitored = true; + } + + else + { + season.Monitored = existingSeason.Monitored; + } + } + + return seriesInfo.Seasons; + } + + public void Execute(RefreshSeriesCommand message) + { + if (message.SeriesId.HasValue) + { + var series = _seriesService.GetSeries(message.SeriesId.Value); + RefreshSeriesInfo(series); + } + else + { + var allSeries = _seriesService.GetAllSeries().OrderBy(c => c.LastInfoSync).ToList(); + + foreach (var series in allSeries) + { + try + { + RefreshSeriesInfo(series); + } + catch (Exception e) + { + _logger.ErrorException("Couldn't refresh info for {0}".Inject(series), e); + } + } + } + } + + public void HandleAsync(SeriesAddedEvent message) + { + RefreshSeriesInfo(message.Series); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Tv/Season.cs b/NzbDrone.Core/Tv/Season.cs index be36fcc86..5073ea2b4 100644 --- a/NzbDrone.Core/Tv/Season.cs +++ b/NzbDrone.Core/Tv/Season.cs @@ -1,15 +1,11 @@ using System; -using System.Collections.Generic; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Tv { - public class Season : ModelBase + public class Season : IEmbeddedDocument { - public int SeriesId { get; set; } public int SeasonNumber { get; set; } public Boolean Monitored { get; set; } - - public List<Episode> Episodes { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Tv/SeasonRepository.cs b/NzbDrone.Core/Tv/SeasonRepository.cs index f580c8d94..470c2e5ca 100644 --- a/NzbDrone.Core/Tv/SeasonRepository.cs +++ b/NzbDrone.Core/Tv/SeasonRepository.cs @@ -1,49 +1,27 @@ using System.Collections.Generic; using System.Linq; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Tv { - public interface ISeasonRepository : IBasicRepository<Season> + public interface ISeasonRepository : IBasicRepository<Series> { - IList<int> GetSeasonNumbers(int seriesId); - Season Get(int seriesId, int seasonNumber); - bool IsMonitored(int seriesId, int seasonNumber); List<Season> GetSeasonBySeries(int seriesId); } - public class SeasonRepository : BasicRepository<Season>, ISeasonRepository + public class SeasonRepository : BasicRepository<Series>, ISeasonRepository { - - public SeasonRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) - { - } - - public IList<int> GetSeasonNumbers(int seriesId) - { - return Query.Where(c => c.SeriesId == seriesId).Select(c => c.SeasonNumber).ToList(); - } - - public Season Get(int seriesId, int seasonNumber) - { - return Query.Single(s => s.SeriesId == seriesId && s.SeasonNumber == seasonNumber); - } - - public bool IsMonitored(int seriesId, int seasonNumber) + public SeasonRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { - var season = Query.SingleOrDefault(s => s.SeriesId == seriesId && s.SeasonNumber == seasonNumber); - - if (season == null) return true; - - return season.Monitored; } public List<Season> GetSeasonBySeries(int seriesId) { - return Query.Where(s => s.SeriesId == seriesId); + return Query.Single(s => s.Id == seriesId).Seasons; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Tv/SeasonService.cs b/NzbDrone.Core/Tv/SeasonService.cs deleted file mode 100644 index 3432f389d..000000000 Binary files a/NzbDrone.Core/Tv/SeasonService.cs and /dev/null differ diff --git a/NzbDrone.Core/Tv/Series.cs b/NzbDrone.Core/Tv/Series.cs index 55a6adb73..107162aaf 100644 --- a/NzbDrone.Core/Tv/Series.cs +++ b/NzbDrone.Core/Tv/Series.cs @@ -34,12 +34,15 @@ namespace NzbDrone.Core.Tv public bool UseSceneNumbering { get; set; } public string TitleSlug { get; set; } public string Path { get; set; } + public int Year { get; set; } public string RootFolderPath { get; set; } public DateTime? FirstAired { get; set; } public LazyLoaded<QualityProfile> QualityProfile { get; set; } + public List<Season> Seasons { get; set; } + public override string ToString() { return string.Format("[{0}][{1}]", TvdbId, Title.NullSafe()); diff --git a/NzbDrone.Core/Tv/SeriesRepository.cs b/NzbDrone.Core/Tv/SeriesRepository.cs index 83fac8844..161ebbac2 100644 --- a/NzbDrone.Core/Tv/SeriesRepository.cs +++ b/NzbDrone.Core/Tv/SeriesRepository.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; + namespace NzbDrone.Core.Tv { @@ -20,8 +22,8 @@ namespace NzbDrone.Core.Tv public class SeriesRepository : BasicRepository<Series>, ISeriesRepository { - public SeriesRepository(IDatabase database, IMessageAggregator messageAggregator) - : base(database, messageAggregator) + public SeriesRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) { } diff --git a/NzbDrone.Core/Tv/SeriesService.cs b/NzbDrone.Core/Tv/SeriesService.cs index 7c6dba091..9e8d7ba99 100644 --- a/NzbDrone.Core/Tv/SeriesService.cs +++ b/NzbDrone.Core/Tv/SeriesService.cs @@ -4,9 +4,10 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Common.EnsureThat; -using NzbDrone.Common.Messaging; using NzbDrone.Core.Configuration; using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Organizer; using NzbDrone.Core.Tv.Events; @@ -14,10 +15,8 @@ namespace NzbDrone.Core.Tv { public interface ISeriesService { - bool IsMonitored(int id); Series GetSeries(int seriesId); Series AddSeries(Series newSeries); - void UpdateFromSeriesEditor(IList<Series> editedSeries); Series FindByTvdbId(int tvdbId); Series FindByTvRageId(int tvRageId); Series FindByTitle(string title); @@ -26,37 +25,32 @@ namespace NzbDrone.Core.Tv List<Series> GetAllSeries(); Series UpdateSeries(Series series); bool SeriesPathExists(string folder); - List<Series> GetSeriesInList(IEnumerable<int> seriesIds); - Series FindBySlug(string slug); - List<String> GetSeriesPaths(); } public class SeriesService : ISeriesService { private readonly ISeriesRepository _seriesRepository; private readonly IConfigService _configService; - private readonly IMessageAggregator _messageAggregator; + private readonly IEventAggregator _eventAggregator; private readonly ISceneMappingService _sceneMappingService; + private readonly IEpisodeService _episodeService; private readonly Logger _logger; public SeriesService(ISeriesRepository seriesRepository, IConfigService configServiceService, - IMessageAggregator messageAggregator, + IEventAggregator eventAggregator, ISceneMappingService sceneMappingService, + IEpisodeService episodeService, Logger logger) { _seriesRepository = seriesRepository; _configService = configServiceService; - _messageAggregator = messageAggregator; + _eventAggregator = eventAggregator; _sceneMappingService = sceneMappingService; + _episodeService = episodeService; _logger = logger; } - public bool IsMonitored(int id) - { - return _seriesRepository.Get(id).Monitored; - } - public Series GetSeries(int seriesId) { return _seriesRepository.Get(seriesId); @@ -80,29 +74,11 @@ namespace NzbDrone.Core.Tv newSeries.SeasonFolder = _configService.UseSeasonFolder; _seriesRepository.Insert(newSeries); - _messageAggregator.PublishEvent(new SeriesAddedEvent(newSeries)); + _eventAggregator.PublishEvent(new SeriesAddedEvent(newSeries)); return newSeries; } - public void UpdateFromSeriesEditor(IList<Series> editedSeries) - { - var allSeries = _seriesRepository.All(); - - foreach (var series in allSeries) - { - //Only update parameters that can be changed in MassEdit - var edited = editedSeries.Single(s => s.Id == series.Id); - series.QualityProfileId = edited.QualityProfileId; - series.Monitored = edited.Monitored; - series.SeasonFolder = edited.SeasonFolder; - series.Path = edited.Path; - - _seriesRepository.Update(series); - } - - } - public Series FindByTvdbId(int tvdbId) { return _seriesRepository.FindByTvdbId(tvdbId); @@ -113,17 +89,6 @@ namespace NzbDrone.Core.Tv return _seriesRepository.FindByTvRageId(tvRageId); } - public Series FindBySlug(string slug) - { - var series = _seriesRepository.FindBySlug(slug); - return series; - } - - public List<string> GetSeriesPaths() - { - return _seriesRepository.GetSeriesPaths(); - } - public Series FindByTitle(string title) { var tvdbId = _sceneMappingService.GetTvDbId(title); @@ -145,7 +110,7 @@ namespace NzbDrone.Core.Tv { var series = _seriesRepository.Get(seriesId); _seriesRepository.Delete(seriesId); - _messageAggregator.PublishEvent(new SeriesDeletedEvent(series, deleteFiles)); + _eventAggregator.PublishEvent(new SeriesDeletedEvent(series, deleteFiles)); } public List<Series> GetAllSeries() @@ -155,6 +120,18 @@ namespace NzbDrone.Core.Tv public Series UpdateSeries(Series series) { + var storedSeries = GetSeries(series.Id); + + foreach (var season in series.Seasons) + { + var storedSeason = storedSeries.Seasons.SingleOrDefault(s => s.SeasonNumber == season.SeasonNumber); + + if (storedSeason != null && season.Monitored != storedSeason.Monitored) + { + _episodeService.SetEpisodeMonitoredBySeason(series.Id, season.SeasonNumber, season.Monitored); + } + } + return _seriesRepository.Update(series); } @@ -162,10 +139,5 @@ namespace NzbDrone.Core.Tv { return _seriesRepository.SeriesPathExists(folder); } - - public List<Series> GetSeriesInList(IEnumerable<int> seriesIds) - { - return _seriesRepository.Get(seriesIds).ToList(); - } } } diff --git a/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs b/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs index bbbf6a1f3..d961c8e80 100644 --- a/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs +++ b/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs @@ -1,8 +1,15 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Update.Commands { - public class ApplicationUpdateCommand : ICommand + public class ApplicationUpdateCommand : Command { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Update/InstallUpdateService.cs b/NzbDrone.Core/Update/InstallUpdateService.cs index d204bcab3..fd74f199c 100644 --- a/NzbDrone.Core/Update/InstallUpdateService.cs +++ b/NzbDrone.Core/Update/InstallUpdateService.cs @@ -3,8 +3,10 @@ using System.IO; using NLog; using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Update.Commands; +using NzbDrone.Core.Instrumentation; namespace NzbDrone.Core.Update { @@ -36,6 +38,7 @@ namespace NzbDrone.Core.Update public void Execute(ApplicationUpdateCommand message) { + _logger.ProgressDebug("Checking for updates"); var latestAvailable = _checkUpdateService.AvailableUpdate(); if (latestAvailable != null) @@ -58,11 +61,11 @@ namespace NzbDrone.Core.Update _diskProvider.DeleteFolder(updateSandboxFolder, true); } - _logger.Info("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination); + _logger.ProgressInfo("Downloading Updated {0} [{1}]", updatePackage.Version, updatePackage.Branch); + _logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination); _httpProvider.DownloadFile(updatePackage.Url, packageDestination); - _logger.Info("Download completed for update package from [{0}]", updatePackage.FileName); - _logger.Info("Extracting Update package"); + _logger.ProgressInfo("Extracting Update package"); _archiveService.Extract(packageDestination, updateSandboxFolder); _logger.Info("Update package extracted successfully"); @@ -72,6 +75,8 @@ namespace NzbDrone.Core.Update _logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath()); + _logger.ProgressInfo("NzbDrone will restart shortly."); + _processProvider.Start(_appFolderInfo.GetUpdateClientExePath(), _processProvider.GetCurrentProcess().Id.ToString()); } catch (Exception ex) diff --git a/NzbDrone.Core/Update/UpdateCheckService.cs b/NzbDrone.Core/Update/UpdateCheckService.cs index 313f1ef58..32cb64be2 100644 --- a/NzbDrone.Core/Update/UpdateCheckService.cs +++ b/NzbDrone.Core/Update/UpdateCheckService.cs @@ -1,5 +1,7 @@ using NLog; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Instrumentation; namespace NzbDrone.Core.Update { @@ -8,28 +10,30 @@ namespace NzbDrone.Core.Update UpdatePackage AvailableUpdate(); } - public class CheckUpdateService : ICheckUpdateService { private readonly IUpdatePackageProvider _updatePackageProvider; + private readonly IConfigFileProvider _configFileProvider; private readonly Logger _logger; - public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, Logger logger) + public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, IConfigFileProvider configFileProvider, Logger logger) { _updatePackageProvider = updatePackageProvider; + _configFileProvider = configFileProvider; _logger = logger; } public UpdatePackage AvailableUpdate() { - var latestAvailable = _updatePackageProvider.GetLatestUpdate(); + if (OsInfo.IsLinux) return null; + + var latestAvailable = _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version); if (latestAvailable == null) { - _logger.Debug("No update available."); - return null; + _logger.ProgressDebug("No update available."); } return latestAvailable; diff --git a/NzbDrone.Core/Update/UpdatePackage.cs b/NzbDrone.Core/Update/UpdatePackage.cs index ce038ac99..70ca40b87 100644 --- a/NzbDrone.Core/Update/UpdatePackage.cs +++ b/NzbDrone.Core/Update/UpdatePackage.cs @@ -1,13 +1,14 @@ using System; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace NzbDrone.Core.Update { public class UpdatePackage { - public String Id { get; set; } + public string Id { get; set; } - [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))] + [JsonConverter(typeof(VersionConverter))] public Version Version { get; set; } public String Branch { get; set; } diff --git a/NzbDrone.Core/Update/UpdatePackageAvailable.cs b/NzbDrone.Core/Update/UpdatePackageAvailable.cs index 1d76e22d7..c6b99b446 100644 --- a/NzbDrone.Core/Update/UpdatePackageAvailable.cs +++ b/NzbDrone.Core/Update/UpdatePackageAvailable.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace NzbDrone.Core.Update { diff --git a/NzbDrone.Core/Update/UpdatePackageProvider.cs b/NzbDrone.Core/Update/UpdatePackageProvider.cs index e045a89fd..c6b4b64c0 100644 --- a/NzbDrone.Core/Update/UpdatePackageProvider.cs +++ b/NzbDrone.Core/Update/UpdatePackageProvider.cs @@ -1,37 +1,27 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using NLog; using NzbDrone.Common; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Core.Configuration; +using RestSharp; +using NzbDrone.Core.Rest; namespace NzbDrone.Core.Update { public interface IUpdatePackageProvider { - UpdatePackage GetLatestUpdate(); + UpdatePackage GetLatestUpdate(string branch, Version currentVersion); } public class UpdatePackageProvider : IUpdatePackageProvider { - private readonly IConfigFileProvider _configFileProvider; - private readonly IHttpProvider _httpProvider; - private readonly Logger _logger; - - public UpdatePackageProvider(IConfigFileProvider configFileProvider, IHttpProvider httpProvider, Logger logger) + public UpdatePackage GetLatestUpdate(string branch, Version currentVersion) { - _configFileProvider = configFileProvider; - _httpProvider = httpProvider; - _logger = logger; - } + var restClient = new RestClient(Services.RootUrl); - public UpdatePackage GetLatestUpdate() - { - var url = String.Format("{0}/v1/update/{1}?version={2}", Services.RootUrl, _configFileProvider.Branch, BuildInfo.Version); - var update = JsonConvert.DeserializeObject<UpdatePackageAvailable>(_httpProvider.DownloadString(url)); + var request = new RestRequest("/v1/update/{branch}"); + + request.AddParameter("version", currentVersion); + request.AddUrlSegment("branch", branch); + + var update = restClient.ExecuteAndValidate<UpdatePackageAvailable>(request); if (!update.Available) return null; diff --git a/NzbDrone.Core/progressmessaging/CommandUpdatedEvent.cs b/NzbDrone.Core/progressmessaging/CommandUpdatedEvent.cs new file mode 100644 index 000000000..2d8c93269 --- /dev/null +++ b/NzbDrone.Core/progressmessaging/CommandUpdatedEvent.cs @@ -0,0 +1,15 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.ProgressMessaging +{ + public class CommandUpdatedEvent : IEvent + { + public Command Command { get; set; } + + public CommandUpdatedEvent(Command command) + { + Command = command; + } + } +} diff --git a/NzbDrone.Host/Bootstrap.cs b/NzbDrone.Host/Bootstrap.cs index e4b592fd1..fb1d23eec 100644 --- a/NzbDrone.Host/Bootstrap.cs +++ b/NzbDrone.Host/Bootstrap.cs @@ -1,5 +1,4 @@ using System.Reflection; -using NLog; using NzbDrone.Common.Composition; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation; @@ -12,7 +11,7 @@ namespace NzbDrone.Host { public static IContainer Start(StartupArguments args, IUserAlert userAlert) { - var logger = LogManager.GetLogger("AppMain"); + var logger = NzbDroneLogger.GetLogger(); GlobalExceptionHandlers.Register(); IgnoreCertErrorPolicy.Register(); diff --git a/NzbDrone.Host/MainAppContainerBuilder.cs b/NzbDrone.Host/MainAppContainerBuilder.cs index e12fc9153..299de7925 100644 --- a/NzbDrone.Host/MainAppContainerBuilder.cs +++ b/NzbDrone.Host/MainAppContainerBuilder.cs @@ -1,13 +1,11 @@ -using System; -using Nancy.Bootstrapper; +using Nancy.Bootstrapper; using NzbDrone.Api; -using NzbDrone.Api.SignalR; using NzbDrone.Common.Composition; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Datastore; using NzbDrone.Core.Organizer; using NzbDrone.Core.RootFolders; -using NzbDrone.Host.Owin; +using NzbDrone.SignalR; namespace NzbDrone.Host { @@ -19,7 +17,7 @@ namespace NzbDrone.Host } private MainAppContainerBuilder(StartupArguments args) - : base(args, "NzbDrone.Host", "NzbDrone.Common", "NzbDrone.Core", "NzbDrone.Api") + : base(args, "NzbDrone.Host", "NzbDrone.Common", "NzbDrone.Core", "NzbDrone.Api", "NzbDrone.SignalR") { AutoRegisterImplementations<NzbDronePersistentConnection>(); diff --git a/NzbDrone.Host/NLog.config b/NzbDrone.Host/NLog.config deleted file mode 100644 index 65103a066..000000000 --- a/NzbDrone.Host/NLog.config +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - autoReload="true" - internalLogLevel="Info" - throwExceptions="true" - internalLogToConsole="true" - internalLogToConsoleError="true"> - <extensions> - <add assembly="NzbDrone.Core"/> - <add assembly="NzbDrone.Common"/> - </extensions> - <targets> - <target xsi:type="ColoredConsole" name="consoleLogger" layout="[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"/> - <target xsi:type="NLogViewer" name="udpTarget" address="udp://127.0.0.1:20480" includeCallSite="true" includeSourceInfo="true" includeNLogData="true" includeNdc="true"> - <parameter> - <name>Exception</name> - <layout>${exception:format=ToString}</layout> - </parameter> - </target> - <target xsi:type="File" name="rollingFileLogger" fileName="${appLog}" autoFlush="true" keepFileOpen="false" - concurrentWrites="false" concurrentWriteAttemptDelay="50" concurrentWriteAttempts ="10" - archiveAboveSize="1024000" maxArchiveFiles="5" enableFileDelete="true" archiveNumbering ="Rolling" - layout="${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"/> - </targets> - <rules> - <logger name="*" minlevel="Trace" writeTo="consoleLogger"/> - <logger name="*" minlevel="Off" writeTo="udpTarget"/> - <logger name="*" minlevel="Info" writeTo="rollingFileLogger"/> - </rules> -</nlog> \ No newline at end of file diff --git a/NzbDrone.Host/NzbDrone.Host.csproj b/NzbDrone.Host/NzbDrone.Host.csproj index c625626ea..7ecf58a13 100644 --- a/NzbDrone.Host/NzbDrone.Host.csproj +++ b/NzbDrone.Host/NzbDrone.Host.csproj @@ -125,6 +125,7 @@ <Compile Include="Owin\NlogTextWriter.cs" /> <Compile Include="Owin\OwinServiceProvider.cs" /> <Compile Include="Owin\OwinTraceOutputFactory.cs" /> + <Compile Include="Owin\PortInUseException.cs" /> <Compile Include="PlatformValidation.cs" /> <Compile Include="MainAppContainerBuilder.cs" /> <Compile Include="ApplicationModes.cs" /> @@ -141,10 +142,6 @@ </ItemGroup> <ItemGroup> <None Include="app.config" /> - <Content Include="NLog.config"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - <SubType>Designer</SubType> - </Content> <None Include="NLog.xsd"> <SubType>Designer</SubType> </None> @@ -188,6 +185,13 @@ <Project>{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}</Project> <Name>NzbDrone.Core</Name> </ProjectReference> + <ProjectReference Include="..\NzbDrone.SignalR\NzbDrone.SignalR.csproj"> + <Project>{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}</Project> + <Name>NzbDrone.SignalR</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Folder Include="SignalR\" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <PropertyGroup> diff --git a/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs b/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs index ba9a436fa..cf4396edf 100644 --- a/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs +++ b/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs @@ -1,32 +1,25 @@ -using System.Collections.Generic; +using System; using Microsoft.AspNet.SignalR; -using NzbDrone.Api.SignalR; using NzbDrone.Common.Composition; +using NzbDrone.SignalR; using Owin; namespace NzbDrone.Host.Owin.MiddleWare { public class SignalRMiddleWare : IOwinMiddleWare { - private readonly IEnumerable<NzbDronePersistentConnection> _persistentConnections; - public int Order { get { return 0; } } - public SignalRMiddleWare(IEnumerable<NzbDronePersistentConnection> persistentConnections, IContainer container) + public SignalRMiddleWare(IContainer container) { - _persistentConnections = persistentConnections; - SignalrDependencyResolver.Register(container); + + GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(300000); } public void Attach(IAppBuilder appBuilder) { - foreach (var nzbDronePersistentConnection in _persistentConnections) - { - var url = string.Format("signalr/{0}", nzbDronePersistentConnection.Resource.Trim('/')); - appBuilder.MapConnection(url, nzbDronePersistentConnection.GetType(), new ConnectionConfiguration { EnableCrossDomain = true }); - } - + appBuilder.MapConnection("signalr", typeof(NzbDronePersistentConnection), new ConnectionConfiguration { EnableCrossDomain = true }); } } } \ No newline at end of file diff --git a/NzbDrone.Host/Owin/NlogTextWriter.cs b/NzbDrone.Host/Owin/NlogTextWriter.cs index d4cd4da7d..d5b4d04aa 100644 --- a/NzbDrone.Host/Owin/NlogTextWriter.cs +++ b/NzbDrone.Host/Owin/NlogTextWriter.cs @@ -1,12 +1,13 @@ using System.IO; using System.Text; using NLog; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Host.Owin { public class NlogTextWriter : TextWriter { - private readonly Logger logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _logger = NzbDroneLogger.GetLogger(); public override Encoding Encoding @@ -17,25 +18,23 @@ namespace NzbDrone.Host.Owin } } - public override void Write(char value) + public override void Write(char[] buffer, int index, int count) { - logger.Trace(value); + Write(buffer); } - public override void Write(char[] buffer) { - logger.Trace(buffer); + Write(new string(buffer)); } public override void Write(string value) { - logger.Trace(value); + _logger.Trace(value); } - public override void Write(char[] buffer, int index, int count) + public override void Write(char value) { - logger.Trace(buffer); + _logger.Trace(value); } - } } \ No newline at end of file diff --git a/NzbDrone.Host/Owin/OwinHostController.cs b/NzbDrone.Host/Owin/OwinHostController.cs index 9968b22bb..b4d8d24ba 100644 --- a/NzbDrone.Host/Owin/OwinHostController.cs +++ b/NzbDrone.Host/Owin/OwinHostController.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; +using System.Reflection; using Microsoft.Owin.Hosting; using NLog; using NzbDrone.Common.EnvironmentInfo; @@ -50,7 +52,26 @@ namespace NzbDrone.Host.Owin _logger.Info("starting server on {0}", _urlAclAdapter.UrlAcl); - _host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp); + try + { + _host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp); + } + catch (TargetInvocationException ex) + { + if (ex.InnerException == null) + { + throw; + } + + if (ex.InnerException is HttpListenerException) + { + throw new PortInUseException("Port {0} is already in use, please ensure NzbDrone is not already running.", + ex, + _configFileProvider.Port); + } + + throw ex.InnerException; + } } private void BuildApp(IAppBuilder appBuilder) diff --git a/NzbDrone.Host/Owin/PortInUseException.cs b/NzbDrone.Host/Owin/PortInUseException.cs new file mode 100644 index 000000000..5c6d7a542 --- /dev/null +++ b/NzbDrone.Host/Owin/PortInUseException.cs @@ -0,0 +1,12 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Host.Owin +{ + public class PortInUseException : NzbDroneException + { + public PortInUseException(string message, Exception innerException, params object[] args) : base(message, innerException, args) + { + } + } +} diff --git a/NzbDrone.Host/PlatformValidation.cs b/NzbDrone.Host/PlatformValidation.cs index 978a579ae..703918aca 100644 --- a/NzbDrone.Host/PlatformValidation.cs +++ b/NzbDrone.Host/PlatformValidation.cs @@ -3,12 +3,13 @@ using System.Diagnostics; using System.Reflection; using NLog; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Instrumentation; namespace NzbDrone.Host { public static class PlatformValidation { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public static bool IsValidate(IUserAlert userAlert) { diff --git a/NzbDrone.Integration.Test/Client/ClientBase.cs b/NzbDrone.Integration.Test/Client/ClientBase.cs index 05ca16711..20e35117f 100644 --- a/NzbDrone.Integration.Test/Client/ClientBase.cs +++ b/NzbDrone.Integration.Test/Client/ClientBase.cs @@ -2,6 +2,7 @@ using System.Net; using FluentAssertions; using NLog; +using NzbDrone.Api; using NzbDrone.Api.REST; using NzbDrone.Common.Serializer; using RestSharp; @@ -35,6 +36,17 @@ namespace NzbDrone.Integration.Test.Client return Get<List<TResource>>(request); } + public PagingResource<TResource> GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir) + { + var request = BuildRequest(); + request.AddParameter("page", pageNumber); + request.AddParameter("pageSize", pageSize); + request.AddParameter("sortKey", sortKey); + request.AddParameter("sortDir", sortDir); + return Get<PagingResource<TResource>>(request); + + } + public TResource Post(TResource body) { var request = BuildRequest(); @@ -74,7 +86,7 @@ namespace NzbDrone.Integration.Test.Client return Post<List<dynamic>>(request, HttpStatusCode.BadRequest); } - protected RestRequest BuildRequest(string command = "") + public RestRequest BuildRequest(string command = "") { return new RestRequest(_resource + "/" + command.Trim('/')) { @@ -82,7 +94,7 @@ namespace NzbDrone.Integration.Test.Client }; } - protected T Get<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.OK) where T : class, new() + public T Get<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.OK) where T : class, new() { request.Method = Method.GET; return Execute<T>(request, statusCode); diff --git a/NzbDrone.Integration.Test/Client/EpisodeClient.cs b/NzbDrone.Integration.Test/Client/EpisodeClient.cs index 675c38bd8..8cc89af4e 100644 --- a/NzbDrone.Integration.Test/Client/EpisodeClient.cs +++ b/NzbDrone.Integration.Test/Client/EpisodeClient.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using System.Net; using NzbDrone.Api.Episodes; -using NzbDrone.Api.Series; using RestSharp; namespace NzbDrone.Integration.Test.Client diff --git a/NzbDrone.Integration.Test/Client/SeasonClient.cs b/NzbDrone.Integration.Test/Client/SeasonClient.cs deleted file mode 100644 index af0572a70..000000000 --- a/NzbDrone.Integration.Test/Client/SeasonClient.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using NzbDrone.Api.Episodes; -using NzbDrone.Api.Seasons; -using RestSharp; - -namespace NzbDrone.Integration.Test.Client -{ - public class SeasonClient : ClientBase<SeasonResource> - { - public SeasonClient(IRestClient restClient) - : base(restClient) - { - } - - public List<SeasonResource> GetSeasonsInSeries(int seriesId) - { - var request = BuildRequest("?seriesId=" + seriesId.ToString()); - return Get<List<SeasonResource>>(request); - } - } -} diff --git a/NzbDrone.Integration.Test/CommandIntegerationTests.cs b/NzbDrone.Integration.Test/CommandIntegerationTests.cs index c1e06bac1..1dbe33b67 100644 --- a/NzbDrone.Integration.Test/CommandIntegerationTests.cs +++ b/NzbDrone.Integration.Test/CommandIntegerationTests.cs @@ -1,15 +1,40 @@ -using NUnit.Framework; +using System.Net; +using FluentAssertions; +using NUnit.Framework; using NzbDrone.Api.Commands; +using NzbDrone.Common.Serializer; +using RestSharp; namespace NzbDrone.Integration.Test { [TestFixture] + [Ignore] public class CommandIntegrationTest : IntegrationTest { [Test] public void should_be_able_to_run_rss_sync() { - Commands.Post(new CommandResource {Command = "rsssync"}); + var request = new RestRequest("command") + { + RequestFormat = DataFormat.Json, + Method = Method.POST + }; + + request.AddBody(new CommandResource { Name = "rsssync" }); + + var restClient = new RestClient("http://localhost:8989/api"); + var response = restClient.Execute(request); + + if (response.ErrorException != null) + { + throw response.ErrorException; + } + + response.ErrorMessage.Should().BeBlank(); + response.StatusCode.Should().Be(HttpStatusCode.Created); + + var trackedCommand = Json.Deserialize<CommandResource>(response.Content); + trackedCommand.Id.Should().NotBe(0); } } } \ No newline at end of file diff --git a/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs b/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs index f4d7fe8c1..681e510ef 100644 --- a/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs +++ b/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading; +using System.Threading; using FluentAssertions; using NUnit.Framework; using NzbDrone.Api.Series; diff --git a/NzbDrone.Integration.Test/HistoryIntegrationTest.cs b/NzbDrone.Integration.Test/HistoryIntegrationTest.cs new file mode 100644 index 000000000..8c5a6c1be --- /dev/null +++ b/NzbDrone.Integration.Test/HistoryIntegrationTest.cs @@ -0,0 +1,21 @@ +using FluentAssertions; +using NUnit.Framework; + +namespace NzbDrone.Integration.Test +{ + [TestFixture] + public class HistoryIntegrationTest : IntegrationTest + { + [Test] + public void history_should_be_empty() + { + var history = History.GetPaged(1, 15, "date", "desc"); + + history.Records.Count.Should().Be(0); + history.Page.Should().Be(1); + history.PageSize.Should().Be(15); + history.Records.Should().BeEmpty(); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Integration.Test/IntegrationTest.cs b/NzbDrone.Integration.Test/IntegrationTest.cs index b858e4ff3..a0b5b3bcd 100644 --- a/NzbDrone.Integration.Test/IntegrationTest.cs +++ b/NzbDrone.Integration.Test/IntegrationTest.cs @@ -1,10 +1,10 @@ -using System.Runtime.CompilerServices; -using NLog; +using NLog; using NLog.Config; using NLog.Targets; using NUnit.Framework; using NzbDrone.Api.Commands; using NzbDrone.Api.Config; +using NzbDrone.Api.History; using NzbDrone.Api.RootFolders; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Integration.Test.Client; @@ -23,9 +23,9 @@ namespace NzbDrone.Integration.Test protected ClientBase<RootFolderResource> RootFolders; protected ClientBase<CommandResource> Commands; protected ReleaseClient Releases; + protected ClientBase<HistoryResource> History; protected IndexerClient Indexers; protected EpisodeClient Episodes; - protected SeasonClient Seasons; protected ClientBase<NamingConfigResource> NamingConfig; private NzbDroneRunner _runner; @@ -59,9 +59,9 @@ namespace NzbDrone.Integration.Test Releases = new ReleaseClient(RestClient); RootFolders = new ClientBase<RootFolderResource>(RestClient); Commands = new ClientBase<CommandResource>(RestClient); + History = new ClientBase<HistoryResource>(RestClient); Indexers = new IndexerClient(RestClient); Episodes = new EpisodeClient(RestClient); - Seasons = new SeasonClient(RestClient); NamingConfig = new ClientBase<NamingConfigResource>(RestClient, "config/naming"); } @@ -72,5 +72,4 @@ namespace NzbDrone.Integration.Test _runner.KillAll(); } } - } diff --git a/NzbDrone.Integration.Test/NamingConfigTests.cs b/NzbDrone.Integration.Test/NamingConfigTests.cs index 573eaf2ed..07592193e 100644 --- a/NzbDrone.Integration.Test/NamingConfigTests.cs +++ b/NzbDrone.Integration.Test/NamingConfigTests.cs @@ -1,6 +1,4 @@ -using System; -using System.IO; -using FluentAssertions; +using FluentAssertions; using NUnit.Framework; namespace NzbDrone.Integration.Test diff --git a/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj index 5762f6877..9bba4d240 100644 --- a/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj +++ b/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj @@ -94,14 +94,13 @@ </ItemGroup> <ItemGroup> <Compile Include="Client\ClientBase.cs" /> - <Compile Include="Client\SeasonClient.cs" /> <Compile Include="Client\EpisodeClient.cs" /> <Compile Include="Client\IndexerClient.cs" /> <Compile Include="Client\ReleaseClient.cs" /> <Compile Include="Client\SeriesClient.cs" /> <Compile Include="CommandIntegerationTests.cs" /> + <Compile Include="HistoryIntegrationTest.cs" /> <Compile Include="NamingConfigTests.cs" /> - <Compile Include="SeasonIntegrationTests.cs" /> <Compile Include="EpisodeIntegrationTests.cs" /> <Compile Include="IndexerIntegrationFixture.cs" /> <Compile Include="IntegrationTestDirectoryInfo.cs" /> diff --git a/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs b/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs index 8af6f77e4..b9a86674e 100644 --- a/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs +++ b/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Integration.Test { releaseResource.Age.Should().BeGreaterOrEqualTo(-1); releaseResource.Title.Should().NotBeBlank(); - releaseResource.NzbUrl.Should().NotBeBlank(); + releaseResource.DownloadUrl.Should().NotBeBlank(); releaseResource.SeriesTitle.Should().NotBeBlank(); //TODO: uncomment these after moving to restsharp for rss //releaseResource.NzbInfoUrl.Should().NotBeBlank(); diff --git a/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs b/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs index 8a888fffa..0b7647fdf 100644 --- a/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs +++ b/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using FluentAssertions; using Microsoft.AspNet.SignalR.Client; using Microsoft.AspNet.SignalR.Client.Transports; diff --git a/NzbDrone.Integration.Test/SeasonIntegrationTests.cs b/NzbDrone.Integration.Test/SeasonIntegrationTests.cs deleted file mode 100644 index 7a2458b4f..000000000 --- a/NzbDrone.Integration.Test/SeasonIntegrationTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Api.Series; -using System.Linq; -using NzbDrone.Test.Common; - -namespace NzbDrone.Integration.Test -{ - [TestFixture] - public class SeasonIntegrationTests : IntegrationTest - { - private SeriesResource GivenSeriesWithEpisodes() - { - var series = Series.Lookup("archer").First(); - - series.QualityProfileId = 1; - series.Path = @"C:\Test\Archer".AsOsAgnostic(); - - series = Series.Post(series); - - while (true) - { - if (Seasons.GetSeasonsInSeries(series.Id).Count > 0) - { - return series; - } - - Thread.Sleep(1000); - } - } - - [Test] - public void should_be_able_to_get_all_seasons_in_series() - { - var series = GivenSeriesWithEpisodes(); - Seasons.GetSeasonsInSeries(series.Id).Count.Should().BeGreaterThan(0); - } - - [Test] - public void should_be_able_to_get_a_single_season() - { - var series = GivenSeriesWithEpisodes(); - var seasons = Seasons.GetSeasonsInSeries(series.Id); - - Seasons.Get(seasons.First().Id).Should().NotBeNull(); - } - - [Test] - public void should_be_able_to_set_monitor_status_via_api() - { - var series = GivenSeriesWithEpisodes(); - var seasons = Seasons.GetSeasonsInSeries(series.Id); - var updatedSeason = seasons.First(); - updatedSeason.Monitored = false; - - Seasons.Put(updatedSeason).Monitored.Should().BeFalse(); - } - } -} diff --git a/NzbDrone.SignalR/BroadcastSignalRMessage.cs b/NzbDrone.SignalR/BroadcastSignalRMessage.cs new file mode 100644 index 000000000..d989affc3 --- /dev/null +++ b/NzbDrone.SignalR/BroadcastSignalRMessage.cs @@ -0,0 +1,14 @@ +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.SignalR +{ + public class BroadcastSignalRMessage : Command + { + public SignalRMessage Body { get; private set; } + + public BroadcastSignalRMessage(SignalRMessage body) + { + Body = body; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/SignalR/NoOpPerformanceCounterManager.cs b/NzbDrone.SignalR/NoOpPerformanceCounterManager.cs similarity index 99% rename from NzbDrone.Api/SignalR/NoOpPerformanceCounterManager.cs rename to NzbDrone.SignalR/NoOpPerformanceCounterManager.cs index a3e3d7c1f..2eec1b004 100644 --- a/NzbDrone.Api/SignalR/NoOpPerformanceCounterManager.cs +++ b/NzbDrone.SignalR/NoOpPerformanceCounterManager.cs @@ -2,7 +2,7 @@ using System.Threading; using Microsoft.AspNet.SignalR.Infrastructure; -namespace NzbDrone.Api.SignalR +namespace NzbDrone.SignalR { public class NoOpPerformanceCounterManager : IPerformanceCounterManager { diff --git a/NzbDrone.SignalR/NzbDrone.SignalR.csproj b/NzbDrone.SignalR/NzbDrone.SignalR.csproj new file mode 100644 index 000000000..859651305 --- /dev/null +++ b/NzbDrone.SignalR/NzbDrone.SignalR.csproj @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>NzbDrone.SignalR</RootNamespace> + <AssemblyName>NzbDrone.SignalR</AssemblyName> + <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> + <RestorePackages>true</RestorePackages> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> + <DebugSymbols>true</DebugSymbols> + <OutputPath>bin\x86\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <DebugType>full</DebugType> + <PlatformTarget>x86</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> + <OutputPath>bin\x86\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <Optimize>true</Optimize> + <DebugType>pdbonly</DebugType> + <PlatformTarget>x86</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.AspNet.SignalR.Core"> + <HintPath>..\packages\Microsoft.AspNet.SignalR.Core.1.1.3\lib\net40\Microsoft.AspNet.SignalR.Core.dll</HintPath> + </Reference> + <Reference Include="Newtonsoft.Json"> + <HintPath>..\packages\Newtonsoft.Json.5.0.6\lib\net40\Newtonsoft.Json.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="Microsoft.CSharp" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs"> + <Link>Properties\SharedAssemblyInfo.cs</Link> + </Compile> + <Compile Include="BroadcastSignalRMessage.cs" /> + <Compile Include="NoOpPerformanceCounterManager.cs" /> + <Compile Include="NzbDronePersistentConnection.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Serializer.cs" /> + <Compile Include="SignalrDependencyResolver.cs" /> + <Compile Include="SignalRMessage.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\NzbDrone.Common\NzbDrone.Common.csproj"> + <Project>{f2be0fdf-6e47-4827-a420-dd4ef82407f8}</Project> + <Name>NzbDrone.Common</Name> + </ProjectReference> + <ProjectReference Include="..\NzbDrone.Core\NzbDrone.Core.csproj"> + <Project>{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}</Project> + <Name>NzbDrone.Core</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/NzbDrone.SignalR/NzbDronePersistentConnection.cs b/NzbDrone.SignalR/NzbDronePersistentConnection.cs new file mode 100644 index 000000000..b9a6f1eed --- /dev/null +++ b/NzbDrone.SignalR/NzbDronePersistentConnection.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNet.SignalR; +using Microsoft.AspNet.SignalR.Infrastructure; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.SignalR +{ + public sealed class NzbDronePersistentConnection : PersistentConnection, IExecute<BroadcastSignalRMessage> + { + private IPersistentConnectionContext Context + { + get + { + return ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); + } + } + + + public void Execute(BroadcastSignalRMessage message) + { + Context.Connection.Broadcast(message.Body); + } + } +} \ No newline at end of file diff --git a/NzbDrone.SignalR/Properties/AssemblyInfo.cs b/NzbDrone.SignalR/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..ffaa67fdd --- /dev/null +++ b/NzbDrone.SignalR/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NzbDrone.SignalR")] +[assembly: Guid("98bd985a-4f23-4201-8ed3-f6f3d7f2a5fe")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NzbDrone.Api/SignalR/Serializer.cs b/NzbDrone.SignalR/Serializer.cs similarity index 95% rename from NzbDrone.Api/SignalR/Serializer.cs rename to NzbDrone.SignalR/Serializer.cs index 4ab2bfc01..e631ef146 100644 --- a/NzbDrone.Api/SignalR/Serializer.cs +++ b/NzbDrone.SignalR/Serializer.cs @@ -3,7 +3,7 @@ using System.IO; using Microsoft.AspNet.SignalR.Json; using NzbDrone.Common.Serializer; -namespace NzbDrone.Api.SignalR +namespace NzbDrone.SignalR { public class Serializer : IJsonSerializer { diff --git a/NzbDrone.SignalR/SignalRMessage.cs b/NzbDrone.SignalR/SignalRMessage.cs new file mode 100644 index 000000000..e8993c286 --- /dev/null +++ b/NzbDrone.SignalR/SignalRMessage.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.SignalR +{ + public class SignalRMessage + { + public object Body { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/SignalR/SignalrDependencyResolver.cs b/NzbDrone.SignalR/SignalrDependencyResolver.cs similarity index 96% rename from NzbDrone.Api/SignalR/SignalrDependencyResolver.cs rename to NzbDrone.SignalR/SignalrDependencyResolver.cs index f95f9ae5a..a3633cbae 100644 --- a/NzbDrone.Api/SignalR/SignalrDependencyResolver.cs +++ b/NzbDrone.SignalR/SignalrDependencyResolver.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Infrastructure; using NzbDrone.Common.Composition; -namespace NzbDrone.Api.SignalR +namespace NzbDrone.SignalR { public class SignalrDependencyResolver : DefaultDependencyResolver { diff --git a/NzbDrone.SignalR/packages.config b/NzbDrone.SignalR/packages.config new file mode 100644 index 000000000..f82b57a29 --- /dev/null +++ b/NzbDrone.SignalR/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Microsoft.AspNet.SignalR.Core" version="1.1.3" targetFramework="net40" /> + <package id="Newtonsoft.Json" version="5.0.6" targetFramework="net40" /> +</packages> \ No newline at end of file diff --git a/NzbDrone.Test.Common/Categories/DiskAccessTestAttribute.cs b/NzbDrone.Test.Common/Categories/DiskAccessTestAttribute.cs new file mode 100644 index 000000000..faccff30d --- /dev/null +++ b/NzbDrone.Test.Common/Categories/DiskAccessTestAttribute.cs @@ -0,0 +1,13 @@ +using NUnit.Framework; + +namespace NzbDrone.Test.Common.Categories +{ + public class DiskAccessTestAttribute : CategoryAttribute + { + public DiskAccessTestAttribute() + : base("DiskAccessTest") + { + + } + } +} \ No newline at end of file diff --git a/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj b/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj index 4e85ec162..dd6ffc0fe 100644 --- a/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj +++ b/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj @@ -76,6 +76,7 @@ <Compile Include="AutoMoq\AutoMoqer.cs" /> <Compile Include="AutoMoq\Unity\AutoMockingBuilderStrategy.cs" /> <Compile Include="AutoMoq\Unity\AutoMockingContainerExtension.cs" /> + <Compile Include="Categories\DiskAccessTestAttribute.cs" /> <Compile Include="ConcurrencyCounter.cs" /> <Compile Include="ExceptionVerification.cs" /> <Compile Include="Categories\IntegrationTestAttribute.cs" /> diff --git a/NzbDrone.Test.Common/NzbDrone.Test.Common.ncrunchproject b/NzbDrone.Test.Common/NzbDrone.Test.Common.ncrunchproject index 8641d3614..1a2228e7f 100644 --- a/NzbDrone.Test.Common/NzbDrone.Test.Common.ncrunchproject +++ b/NzbDrone.Test.Common/NzbDrone.Test.Common.ncrunchproject @@ -1,8 +1,8 @@ <ProjectConfiguration> <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> - <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> + <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,8 +12,11 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration /> - <ProxyProcessPath /> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> + <ProxyProcessPath></ProxyProcessPath> + <UseCPUArchitecture>x86</UseCPUArchitecture> + <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> + <BuildProcessArchitecture>x86</BuildProcessArchitecture> </ProjectConfiguration> \ No newline at end of file diff --git a/NzbDrone.Test.Common/TestBase.cs b/NzbDrone.Test.Common/TestBase.cs index 94521c8d5..82e73ab4d 100644 --- a/NzbDrone.Test.Common/TestBase.cs +++ b/NzbDrone.Test.Common/TestBase.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading; using FluentAssertions; using Moq; using NLog; @@ -8,6 +9,8 @@ using NzbDrone.Common; using NzbDrone.Common.Cache; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Messaging; +using NzbDrone.Core.Messaging; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Test.Common.AutoMoq; namespace NzbDrone.Test.Common @@ -42,6 +45,9 @@ namespace NzbDrone.Test.Common public abstract class TestBase : LoggingTest { + + private static readonly Random _random = new Random(); + private AutoMoqer _mocker; protected AutoMoqer Mocker { @@ -58,6 +64,15 @@ namespace NzbDrone.Test.Common protected Mock<RestProvider> MockedRestProvider { get; private set; } + protected int RandomNumber + { + get + { + Thread.Sleep(1); + return _random.Next(0, int.MaxValue); + } + } + private string VirtualPath { get @@ -143,6 +158,11 @@ namespace NzbDrone.Test.Common return Path.Combine(SandboxFolder, fileName); } + protected string GetTestFilePath() + { + return GetTestFilePath(Path.GetRandomFileName()); + } + protected string SandboxFolder { get @@ -160,12 +180,12 @@ namespace NzbDrone.Test.Common protected void VerifyEventPublished<TEvent>(Times times) where TEvent : class, IEvent { - Mocker.GetMock<IMessageAggregator>().Verify(c => c.PublishEvent(It.IsAny<TEvent>()), times); + Mocker.GetMock<IEventAggregator>().Verify(c => c.PublishEvent(It.IsAny<TEvent>()), times); } protected void VerifyEventNotPublished<TEvent>() where TEvent : class, IEvent { - Mocker.GetMock<IMessageAggregator>().Verify(c => c.PublishEvent(It.IsAny<TEvent>()), Times.Never()); + Mocker.GetMock<IEventAggregator>().Verify(c => c.PublishEvent(It.IsAny<TEvent>()), Times.Never()); } } } diff --git a/NzbDrone.Test.Dummy/NzbDrone.Test.Dummy.ncrunchproject b/NzbDrone.Test.Dummy/NzbDrone.Test.Dummy.ncrunchproject index 8641d3614..1a2228e7f 100644 --- a/NzbDrone.Test.Dummy/NzbDrone.Test.Dummy.ncrunchproject +++ b/NzbDrone.Test.Dummy/NzbDrone.Test.Dummy.ncrunchproject @@ -1,8 +1,8 @@ <ProjectConfiguration> <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> - <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> + <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,8 +12,11 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration /> - <ProxyProcessPath /> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> + <ProxyProcessPath></ProxyProcessPath> + <UseCPUArchitecture>x86</UseCPUArchitecture> + <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> + <BuildProcessArchitecture>x86</BuildProcessArchitecture> </ProjectConfiguration> \ No newline at end of file diff --git a/NzbDrone.Update.Test/NzbDrone.Update.Test.ncrunchproject b/NzbDrone.Update.Test/NzbDrone.Update.Test.ncrunchproject index 514d01a85..1a2228e7f 100644 --- a/NzbDrone.Update.Test/NzbDrone.Update.Test.ncrunchproject +++ b/NzbDrone.Update.Test/NzbDrone.Update.Test.ncrunchproject @@ -2,7 +2,7 @@ <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,11 +12,11 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration></UseBuildConfiguration> - <UseBuildPlatform /> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> <ProxyProcessPath></ProxyProcessPath> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <UseCPUArchitecture>x86</UseCPUArchitecture> <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> <BuildProcessArchitecture>x86</BuildProcessArchitecture> </ProjectConfiguration> \ No newline at end of file diff --git a/NzbDrone.Update/NLog.config b/NzbDrone.Update/NLog.config deleted file mode 100644 index 8398980c9..000000000 --- a/NzbDrone.Update/NLog.config +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <extensions> - <add assembly="NzbDrone.Common"/> - </extensions> - <targets> - <target xsi:type="ColoredConsole" name="consoleLogger" layout="[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"/> - <target xsi:type="File" name="fileLogger" fileName="${updateLog}" autoFlush="true" keepFileOpen="false" - concurrentWrites="false" concurrentWriteAttemptDelay="50" concurrentWriteAttempts ="10" - layout="${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"/> - </targets> - <rules> - <logger name="*" minlevel="Debug" writeTo="consoleLogger"/> - <logger name="*" minlevel="Trace" writeTo="fileLogger"/> - </rules> -</nlog> \ No newline at end of file diff --git a/NzbDrone.Update/NzbDrone.Update.csproj b/NzbDrone.Update/NzbDrone.Update.csproj index c2d12c5c4..a1d42c2e0 100644 --- a/NzbDrone.Update/NzbDrone.Update.csproj +++ b/NzbDrone.Update/NzbDrone.Update.csproj @@ -63,10 +63,6 @@ </ItemGroup> <ItemGroup> <None Include="app.config" /> - <Content Include="NLog.config"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - <SubType>Designer</SubType> - </Content> <None Include="NLog.xsd"> <SubType>Designer</SubType> </None> diff --git a/NzbDrone.Update/NzbDrone.Update.ncrunchproject b/NzbDrone.Update/NzbDrone.Update.ncrunchproject index 8641d3614..1a2228e7f 100644 --- a/NzbDrone.Update/NzbDrone.Update.ncrunchproject +++ b/NzbDrone.Update/NzbDrone.Update.ncrunchproject @@ -1,8 +1,8 @@ <ProjectConfiguration> <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> - <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> + <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,8 +12,11 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration /> - <ProxyProcessPath /> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> + <ProxyProcessPath></ProxyProcessPath> + <UseCPUArchitecture>x86</UseCPUArchitecture> + <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> + <BuildProcessArchitecture>x86</BuildProcessArchitecture> </ProjectConfiguration> \ No newline at end of file diff --git a/NzbDrone.Update/UpdateApp.cs b/NzbDrone.Update/UpdateApp.cs index 167499373..3a882cd74 100644 --- a/NzbDrone.Update/UpdateApp.cs +++ b/NzbDrone.Update/UpdateApp.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Update private readonly IProcessProvider _processProvider; private static IContainer _container; - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger logger = NzbDroneLogger.GetLogger(); public UpdateApp(IInstallUpdateService installUpdateService, IProcessProvider processProvider) { @@ -28,14 +28,16 @@ namespace NzbDrone.Update { try { + var startupArgument = new StartupArguments(args); + LogTargets.Register(startupArgument, true, true); + Console.WriteLine("Starting NzbDrone Update Client"); IgnoreCertErrorPolicy.Register(); GlobalExceptionHandlers.Register(); - new LogglyTarget().Register(LogLevel.Trace); - _container = UpdateContainerBuilder.Build(new StartupArguments(args)); + _container = UpdateContainerBuilder.Build(startupArgument); logger.Info("Updating NzbDrone to version {0}", BuildInfo.Version); _container.Resolve<UpdateApp>().Start(args); diff --git a/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs b/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs index 78c3b8c73..91552105c 100644 --- a/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs +++ b/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs @@ -31,7 +31,6 @@ namespace NzbDrone.Update.UpdateEngine public void Restore(string target) { - //TODO:this should ignore single file failures. _logger.Info("Attempting to rollback upgrade"); _diskProvider.CopyFolder(_appFolderInfo.GetUpdateBackUpFolder(), target); } diff --git a/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs index ffafff691..0323360cc 100644 --- a/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs +++ b/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs @@ -64,6 +64,7 @@ namespace NzbDrone.Update.UpdateEngine try { + _diskProvider.EmptyFolder(installationFolder); _diskProvider.CopyFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder); } catch (Exception e) diff --git a/NzbDrone.Wix/configuration.xml b/NzbDrone.Wix/configuration.xml new file mode 100644 index 000000000..fdf4f44bf --- /dev/null +++ b/NzbDrone.Wix/configuration.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<configurations lcid_type="UserExe" show_language_selector="False" language_selector_title="" language_selector_ok="OK" language_selector_cancel="Cancel" configuration_no_match_message="" ui_level="basic" fileversion="" productversion="" log_enabled="True" log_file="#TEMPPATH\dotNetInstallerLog.txt"> + <schema version="2.2.824.0" generator="dotNetInstaller InstallerEditor" /> + <configuration dialog_caption="SyntikX Installer" dialog_message="Following components need to be installed" dialog_message_uninstall="" dialog_bitmap="#APPPATH\banner.bmp" skip_caption="Skip" install_caption="Install" uninstall_caption="Uninstall" cancel_caption="Close" status_installed=" (Already Installed)" status_notinstalled="" failed_exec_command_continue="Failed to install %s." installation_completed="SyntikX installed successfully!" uninstallation_completed="SyntikX uninstalled successfully!" installation_none="SyntikX is not compatible with your machine. Please contact support for more information." uninstallation_none="SyntikX is not installed!" installing_component_wait="Installing %s. Wait, this operation could take some time ..." uninstalling_component_wait="Uninstalling %s. Wait, this operation could take some time ..." reboot_required="To continue the installation you must restart your computer. Restart now?" must_reboot_required="False" dialog_otherinfo_caption="" dialog_otherinfo_link="" complete_command="" complete_command_silent="" complete_command_basic="" wait_for_complete_command="True" prompt_for_optional_components="False" auto_close_if_installed="False" auto_close_on_error="False" reload_on_error="True" dialog_show_installed="True" dialog_show_uninstalled="False" dialog_show_required="True" cab_dialog_message="%s" cab_cancelled_message="" cab_dialog_caption="" cab_path="#TEMPPATH\#GUID" cab_path_autodelete="True" dialog_default_button="install" dialog_position="" dialog_components_list_position="" dialog_message_position="" dialog_bitmap_position="" dialog_otherinfo_link_position="" dialog_osinfo_position="" dialog_install_button_position="" dialog_cancel_button_position="" dialog_skip_button_position="" auto_start="True" auto_continue_on_reboot="True" reboot_cmd="" show_progress_dialog="False" show_cab_dialog="True" disable_wow64_fs_redirection="False" administrator_required="False" administrator_required_message="SyntikX installation requires administration rights." type="install" lcid_filter="" language_id="" language="" os_filter="" os_filter_min="" os_filter_max="" processor_architecture_filter="" supports_install="True" supports_uninstall="False"> + <component executable="#TEMPPATH\syntik_bootstrap\WindowsXP-KB936929-SP3-x86-ENU.exe" executable_silent="" executable_basic="" install_directory="" responsefile_source="" responsefile_target="" responsefile_format="none" uninstall_executable="" uninstall_executable_silent="" uninstall_executable_basic="" uninstall_responsefile_source="" uninstall_responsefile_target="" returncodes_success="" returncodes_reboot="" exeparameters="" exeparameters_basic="" exeparameters_silent="" uninstall_exeparameters="" uninstall_exeparameters_basic="" uninstall_exeparameters_silent="" disable_wow64_fs_redirection="False" id="XPSP3" display_name="Windows XP Service Pack 3" uninstall_display_name="" os_filter="" os_filter_min="winXPsp1" os_filter_max="winXPsp2" os_filter_lcid="" type="exe" installcompletemessage="" uninstallcompletemessage="" mustreboot="False" reboot_required="" must_reboot_required="False" failed_exec_command_continue="" allow_continue_on_error="False" default_continue_on_error="False" required_install="True" required_uninstall="False" selected_install="True" selected_uninstall="True" note="Windows XP Service Pack 3" processor_architecture_filter="" status_installed="" status_notinstalled="" supports_install="True" supports_uninstall="False" show_progress_dialog="True" show_cab_dialog="True"> + <downloaddialog dialog_caption="Download Windows XP Service Pack 3" dialog_message="SyntikX requires Windows XP Service Pack 3 or latter. Press start to automatically download and install this update." dialog_message_downloading="Downloading ..." dialog_message_copying="Copying ..." dialog_message_connecting="Connecting ..." dialog_message_sendingrequest="Sending request ..." autostartdownload="False" buttonstart_caption="Start" buttoncancel_caption="Cancel"> + <download componentname="Windows XP Service Pack 3" sourceurl="http://download.microsoft.com/download/d/3/0/d30e32d8-418a-469d-b600-f32ce3edf42d/WindowsXP-KB936929-SP3-x86-ENU.exe" sourcepath="" destinationpath="#TEMPPATH\syntik_bootstrap\" destinationfilename="" alwaysdownload="False" clear_cache="False" /> + </downloaddialog> + </component> + <component command="#TEMPPATH\syntik_bootstrap\dotNetFx40_Full_setup.exe /passive" command_silent="" command_basic="" uninstall_command="" uninstall_command_silent="" uninstall_command_basic="" returncodes_success="" returncodes_reboot="3010" disable_wow64_fs_redirection="False" id="Microsoft .NET Framework 4.0 - Full" display_name="Microsoft .NET Framework 4.0 " uninstall_display_name="" os_filter="" os_filter_min="winXPsp3" os_filter_max="" os_filter_lcid="" type="cmd" installcompletemessage="" uninstallcompletemessage="" mustreboot="False" reboot_required="" must_reboot_required="False" failed_exec_command_continue="" allow_continue_on_error="False" default_continue_on_error="False" required_install="True" required_uninstall="False" selected_install="True" selected_uninstall="False" note="English - WebSetup - .NET Framework 4.0 - Full for all operating system since Windows XP SP3 (Install check)" processor_architecture_filter="" status_installed="" status_notinstalled="" supports_install="True" supports_uninstall="False" show_progress_dialog="True" show_cab_dialog="True"> + <installedcheck path="SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" fieldname="Install" fieldvalue="1" defaultvalue="False" fieldtype="REG_DWORD" comparison="match" rootkey="HKEY_LOCAL_MACHINE" wowoption="NONE" type="check_registry_value" description="Installed Check" /> + <downloaddialog dialog_caption="Microsoft .NET Framework 4.0" dialog_message="Press 'Start' to download and install Microsoft .NET Framework 4.0 - Full" dialog_message_downloading="Download in progress. Please wait..." dialog_message_copying="Files are downloaded. Please wait ..." dialog_message_connecting="Connecting ..." dialog_message_sendingrequest="Sending request ..." autostartdownload="True" buttonstart_caption="Start" buttoncancel_caption="Cancel"> + <download componentname="Microsoft .NET Framework 4.0" sourceurl="http://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe" sourcepath="" destinationpath="#TEMPPATH\syntik_bootstrap\" destinationfilename="" alwaysdownload="True" clear_cache="True" /> + </downloaddialog> + </component> + <component package="#TEMPPATH\syntik_update\syntikx.msi" cmdparameters="" cmdparameters_silent="" cmdparameters_basic="" uninstall_package="" uninstall_cmdparameters="/qb-" uninstall_cmdparameters_silent="/qn" uninstall_cmdparameters_basic="/qb-" disable_wow64_fs_redirection="False" id="SyntikX" display_name="SyntikX" uninstall_display_name="" os_filter="" os_filter_min="winXPsp3" os_filter_max="" os_filter_lcid="" type="msi" installcompletemessage="" uninstallcompletemessage="" mustreboot="False" reboot_required="" must_reboot_required="False" failed_exec_command_continue="" allow_continue_on_error="False" default_continue_on_error="False" required_install="True" required_uninstall="True" selected_install="True" selected_uninstall="True" note="" processor_architecture_filter="" status_installed="" status_notinstalled="" supports_install="True" supports_uninstall="True" show_progress_dialog="True" show_cab_dialog="True"> + <downloaddialog dialog_caption="Downloading latest version of SyntikX" dialog_message="Press 'Start' to install latest version of SyntikX" dialog_message_downloading="Downloading ..." dialog_message_copying="Copying ..." dialog_message_connecting="Connecting ..." dialog_message_sendingrequest="Sending request ..." autostartdownload="True" buttonstart_caption="Start" buttoncancel_caption="Cancel"> + <download componentname="Download Setup" sourceurl="http://localhost:59330/v1/update" sourcepath="" destinationpath="#TEMPPATH\syntik_update\" destinationfilename="syntikx.msi" alwaysdownload="True" clear_cache="True" /> + </downloaddialog> + </component> + </configuration> +</configurations> \ No newline at end of file diff --git a/NzbDrone.Wix/dotNetInstaller.exe b/NzbDrone.Wix/dotNetInstaller.exe new file mode 100644 index 000000000..a0dd60f8b Binary files /dev/null and b/NzbDrone.Wix/dotNetInstaller.exe differ diff --git a/NzbDrone.Wix/nzbdrone.wix.build.bat b/NzbDrone.Wix/nzbdrone.wix.build.bat new file mode 100644 index 000000000..f031916c2 --- /dev/null +++ b/NzbDrone.Wix/nzbdrone.wix.build.bat @@ -0,0 +1,8 @@ +rd _raw /s /q +rd _setup /s /q +xcopy ..\SyntikX.Client\bin\release\*.* _raw\ /S /V /I /F /R + +"C:\Program Files (x86)\WiX Toolset v3.6\bin\candle.exe" -nologo "syntik.wix.build.wxs" -out "_setup\SyntikX.Wix.wixobj" -ext WixNetFxExtension -ext WixUIExtension +"C:\Program Files (x86)\WiX Toolset v3.6\bin\light.exe" -nologo "_setup\SyntikX.Wix.wixobj" -out "_setup\SyntikX.msi" -ext WixNetFxExtension -ext WixUIExtension + +pause \ No newline at end of file diff --git a/NzbDrone.Wix/nzbdrone.wix.build.debug.bat b/NzbDrone.Wix/nzbdrone.wix.build.debug.bat new file mode 100644 index 000000000..abf84ccaf --- /dev/null +++ b/NzbDrone.Wix/nzbdrone.wix.build.debug.bat @@ -0,0 +1,10 @@ +rd _raw /s /q +rd _setup /s /q +xcopy ..\SyntikX.Client\bin\debug\*.* _raw\ /S /V /I /F /R + +SET BUILD_NUMBER=1.9.9.9 + +"C:\Program Files (x86)\WiX Toolset v3.6\bin\candle.exe" -nologo "syntik.wix.build.wxs" -out "_setup\SyntikX.Wix.wixobj" -ext WixNetFxExtension -ext WixUIExtension +"C:\Program Files (x86)\WiX Toolset v3.6\bin\light.exe" -nologo "_setup\SyntikX.Wix.wixobj" -out "_setup\SyntikX.Wix.msi" -ext WixNetFxExtension -ext WixUIExtension + +pause \ No newline at end of file diff --git a/NzbDrone.Wix/nzbdrone.wix.build.wxs b/NzbDrone.Wix/nzbdrone.wix.build.wxs new file mode 100644 index 000000000..36284be23 --- /dev/null +++ b/NzbDrone.Wix/nzbdrone.wix.build.wxs @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="utf-8"?> +<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> + <Product Id="*" Name="NzbDrone" Language="1033" Version="$(env.BUILD_NUMBER)" Manufacturer="NzbDrone Team" UpgradeCode="56833D74-A480-4CA2-B562-5A018B3A0F99"> + <Package Description="NzbDrone" + Comments="NzbDrone" + InstallerVersion="200" + Compressed="yes" + InstallPrivileges="limited" + InstallScope="perUser" + Platform="x86" + Manufacturer="NzbDrone Team" + /> + <Media Id="1" Cabinet="NzbDrone.cab" EmbedCab="yes" CompressionLevel="high"/> + <Property Id="ARPPRODUCTICON" Value="ND_ICON" /> + <Directory Id="TARGETDIR" Name="SourceDir"> + <Directory Id="LocalAppDataFolder" Name="AppDataFolder"> + <Directory Name="NzbDrone" Id="SX_ROOT"> + <Component Id="SX_APP_COMP" DiskId="1" Guid="9c3ac309-cde4-4338-be75-a914cbce2601"> + <File Id="SX_EXE_FILE" Name="NzbDrone.Client.exe" Source="_raw\NzbDrone.Client.exe" /> + <File Id="AUTOFAC.CONFIGURATION.DLL" Name="Autofac.Configuration.dll" Source="_raw\Autofac.Configuration.dll" /> + <File Id="AUTOFAC.DLL" Name="Autofac.dll" Source="_raw\Autofac.dll" /> + <File Id="BCRYPT.NET.DLL" Name="BCrypt.Net.dll" Source="_raw\BCrypt.Net.dll" /> + <File Id="CALIBURN.MICRO.DLL" Name="Caliburn.Micro.dll" Source="_raw\Caliburn.Micro.dll" /> + <File Id="ICSHARPCODE.SHARPZIPLIB.DLL" Name="ICSharpCode.SharpZipLib.dll" Source="_raw\ICSharpCode.SharpZipLib.dll" /> + <File Id="MAHAPPS.METRO.DLL" Name="MahApps.Metro.dll" Source="_raw\MahApps.Metro.dll" /> + <File Id="NEWTONSOFT.JSON.DLL" Name="Newtonsoft.Json.dll" Source="_raw\Newtonsoft.Json.dll" /> + <File Id="NLOG.DLL" Name="NLog.dll" Source="_raw\NLog.dll" /> + <File Id="RESTSHARP.DLL" Name="RestSharp.dll" Source="_raw\RestSharp.dll" /> + <File Id="EXCEPTRON.CLIENT.DLL" Name="exceptron.client.dll" Source="_raw\exceptron.client.dll" /> + <File Id="EXCEPTRON.NLOG.DLL" Name="exceptron.nlog.dll" Source="_raw\exceptron.nlog.dll" /> + <File Id="NzbDrone.CLIENT.CORE.DLL" Name="NzbDrone.Client.Core.dll" Source="_raw\NzbDrone.Client.Core.dll" /> + <File Id="NzbDrone.CLIENT.CORE.PDB" Name="NzbDrone.Client.Core.pdb" Source="_raw\NzbDrone.Client.Core.pdb" /> + <File Id="NzbDrone.CLIENT.EXE.CONFIG" Name="NzbDrone.Client.exe.config" Source="_raw\NzbDrone.Client.exe.config" /> + <File Id="NzbDrone.CLIENT.PDB" Name="NzbDrone.Client.pdb" Source="_raw\NzbDrone.Client.pdb" /> + <File Id="NzbDrone.SHARED.DLL" Name="NzbDrone.Shared.dll" Source="_raw\NzbDrone.Shared.dll" /> + <File Id="NzbDrone.SHARED.PDB" Name="NzbDrone.Shared.pdb" Source="_raw\NzbDrone.Shared.pdb" /> + <File Id="SYSTEM.WINDOWS.INTERACTIVITY.DLL" Name="System.Windows.Interactivity.dll" Source="_raw\System.Windows.Interactivity.dll" /> + <RegistryValue Root="HKCU" Key="Software\Microsoft\NzbDrone" Name="installed" Type="integer" Value="1" KeyPath="yes" /> + <RemoveFolder Id="SX_ROOT" On="uninstall" /> + </Component> + </Directory> + </Directory> + <Directory Id="DesktopFolder"> + <Component Id="SX_DESKTOP_SHORTCUT_COMP" Guid="1f395635-7a9d-454d-aab4-95a5a4e70be4"> + <Shortcut Id="SX_DESKTOP_SHORTCUT" Name="NzbDrone" Description="NzbDrone Backup Client" Target="[SX_ROOT]NzbDrone.Client.exe" WorkingDirectory="SX_ROOT" /> + <RegistryValue Root="HKCU" Key="Software\Microsoft\NzbDrone" Name="installed" Type="integer" Value="1" KeyPath="yes" /> + </Component> + </Directory> + <Directory Id="ProgramMenuFolder"> + <Directory Id="SX_START_DIR" Name="NzbDrone"> + <Component Id="SX_START_SHORTCUT_COMP" Guid="8b3d54c6-712b-4bc2-b1e9-7cf40bcc1344"> + <Shortcut Id="SX_START_SHORTCUT" Name="NzbDrone" Description="NzbDrone Backup Client" Target="[SX_ROOT]NzbDrone.Client.exe" WorkingDirectory="SX_ROOT" /> + <RemoveFolder Id="SX_START_DIR" On="uninstall" /> + <RegistryValue Root="HKCU" Key="Software\Microsoft\NzbDrone" Name="installed" Type="integer" Value="1" KeyPath="yes" /> + </Component> + </Directory> + <Directory Id="StartupFolder" Name="Startup"> + <Component Id="SX_STARTUP_SHORTCUT_COMP" Guid="0fdfe510-621e-4925-a0d4-395617fb7cbc"> + <Shortcut Id="SX_STARTUP_SHORTCUT" Name="NzbDrone" Description="NzbDrone Backup Client" Target="[SX_ROOT]NzbDrone.Client.exe" Arguments="/startup" WorkingDirectory="SX_ROOT" /> + <RegistryValue Root="HKCU" Key="Software\Microsoft\NzbDrone" Name="installed" Type="integer" Value="1" KeyPath="yes" /> + </Component> + </Directory> + </Directory> + </Directory> + <!--<UIRef Id="WixUI_Minimal" />--> + <UI /> + <MajorUpgrade AllowDowngrades="no" AllowSameVersionUpgrades="yes" MigrateFeatures="yes" Schedule="afterInstallInitialize" DowngradeErrorMessage="Newer version of NzbDrone is already installed." /> + <Feature Id="DefaultFeature" Title="Main Feature" Level="1"> + <ComponentRef Id="SX_APP_COMP" /> + <ComponentRef Id="SX_START_SHORTCUT_COMP" /> + <ComponentRef Id="SX_STARTUP_SHORTCUT_COMP" /> + <ComponentRef Id="SX_DESKTOP_SHORTCUT_COMP" /> + </Feature> + <PropertyRef Id="NETFRAMEWORK40FULL" /> + <Condition Message="This application requires .NET Framework 4.0 or later. Please install the .NET Framework then run this installer again.">NETFRAMEWORK40FULL</Condition> + <Icon Id="SX_ICON" SourceFile="_raw\NzbDrone.Client.exe" /> + <InstallExecuteSequence> + <InstallInitialize/> + <Custom Action="SX_START_ACTION" After="InstallFiles" /> + <InstallFinalize/> + </InstallExecuteSequence> + <CustomAction Id="SX_START_ACTION" FileKey="SX_EXE_FILE" ExeCommand="" Execute="deferred" Impersonate="no" Return="asyncNoWait" /> + </Product> +</Wix> \ No newline at end of file diff --git a/NzbDrone.ncrunchsolution b/NzbDrone.ncrunchsolution index 6cb47a29a..098d74afe 100644 --- a/NzbDrone.ncrunchsolution +++ b/NzbDrone.ncrunchsolution @@ -1,12 +1,13 @@ <SolutionConfiguration> <FileVersion>1</FileVersion> - <AutoEnableOnStartup>False</AutoEnableOnStartup> + <AutoEnableOnStartup>True</AutoEnableOnStartup> <AllowParallelTestExecution>true</AllowParallelTestExecution> + <AllowTestsToRunInParallelWithThemselves>true</AllowTestsToRunInParallelWithThemselves> <FrameworkUtilisationTypeForNUnit>UseDynamicAnalysis</FrameworkUtilisationTypeForNUnit> <FrameworkUtilisationTypeForGallio>Disabled</FrameworkUtilisationTypeForGallio> <FrameworkUtilisationTypeForMSpec>Disabled</FrameworkUtilisationTypeForMSpec> <FrameworkUtilisationTypeForMSTest>Disabled</FrameworkUtilisationTypeForMSTest> - <EngineModes>Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk</EngineModes> + <EngineModes>Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk;Fast:DlN0cnVjdHVyYWxOb2RlBAAAABNEb2VzTm90SGF2ZUNhdGVnb3J5D0ludGVncmF0aW9uVGVzdBNEb2VzTm90SGF2ZUNhdGVnb3J5BkRiVGVzdApJc0ltcGFjdGVkE0RvZXNOb3RIYXZlQ2F0ZWdvcnkORGlza0FjY2Vzc1Rlc3QAAAAAAAAAAAAAAAA=</EngineModes> <MetricsExclusionList> </MetricsExclusionList> </SolutionConfiguration> \ No newline at end of file diff --git a/NzbDrone.sln b/NzbDrone.sln index 4fa697bac..f118155ff 100644 --- a/NzbDrone.sln +++ b/NzbDrone.sln @@ -56,92 +56,228 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Host", "NzbDrone.H EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Host", "Host", "{486ADF86-DD89-4E19-B805-9D94F19800D9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.SignalR", "NzbDrone.SignalR\NzbDrone.SignalR.csproj", "{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|Any CPU.ActiveCfg = Debug|x86 + {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|Mixed Platforms.Build.0 = Debug|x86 {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|x86.ActiveCfg = Debug|x86 {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|x86.Build.0 = Debug|x86 + {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|Any CPU.ActiveCfg = Release|x86 + {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|Mixed Platforms.Build.0 = Release|x86 {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|x86.ActiveCfg = Release|x86 {D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|x86.Build.0 = Release|x86 + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|Any CPU.ActiveCfg = Debug|x86 + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|Mixed Platforms.Build.0 = Debug|x86 {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|x86.ActiveCfg = Debug|x86 {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|x86.Build.0 = Debug|x86 + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|Any CPU.ActiveCfg = Release|x86 + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|Mixed Platforms.Build.0 = Release|x86 {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|x86.ActiveCfg = Release|x86 {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|x86.Build.0 = Release|x86 + {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|Any CPU.ActiveCfg = Debug|x86 + {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|Mixed Platforms.Build.0 = Debug|x86 {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|x86.ActiveCfg = Debug|x86 {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|x86.Build.0 = Debug|x86 + {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|Any CPU.ActiveCfg = Release|x86 + {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|Mixed Platforms.Build.0 = Release|x86 {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|x86.ActiveCfg = Release|x86 {193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|x86.Build.0 = Release|x86 + {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|Any CPU.ActiveCfg = Debug|x86 + {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|Mixed Platforms.Build.0 = Debug|x86 {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|x86.ActiveCfg = Debug|x86 {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|x86.Build.0 = Debug|x86 + {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|Any CPU.ActiveCfg = Release|x86 + {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|Mixed Platforms.Build.0 = Release|x86 {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|x86.ActiveCfg = Release|x86 {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|x86.Build.0 = Release|x86 + {FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|Any CPU.ActiveCfg = Debug|x86 + {FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|Mixed Platforms.Build.0 = Debug|x86 {FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|x86.ActiveCfg = Debug|x86 {FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|x86.Build.0 = Debug|x86 + {FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|Any CPU.ActiveCfg = Release|x86 + {FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|Mixed Platforms.Build.0 = Release|x86 {FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|x86.ActiveCfg = Release|x86 {FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|x86.Build.0 = Release|x86 + {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|Any CPU.ActiveCfg = Debug|x86 + {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|Mixed Platforms.Build.0 = Debug|x86 {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|x86.ActiveCfg = Debug|x86 {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|x86.Build.0 = Debug|x86 + {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|Any CPU.ActiveCfg = Release|x86 + {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|Mixed Platforms.Build.0 = Release|x86 {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|x86.ActiveCfg = Release|x86 {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|x86.Build.0 = Release|x86 + {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|Any CPU.ActiveCfg = Debug|x86 + {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|Mixed Platforms.Build.0 = Debug|x86 {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|x86.ActiveCfg = Debug|x86 {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|x86.Build.0 = Debug|x86 + {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|Any CPU.ActiveCfg = Release|x86 + {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|Mixed Platforms.Build.0 = Release|x86 {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|x86.ActiveCfg = Release|x86 {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|x86.Build.0 = Release|x86 + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|Any CPU.ActiveCfg = Debug|x86 + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|Mixed Platforms.Build.0 = Debug|x86 {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|x86.ActiveCfg = Debug|x86 {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|x86.Build.0 = Debug|x86 + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|Any CPU.ActiveCfg = Release|x86 + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|Mixed Platforms.Build.0 = Release|x86 {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|x86.ActiveCfg = Release|x86 {F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|x86.Build.0 = Release|x86 + {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|Any CPU.ActiveCfg = Debug|x86 + {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|Mixed Platforms.Build.0 = Debug|x86 {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|x86.ActiveCfg = Debug|x86 {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|x86.Build.0 = Debug|x86 + {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|Any CPU.ActiveCfg = Release|x86 + {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|Mixed Platforms.Build.0 = Release|x86 {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|x86.ActiveCfg = Release|x86 {BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|x86.Build.0 = Release|x86 + {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|Any CPU.ActiveCfg = Debug|x86 + {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|Mixed Platforms.Build.0 = Debug|x86 {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|x86.ActiveCfg = Debug|x86 {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|x86.Build.0 = Debug|x86 + {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|Any CPU.ActiveCfg = Release|x86 + {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|Mixed Platforms.Build.0 = Release|x86 {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|x86.ActiveCfg = Release|x86 {CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|x86.Build.0 = Release|x86 + {6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|Any CPU.ActiveCfg = Debug|x86 + {6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|Mixed Platforms.Build.0 = Debug|x86 {6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|x86.ActiveCfg = Debug|x86 {6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|x86.Build.0 = Debug|x86 + {6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|Any CPU.ActiveCfg = Release|x86 + {6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|Mixed Platforms.Build.0 = Release|x86 {6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|x86.ActiveCfg = Release|x86 {6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|x86.Build.0 = Release|x86 + {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|Any CPU.ActiveCfg = Debug|x86 + {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|Mixed Platforms.Build.0 = Debug|x86 {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|x86.ActiveCfg = Debug|x86 {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|x86.Build.0 = Debug|x86 + {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|Any CPU.ActiveCfg = Release|x86 + {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|Mixed Platforms.Build.0 = Release|x86 {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|x86.ActiveCfg = Release|x86 {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|x86.Build.0 = Release|x86 + {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|Any CPU.ActiveCfg = Debug|x86 + {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|Mixed Platforms.Build.0 = Debug|x86 {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|x86.ActiveCfg = Debug|x86 {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|x86.Build.0 = Debug|x86 + {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|Any CPU.ActiveCfg = Release|x86 + {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|Mixed Platforms.Build.0 = Release|x86 {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|x86.ActiveCfg = Release|x86 {FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|x86.Build.0 = Release|x86 + {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|Any CPU.ActiveCfg = Debug|x86 + {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|Mixed Platforms.Build.0 = Debug|x86 {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|x86.ActiveCfg = Debug|x86 {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|x86.Build.0 = Debug|x86 + {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|Any CPU.ActiveCfg = Release|x86 + {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|Mixed Platforms.Build.0 = Release|x86 {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|x86.ActiveCfg = Release|x86 {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|x86.Build.0 = Release|x86 + {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|Any CPU.ActiveCfg = Debug|x86 + {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|Mixed Platforms.Build.0 = Debug|x86 {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|x86.ActiveCfg = Debug|x86 {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|x86.Build.0 = Debug|x86 + {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|Any CPU.ActiveCfg = Release|x86 + {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|Mixed Platforms.Build.0 = Release|x86 {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|x86.ActiveCfg = Release|x86 {D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|x86.Build.0 = Release|x86 + {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|Any CPU.ActiveCfg = Debug|x86 + {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|Mixed Platforms.Build.0 = Debug|x86 {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|x86.ActiveCfg = Debug|x86 {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|x86.Build.0 = Debug|x86 + {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|Any CPU.ActiveCfg = Release|x86 + {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|Mixed Platforms.Build.0 = Release|x86 {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|x86.ActiveCfg = Release|x86 {F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|x86.Build.0 = Release|x86 + {CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|Any CPU.ActiveCfg = Debug|x86 + {CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|Mixed Platforms.Build.0 = Debug|x86 {CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|x86.ActiveCfg = Debug|x86 {CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|x86.Build.0 = Debug|x86 + {CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|Any CPU.ActiveCfg = Release|x86 + {CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|Mixed Platforms.Build.0 = Release|x86 {CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|x86.ActiveCfg = Release|x86 {CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|x86.Build.0 = Release|x86 + {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|Any CPU.ActiveCfg = Debug|x86 + {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|Mixed Platforms.Build.0 = Debug|x86 {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|x86.ActiveCfg = Debug|x86 {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|x86.Build.0 = Debug|x86 + {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|Any CPU.ActiveCfg = Release|x86 + {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|Mixed Platforms.Build.0 = Release|x86 {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|x86.ActiveCfg = Release|x86 {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|x86.Build.0 = Release|x86 + {B1784698-592E-4132-BDFA-9817409E3A96}.Debug|Any CPU.ActiveCfg = Debug|x86 + {B1784698-592E-4132-BDFA-9817409E3A96}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {B1784698-592E-4132-BDFA-9817409E3A96}.Debug|Mixed Platforms.Build.0 = Debug|x86 {B1784698-592E-4132-BDFA-9817409E3A96}.Debug|x86.ActiveCfg = Debug|x86 {B1784698-592E-4132-BDFA-9817409E3A96}.Debug|x86.Build.0 = Debug|x86 + {B1784698-592E-4132-BDFA-9817409E3A96}.Release|Any CPU.ActiveCfg = Release|x86 + {B1784698-592E-4132-BDFA-9817409E3A96}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {B1784698-592E-4132-BDFA-9817409E3A96}.Release|Mixed Platforms.Build.0 = Release|x86 {B1784698-592E-4132-BDFA-9817409E3A96}.Release|x86.ActiveCfg = Release|x86 {B1784698-592E-4132-BDFA-9817409E3A96}.Release|x86.Build.0 = Release|x86 + {95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|Any CPU.ActiveCfg = Debug|x86 + {95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|Mixed Platforms.Build.0 = Debug|x86 {95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|x86.ActiveCfg = Debug|x86 {95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|x86.Build.0 = Debug|x86 + {95C11A9E-56ED-456A-8447-2C89C1139266}.Release|Any CPU.ActiveCfg = Release|x86 + {95C11A9E-56ED-456A-8447-2C89C1139266}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {95C11A9E-56ED-456A-8447-2C89C1139266}.Release|Mixed Platforms.Build.0 = Release|x86 {95C11A9E-56ED-456A-8447-2C89C1139266}.Release|x86.ActiveCfg = Release|x86 {95C11A9E-56ED-456A-8447-2C89C1139266}.Release|x86.Build.0 = Release|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|Any CPU.ActiveCfg = Debug|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|x86.ActiveCfg = Debug|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|x86.Build.0 = Debug|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|Any CPU.ActiveCfg = Release|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|Mixed Platforms.Build.0 = Release|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|x86.ActiveCfg = Release|x86 + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -159,9 +295,9 @@ Global {CADDFCE0-7509-4430-8364-2074E1EEFCA2} = {47697CDB-27B6-4B05-B4F8-0CBE6F6EDF97} {6BCE712F-846D-4846-9D1B-A66B858DA755} = {F9E67978-5CD6-4A5F-827B-4249711C0B02} {700D0B95-95CD-43F3-B6C9-FAA0FC1358D4} = {F9E67978-5CD6-4A5F-827B-4249711C0B02} - {D12F7F2F-8A3C-415F-88FA-6DD061A84869} = {486ADF86-DD89-4E19-B805-9D94F19800D9} {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976} = {486ADF86-DD89-4E19-B805-9D94F19800D9} {95C11A9E-56ED-456A-8447-2C89C1139266} = {486ADF86-DD89-4E19-B805-9D94F19800D9} + {D12F7F2F-8A3C-415F-88FA-6DD061A84869} = {486ADF86-DD89-4E19-B805-9D94F19800D9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.0\lib\NET35;packages\Unity.2.1.505.2\lib\NET35 diff --git a/NzbDrone.sln.DotSettings b/NzbDrone.sln.DotSettings index 2aded5528..a7de7ce4f 100644 --- a/NzbDrone.sln.DotSettings +++ b/NzbDrone.sln.DotSettings @@ -11,6 +11,8 @@ <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue">WARNING</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedParameter_002ELocal/@EntryIndexedValue">WARNING</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseObjectOrCollectionInitializer/@EntryIndexedValue">HINT</s:String> + <s:String x:Key="/Default/CodeInspection/TestFileAnalysis/TestClassSuffix/@EntryValue">Fixture</s:String> + <s:String x:Key="/Default/CodeInspection/TestFileAnalysis/TestProjectToCodeProjectNameSpaceRegEx/@EntryValue">^(.*)\.Test(\..*)$</s:String> <s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=NzbDrone/@EntryIndexedValue"><?xml version="1.0" encoding="utf-16"?><Profile name="NzbDrone"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>IMPLICIT_EXCEPT_SIMPLE_TYPES</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSUpdateFileHeader>True</CSUpdateFileHeader><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile></s:String> <s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">NzbDrone</s:String> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_CHOP_COMPOUND_WHILE_EXPRESSION/@EntryValue">True</s:Boolean> @@ -47,6 +49,8 @@ <s:Boolean x:Key="/Default/Environment/UnitTesting/DisabledProviders/=Jasmine/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/UnitTesting/DisabledProviders/=MSTest/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/UnitTesting/DisabledProviders/=QUnit/@EntryIndexedValue">True</s:Boolean> + <s:Int64 x:Key="/Default/Environment/UnitTesting/ParallelProcessesCount/@EntryValue">4</s:Int64> + <s:Boolean x:Key="/Default/Environment/UnitTesting/SeparateAppDomainPerAssembly/@EntryValue">True</s:Boolean> <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=26E712D4B91E2E49A0E92C0AFE6FF57E/Entry/=38860059D7978D4DAF1997C7CBC46A78/EntryName/@EntryValue">Backbone model</s:String> <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=26E712D4B91E2E49A0E92C0AFE6FF57E/Entry/=38860059D7978D4DAF1997C7CBC46A78/Position/@EntryValue">5</s:Int64> <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=09B531154963914B9AB9E2A05E1F2B44/@KeyIndexDefined">True</s:Boolean> diff --git a/NzbDrone/Properties/Resources.Designer.cs b/NzbDrone/Properties/Resources.Designer.cs index d47a0a7ea..65584111d 100644 --- a/NzbDrone/Properties/Resources.Designer.cs +++ b/NzbDrone/Properties/Resources.Designer.cs @@ -9,9 +9,6 @@ //------------------------------------------------------------------------------ namespace NzbDrone.Properties { - using System; - - /// <summary> /// A strongly-typed resource class, for looking up localized strings, etc. /// </summary> diff --git a/NzbDrone/WindowsApp.cs b/NzbDrone/WindowsApp.cs index 4d4dc9d69..5b20de23b 100644 --- a/NzbDrone/WindowsApp.cs +++ b/NzbDrone/WindowsApp.cs @@ -2,6 +2,7 @@ using System.Windows.Forms; using NLog; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Instrumentation; using NzbDrone.Host; using NzbDrone.SysTray; @@ -9,12 +10,17 @@ namespace NzbDrone { public static class WindowsApp { + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); public static void Main(string[] args) { try { - var container = Bootstrap.Start(new StartupArguments(args), new MessageBoxUserAlert()); + var startupArgs = new StartupArguments(args); + + LogTargets.Register(startupArgs, false, true); + + var container = Bootstrap.Start(startupArgs, new MessageBoxUserAlert()); container.Register<ISystemTrayApp, SystemTrayApp>(); container.Resolve<ISystemTrayApp>().Start(); } @@ -23,9 +29,10 @@ namespace NzbDrone } catch (Exception e) { + Logger.FatalException("EPIC FAIL: " + e.Message, e); var message = string.Format("{0}: {1}", e.GetType().Name, e.Message); MessageBox.Show(text: message, buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Error, caption: "Epic Fail!"); } } } -} \ No newline at end of file +} diff --git a/ServiceHelpers/ServiceInstall/ServiceInstall.ncrunchproject b/ServiceHelpers/ServiceInstall/ServiceInstall.ncrunchproject index 73e1ebbb9..b2eed192e 100644 --- a/ServiceHelpers/ServiceInstall/ServiceInstall.ncrunchproject +++ b/ServiceHelpers/ServiceInstall/ServiceInstall.ncrunchproject @@ -1,8 +1,8 @@ <ProjectConfiguration> <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> - <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> + <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,10 +12,12 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration></UseBuildConfiguration> - <UseBuildPlatform /> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> <ProxyProcessPath></ProxyProcessPath> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <UseCPUArchitecture>x86</UseCPUArchitecture> + <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> + <BuildProcessArchitecture>x86</BuildProcessArchitecture> <HiddenWarnings>PostBuildEventDisabled</HiddenWarnings> </ProjectConfiguration> \ No newline at end of file diff --git a/ServiceHelpers/ServiceUninstall/ServiceUninstall.ncrunchproject b/ServiceHelpers/ServiceUninstall/ServiceUninstall.ncrunchproject index 73e1ebbb9..b2eed192e 100644 --- a/ServiceHelpers/ServiceUninstall/ServiceUninstall.ncrunchproject +++ b/ServiceHelpers/ServiceUninstall/ServiceUninstall.ncrunchproject @@ -1,8 +1,8 @@ <ProjectConfiguration> <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> - <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> + <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing> <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> - <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> + <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <RunPreBuildEvents>false</RunPreBuildEvents> @@ -12,10 +12,12 @@ <PreventSigningOfAssembly>false</PreventSigningOfAssembly> <AnalyseExecutionTimes>true</AnalyseExecutionTimes> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> - <DefaultTestTimeout>60000</DefaultTestTimeout> - <UseBuildConfiguration></UseBuildConfiguration> - <UseBuildPlatform /> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <UseBuildConfiguration>Debug</UseBuildConfiguration> + <UseBuildPlatform>x86</UseBuildPlatform> <ProxyProcessPath></ProxyProcessPath> - <UseCPUArchitecture>AutoDetect</UseCPUArchitecture> + <UseCPUArchitecture>x86</UseCPUArchitecture> + <MSTestThreadApartmentState>STA</MSTestThreadApartmentState> + <BuildProcessArchitecture>x86</BuildProcessArchitecture> <HiddenWarnings>PostBuildEventDisabled</HiddenWarnings> </ProjectConfiguration> \ No newline at end of file diff --git a/UI/AddSeries/AddSeriesTemplate.html b/UI/AddSeries/AddSeriesTemplate.html index 87715f2b1..696eb92f7 100644 --- a/UI/AddSeries/AddSeriesTemplate.html +++ b/UI/AddSeries/AddSeriesTemplate.html @@ -1,9 +1,9 @@ {{#if folder.path}} - <div class="row well unmapped-folder-path"> - <div class="span11"> - {{folder.path}} - </div> +<div class="row well unmapped-folder-path"> + <div class="span11"> + {{folder.path}} </div> +</div> {{/if}} <div class="row x-search-bar"> <div class="input-prepend nz-input-large add-series-search span11"> diff --git a/UI/AddSeries/Collection.js b/UI/AddSeries/Collection.js index 04101920b..0dce7bc1e 100644 --- a/UI/AddSeries/Collection.js +++ b/UI/AddSeries/Collection.js @@ -5,7 +5,7 @@ define( 'Series/SeriesModel' ], function (Backbone, SeriesModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/series/lookup', + url : window.NzbDrone.ApiRoot + '/series/lookup', model: SeriesModel, initialize: function (options) { diff --git a/UI/AddSeries/RootFolders/Collection.js b/UI/AddSeries/RootFolders/Collection.js index 74b9b4555..157bf19ce 100644 --- a/UI/AddSeries/RootFolders/Collection.js +++ b/UI/AddSeries/RootFolders/Collection.js @@ -7,11 +7,11 @@ define( ], function (Backbone, RootFolderModel) { var RootFolderCollection = Backbone.Collection.extend({ - url : window.ApiRoot + '/rootfolder', + url : window.NzbDrone.ApiRoot + '/rootfolder', model: RootFolderModel }); - var collection = new RootFolderCollection().bindSignalR(); + //var collection = new RootFolderCollection().bindSignalR(); - return collection; + return new RootFolderCollection(); }); diff --git a/UI/AddSeries/RootFolders/LayoutTemplate.html b/UI/AddSeries/RootFolders/LayoutTemplate.html index a62ef128d..390b3c056 100644 --- a/UI/AddSeries/RootFolders/LayoutTemplate.html +++ b/UI/AddSeries/RootFolders/LayoutTemplate.html @@ -6,7 +6,7 @@ <div class="validation-errors"></div> <div class="input-prepend input-append x-path control-group"> <span class="add-on"> <i class="icon-folder-open"></i></span> - <input class="span9" type="text" validation-name="path" placeholder="Start Typing Folder Path..."> + <input class="span9" type="text" validation-name="path" placeholder="Enter path to folder that contains your shows"> <button class="btn btn-success x-add"> <i class="icon-ok"/> </button> diff --git a/UI/AddSeries/RootFolders/Model.js b/UI/AddSeries/RootFolders/Model.js index 2c54f6ae3..6cd0dc17b 100644 --- a/UI/AddSeries/RootFolders/Model.js +++ b/UI/AddSeries/RootFolders/Model.js @@ -4,7 +4,7 @@ define( 'backbone' ], function (Backbone) { return Backbone.Model.extend({ - urlRoot : window.ApiRoot + '/rootfolder', + urlRoot : window.NzbDrone.ApiRoot + '/rootfolder', defaults: { freeSpace: 0 } diff --git a/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html b/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html index 60f2a49d7..56d547da5 100644 --- a/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html +++ b/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html @@ -1,4 +1,4 @@ -<select class="span6 x-root-folder"> +<select class="span4 x-root-folder"> {{#if this}} {{#each this}} <option value="{{id}}">{{path}}</option> diff --git a/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html b/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html new file mode 100644 index 000000000..0827ba052 --- /dev/null +++ b/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html @@ -0,0 +1,9 @@ +<select class="span2 x-starting-season"> + {{#each this}} + {{#if_eq seasonNumber compare="0"}} + <option value="{{seasonNumber}}">Specials</option> + {{else}} + <option value="{{seasonNumber}}">Season {{seasonNumber}}</option> + {{/if_eq}} + {{/each}} +</select> diff --git a/UI/AddSeries/SearchResultView.js b/UI/AddSeries/SearchResultView.js index e7d16631a..28ba1a774 100644 --- a/UI/AddSeries/SearchResultView.js +++ b/UI/AddSeries/SearchResultView.js @@ -3,7 +3,6 @@ define( [ 'app', 'marionette', - 'Quality/QualityProfileCollection', 'AddSeries/RootFolders/Collection', 'AddSeries/RootFolders/Layout', @@ -15,13 +14,14 @@ define( return Marionette.ItemView.extend({ - template: 'AddSeries/SearchResultTemplate', + template: 'AddSeries/SearchResultViewTemplate', ui: { qualityProfile: '.x-quality-profile', rootFolder : '.x-root-folder', addButton : '.x-add', - overview : '.x-overview' + overview : '.x-overview', + startingSeason: '.x-starting-season' }, events: { @@ -57,6 +57,12 @@ define( this.ui.rootFolder.val(defaultRoot); } + var minSeasonNotZero = _.min(_.reject(this.model.get('seasons'), { seasonNumber: 0 }), 'seasonNumber'); + + if (minSeasonNotZero) { + this.ui.startingSeason.val(minSeasonNotZero.seasonNumber); + } + //TODO: make this work via onRender, FM? //works with onShow, but stops working after the first render this.ui.overview.dotdotdot({ @@ -117,15 +123,16 @@ define( var quality = this.ui.qualityProfile.val(); var rootFolderPath = this.ui.rootFolder.children(':selected').text(); + var startingSeason = this.ui.startingSeason.val(); this.model.set('qualityProfileId', quality); this.model.set('rootFolderPath', rootFolderPath); + this.model.setSeasonPass(startingSeason); var self = this; SeriesCollection.add(this.model); - this.model.save().done(function () { self.close(); icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search'); diff --git a/UI/AddSeries/SearchResultTemplate.html b/UI/AddSeries/SearchResultViewTemplate.html similarity index 65% rename from UI/AddSeries/SearchResultTemplate.html rename to UI/AddSeries/SearchResultViewTemplate.html index e60f15039..574422b17 100644 --- a/UI/AddSeries/SearchResultTemplate.html +++ b/UI/AddSeries/SearchResultViewTemplate.html @@ -2,36 +2,34 @@ <div class="row"> <div class="span2"> <a href="{{traktUrl}}" target="_blank"> - <img class="new-series-poster" src="{{remotePoster}}" - {{defaultImg}} > + <img class="new-series-poster" src="{{remotePoster}}" {{defaultImg}} > </a> </div> <div class="span9"> <div class="row"> - <h2>{{title}}</h2> + <h2>{{titleWithYear}}</h2> </div> <div class="row new-series-overview x-overview"> {{overview}} </div> <div class="row"> {{#if existing}} - <div class="btn pull-right add-series disabled"> + <div class="btn add-series disabled pull-right"> Already Exists </div> {{else}} - <div class="btn btn-success x-add pull-right add-series"> - Add - <icon class="icon-plus"></icon> - </div> {{#unless path}} {{> RootFolderSelectionPartial rootFolders}} {{/unless}} - <div class='pull-right'> - {{> QualityProfileSelectionPartial qualityProfiles}} + + {{> StartingSeasonSelectionPartial seasons}} + {{> QualityProfileSelectionPartial qualityProfiles}} + + <div class="span1 btn btn-success x-add add-series pull-right"> + Add <i class="icon-plus"></i> </div> {{/if}} - </div> </div> </div> diff --git a/UI/Calendar/Collection.js b/UI/Calendar/Collection.js index 41efd7979..87972acf7 100644 --- a/UI/Calendar/Collection.js +++ b/UI/Calendar/Collection.js @@ -5,7 +5,7 @@ define( 'Series/EpisodeModel' ], function (Backbone, EpisodeModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/calendar', + url : window.NzbDrone.ApiRoot + '/calendar', model: EpisodeModel, comparator: function (model) { diff --git a/UI/Calendar/UpcomingCollection.js b/UI/Calendar/UpcomingCollection.js index fdc7d58a0..2429e3744 100644 --- a/UI/Calendar/UpcomingCollection.js +++ b/UI/Calendar/UpcomingCollection.js @@ -6,7 +6,7 @@ define( 'Series/EpisodeModel' ], function (Backbone, Moment, EpisodeModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/calendar', + url : window.NzbDrone.ApiRoot + '/calendar', model: EpisodeModel, comparator: function (model1, model2) { diff --git a/UI/Cells/Edit/QualityCellEditor.js b/UI/Cells/Edit/QualityCellEditor.js index 711bbed99..51ebceb55 100644 --- a/UI/Cells/Edit/QualityCellEditor.js +++ b/UI/Cells/Edit/QualityCellEditor.js @@ -28,7 +28,10 @@ define( self.schema = qualityProfileSchemaCollection.first(); var selected = _.find(self.schema.get('available'), { 'id': self.model.get(self.column.get("name")).quality.id }); - selected.selected = true; + + if (selected) { + selected.selected = true; + } self.templateFunction = Marionette.TemplateCache.get(templateName); var data = self.schema.toJSON(); diff --git a/UI/Cells/EpisodeNumberCell.js b/UI/Cells/EpisodeNumberCell.js index b72720de1..85be40255 100644 --- a/UI/Cells/EpisodeNumberCell.js +++ b/UI/Cells/EpisodeNumberCell.js @@ -17,13 +17,26 @@ define( var seasonField = this.column.get('seasonNumber') || 'seasonNumber'; var episodeField = this.column.get('episodes') || 'episodeNumber'; - if (this.cellValue) { + if (this.model) { + var result = 'Unknown'; - var airDate = this.cellValue.get(airDateField); - var seasonNumber = this.cellValue.get(seasonField); - var episodes = this.cellValue.get(episodeField); + var airDate = this.model.get(airDateField); + var seasonNumber = this.model.get(seasonField); + var episodes = this.model.get(episodeField); - var result = 'Unknown'; + if (this.cellValue) { + if (!seasonNumber) { + seasonNumber = this.cellValue.get(seasonField); + } + + if (!episodes) { + episodes = this.cellValue.get(episodeField); + } + + if (!airDate) { + this.model.get(airDateField); + } + } if (episodes) { diff --git a/UI/Commands/CommandCollection.js b/UI/Commands/CommandCollection.js new file mode 100644 index 000000000..52aaef365 --- /dev/null +++ b/UI/Commands/CommandCollection.js @@ -0,0 +1,26 @@ +'use strict'; +define( + [ + 'backbone', + 'Commands/CommandModel', + 'Mixins/backbone.signalr.mixin' + ], function (Backbone, CommandModel) { + + var CommandCollection = Backbone.Collection.extend({ + url : window.NzbDrone.ApiRoot + '/command', + model: CommandModel, + + findCommand: function (command) { + return this.find(function (model) { + return model.isSameCommand(command); + }); + } + + }); + + var collection = new CommandCollection().bindSignalR(); + + collection.fetch(); + + return collection; + }); diff --git a/UI/Commands/CommandController.js b/UI/Commands/CommandController.js index c10d66ae5..650d8f67b 100644 --- a/UI/Commands/CommandController.js +++ b/UI/Commands/CommandController.js @@ -1,16 +1,56 @@ 'use strict'; -define({ - Execute: function (name, properties) { - var data = { command: name }; +define( + [ + 'Commands/CommandModel', + 'Commands/CommandCollection', + 'underscore', + 'jQuery/jquery.spin' + ], function (CommandModel, CommandCollection, _) { - if (properties) { - $.extend(data, properties); - } + return{ + + Execute: function (name, properties) { + + var attr = _.extend({name: name.toLocaleLowerCase()}, properties); + + var commandModel = new CommandModel(attr); + + return commandModel.save().success(function () { + CommandCollection.add(commandModel); + }); + }, + + bindToCommand: function (options) { + + var self = this; - return $.ajax({ - type: 'POST', - url : window.ApiRoot + '/command', - data: JSON.stringify(data) - }); + var existingCommand = CommandCollection.findCommand(options.command); + + if (existingCommand) { + this._bindToCommandModel.call(this, existingCommand, options); + } + + CommandCollection.bind('add sync', function (model) { + if (model.isSameCommand(options.command)) { + self._bindToCommandModel.call(self, model, options); + } + }); + }, + + _bindToCommandModel: function bindToCommand(model, options) { + + if (!model.isActive()) { + options.element.stopSpin(); + return; + } + + model.bind('change:state', function (model) { + if (!model.isActive()) { + options.element.stopSpin(); + } + }); + + options.element.startSpin(); + } } }); diff --git a/UI/Commands/CommandMessengerCollectionView.js b/UI/Commands/CommandMessengerCollectionView.js new file mode 100644 index 000000000..238642ab6 --- /dev/null +++ b/UI/Commands/CommandMessengerCollectionView.js @@ -0,0 +1,15 @@ +'use strict'; +define( + [ + 'app', + 'marionette', + 'Commands/CommandCollection', + 'Commands/CommandMessengerItemView' + ], function (App, Marionette, commandCollection, CommandMessengerItemView) { + + var CollectionView = Marionette.CollectionView.extend({ + itemView : CommandMessengerItemView + }); + + new CollectionView({collection: commandCollection}); + }); diff --git a/UI/Commands/CommandMessengerItemView.js b/UI/Commands/CommandMessengerItemView.js new file mode 100644 index 000000000..03199462b --- /dev/null +++ b/UI/Commands/CommandMessengerItemView.js @@ -0,0 +1,48 @@ +'use strict'; +define( + [ + 'app', + 'marionette', + 'Shared/Messenger' + ], function (App, Marionette, Messenger) { + + return Marionette.ItemView.extend({ + + + initialize: function () { + this.listenTo(this.model, 'change', this.render); + }, + + + render: function () { + if (!this.model.get('message') || !this.model.get('sendUpdatesToClient')) { + return; + } + + var message = { + type : 'info', + message : '[{0}] {1}'.format(this.model.get('name'), this.model.get('message')), + id : this.model.id, + hideAfter: 0 + }; + + switch (this.model.get('state')) { + case 'completed': + message.hideAfter = 4; + break; + case 'failed': + message.hideAfter = 4; + message.type = 'error'; + break; + default : + message.hideAfter = 0; + } + + Messenger.show(message); + + console.log(message.message); + } + + }); + + }); diff --git a/UI/Commands/CommandModel.js b/UI/Commands/CommandModel.js new file mode 100644 index 000000000..fcb08dd6d --- /dev/null +++ b/UI/Commands/CommandModel.js @@ -0,0 +1,33 @@ +'use strict'; +define( + [ + 'backbone' + ], function (Backbone) { + return Backbone.Model.extend({ + url: window.NzbDrone.ApiRoot + '/command', + + parse: function (response) { + response.name = response.name.toLocaleLowerCase(); + return response; + }, + + isActive: function () { + return this.get('state') !== 'completed' && this.get('state') !== 'failed'; + }, + + isSameCommand: function (command) { + + if (command.name.toLocaleLowerCase() != this.get('name').toLocaleLowerCase()) { + return false; + } + + for (var key in command) { + if (key !== 'name' && command[key] !== this.get(key)) { + return false; + } + } + + return true; + } + }); + }); diff --git a/UI/Content/form.less b/UI/Content/form.less index 00a64b748..3d9e5267c 100644 --- a/UI/Content/form.less +++ b/UI/Content/form.less @@ -3,8 +3,9 @@ .control-group { .controls { i { - font-size : 16px; - color : #595959; + font-size : 16px; + color : #595959; + margin-right : 5px; } .checkbox { width : 100px; @@ -15,7 +16,6 @@ } .help-inline-checkbox { - padding-left : 5px; display : inline-block; margin-top : -20px; margin-bottom : 0; @@ -35,9 +35,9 @@ textarea.release-restrictions { } .help-link { - text-decoration: none !important; + text-decoration : none !important; i { .clickable; } -} \ No newline at end of file +} diff --git a/UI/Content/icons.less b/UI/Content/icons.less index 34a55b224..0a253e951 100644 --- a/UI/Content/icons.less +++ b/UI/Content/icons.less @@ -46,6 +46,11 @@ transform : scale(1, -1); } +.icon-nd-warning:before { + .icon(@warning-sign); + color : #f89406; +} + .icon-nd-edit:before { .icon(@wrench); } @@ -109,4 +114,16 @@ .icon-nd-imported:before { .icon(@download-alt); +} + +.icon-nd-status:before { + .icon(@circle); +} + +.icon-nd-monitored:before { + .icon(@bookmark); +} + +.icon-nd-unmonitored:before { + .icon(@bookmark-empty); } \ No newline at end of file diff --git a/UI/Content/theme.less b/UI/Content/theme.less index 4c8c3a1e8..a11ceb566 100644 --- a/UI/Content/theme.less +++ b/UI/Content/theme.less @@ -12,7 +12,7 @@ @import "../Shared/Styles/clickable"; @import "../Shared/Styles/card"; -.progress { +.progress.episode-progress { width : 125px; position : relative; margin-bottom : 2px; @@ -21,6 +21,7 @@ font-size : 11.844px; font-weight : bold; text-align : center; + cursor : default; } .progressbar-back-text { @@ -33,6 +34,7 @@ display : block; width : 125px; } + .bar { position : absolute; overflow : hidden; @@ -147,3 +149,15 @@ footer { padding-right : 5px; } } + +.status-primary { + color: @linkColor; +} + +.status-success { + color: @successText; +} + +.status-danger { + color: @errorText; +} \ No newline at end of file diff --git a/UI/Controller.js b/UI/Controller.js index b11840bbb..190708195 100644 --- a/UI/Controller.js +++ b/UI/Controller.js @@ -10,7 +10,6 @@ define( 'Series/Details/SeriesDetailsLayout', 'Series/SeriesCollection', 'Missing/MissingLayout', - 'Series/SeriesModel', 'Calendar/CalendarLayout', 'Logs/Layout', 'Logs/Files/Layout', @@ -19,7 +18,7 @@ define( 'SeasonPass/Layout', 'Shared/NotFoundView', 'Shared/Modal/Region' - ], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, SeriesModel, CalendarLayout, + ], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, CalendarLayout, LogsLayout, LogFileLayout, ReleaseLayout, SystemLayout, SeasonPassLayout, NotFoundView) { return Marionette.Controller.extend({ diff --git a/UI/Episode/Search/Layout.js b/UI/Episode/Search/Layout.js index 15b89f79a..2d812ce4c 100644 --- a/UI/Episode/Search/Layout.js +++ b/UI/Episode/Search/Layout.js @@ -7,11 +7,9 @@ define( 'Episode/Search/ManualLayout', 'Release/Collection', 'Series/SeriesCollection', - 'Shared/LoadingView', - 'Shared/Messenger', 'Commands/CommandController', - 'Shared/FormatHelpers' - ], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection, LoadingView, Messenger, CommandController, FormatHelpers) { + 'Shared/LoadingView' + ], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection,CommandController, LoadingView) { return Marionette.Layout.extend({ template: 'Episode/Search/LayoutTemplate', @@ -39,16 +37,8 @@ define( e.preventDefault(); } - CommandController.Execute('episodeSearch', { episodeId: this.model.get('id') }); - - var series = SeriesCollection.get(this.model.get('seriesId')); - var seriesTitle = series.get('title'); - var season = this.model.get('seasonNumber'); - var episode = this.model.get('episodeNumber'); - var message = seriesTitle + ' - ' + season + 'x' + FormatHelpers.pad(episode, 2); - - Messenger.show({ - message: 'Search started for: ' + message + CommandController.Execute('episodeSearch', { + episodeId: this.model.get('id') }); App.vent.trigger(App.Commands.CloseModalCommand); diff --git a/UI/Episode/Summary/Layout.js b/UI/Episode/Summary/Layout.js deleted file mode 100644 index b263c5682..000000000 --- a/UI/Episode/Summary/Layout.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; -define( - [ - 'app', - 'marionette', - 'backgrid', - 'Cells/FileSizeCell', - 'Cells/QualityCell', - 'Episode/Summary/NoFileView' - ], function (App, Marionette, Backgrid, FileSizeCell, QualityCell, NoFileView) { - - return Marionette.Layout.extend({ - template: 'Episode/Summary/LayoutTemplate', - - regions: { - overview: '.episode-overview', - activity: '.episode-file-info' - }, - - columns: - [ - { - name : 'path', - label : 'Path', - cell : 'string', - sortable: false - }, - { - name : 'size', - label : 'Size', - cell : FileSizeCell, - sortable: false - }, - { - name : 'quality', - label : 'Quality', - cell : QualityCell, - sortable: false, - editable: true - } - ], - - templateHelpers: {}, - - initialize: function (options) { - if (!this.model.series) { - this.templateHelpers.series = options.series.toJSON(); - } - }, - - onShow: function () { - if (this.model.get('hasFile')) { - var episodeFile = App.request(App.Reqres.GetEpisodeFileById, this.model.get('episodeFileId')); - - this.activity.show(new Backgrid.Grid({ - collection: new Backbone.Collection(episodeFile), - columns : this.columns, - className : 'table table-bordered'spinn - })); - } - - else { - this.activity.show(new NoFileView()); - } - } - }); - }); diff --git a/UI/Handlebars/Helpers/Html.js b/UI/Handlebars/Helpers/Html.js index 21534b9ac..9c5556c0f 100644 --- a/UI/Handlebars/Helpers/Html.js +++ b/UI/Handlebars/Helpers/Html.js @@ -4,7 +4,17 @@ define( [ 'handlebars' ], function (Handlebars) { + + var placeHolder = '/Content/Images/poster-dark.jpg'; + + window.NzbDrone.imageError = function (img) { + if (!img.src.contains(placeHolder)) { + img.src = placeHolder; + } + img.onerror = null; + }; + Handlebars.registerHelper('defaultImg', function () { - return new Handlebars.SafeString('onerror=this.src=\'/Content/Images/poster-dark.jpg\';'); + return new Handlebars.SafeString('onerror=window.NzbDrone.imageError(this)'); }); }); diff --git a/UI/Handlebars/Helpers/Series.js b/UI/Handlebars/Helpers/Series.js index ac2a21747..f9d849325 100644 --- a/UI/Handlebars/Helpers/Series.js +++ b/UI/Handlebars/Helpers/Series.js @@ -62,4 +62,12 @@ define( return new Handlebars.SafeString('<span class="label label-info">{0} Seasons</span>'.format(seasonCount)) }); + + Handlebars.registerHelper('titleWithYear', function () { + if (this.title.endsWith(' ({0})'.format(this.year))) { + return this.title; + } + + return '{0} ({1})'.format(this.title, this.year); + }); }); diff --git a/UI/History/Collection.js b/UI/History/Collection.js index 525bf1e48..e3f19ca04 100644 --- a/UI/History/Collection.js +++ b/UI/History/Collection.js @@ -5,7 +5,7 @@ define( 'backbone.pageable' ], function (HistoryModel, PageableCollection) { return PageableCollection.extend({ - url : window.ApiRoot + '/history', + url : window.NzbDrone.ApiRoot + '/history', model: HistoryModel, state: { diff --git a/UI/History/Details/HistoryDetailsViewTemplate.html b/UI/History/Details/HistoryDetailsViewTemplate.html index 5d15ca7b4..2dd83ed5e 100644 --- a/UI/History/Details/HistoryDetailsViewTemplate.html +++ b/UI/History/Details/HistoryDetailsViewTemplate.html @@ -27,7 +27,7 @@ {{#if nzbInfoUrl}} <dt>Info</dt> - <dd><a href="{{nzbInfoUrl}}">{{nzbInfoUrl}}o</a></dd> + <dd><a href="{{infoUrl}}">{{infoUrl}}o</a></dd> {{/if}} {{/with}} </dl> diff --git a/UI/Instrumentation/ErrorHandler.js b/UI/Instrumentation/ErrorHandler.js index 589723771..7909dc5fb 100644 --- a/UI/Instrumentation/ErrorHandler.js +++ b/UI/Instrumentation/ErrorHandler.js @@ -73,8 +73,14 @@ return false; } + else if (xmlHttpRequest.status === 503) { + message.message = xmlHttpRequest.responseJSON.message; + } - message.message = '[{0}] {1} : {2}'.format(ajaxOptions.type, xmlHttpRequest.statusText, ajaxOptions.url); + else + { + message.message = '[{0}] {1} : {2}'.format(ajaxOptions.type, xmlHttpRequest.statusText, ajaxOptions.url); + } window.Messenger().post(message); return false; diff --git a/UI/Logs/Collection.js b/UI/Logs/Collection.js index 3b6070b59..9b755203a 100644 --- a/UI/Logs/Collection.js +++ b/UI/Logs/Collection.js @@ -1,7 +1,7 @@ 'use strict'; define(['backbone.pageable', 'Logs/Model', ], function (PagableCollection, LogsModel) { return PagableCollection.extend({ - url : window.ApiRoot + '/log', + url : window.NzbDrone.ApiRoot + '/log', model: LogsModel, state: { diff --git a/UI/Logs/Files/Collection.js b/UI/Logs/Files/Collection.js index 969070232..0b2a5c8bb 100644 --- a/UI/Logs/Files/Collection.js +++ b/UI/Logs/Files/Collection.js @@ -2,7 +2,7 @@ define(['Logs/Files/Model' ], function (LogFileModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/log/files', + url : window.NzbDrone.ApiRoot + '/log/files', model: LogFileModel, state: { diff --git a/UI/Missing/Collection.js b/UI/Missing/Collection.js index 7ef9f627f..4461dad06 100644 --- a/UI/Missing/Collection.js +++ b/UI/Missing/Collection.js @@ -5,7 +5,7 @@ define( 'backbone.pageable' ], function (EpisodeModel, PagableCollection) { return PagableCollection.extend({ - url : window.ApiRoot + '/missing', + url : window.NzbDrone.ApiRoot + '/missing', model: EpisodeModel, state: { diff --git a/UI/Mixins/AutoComplete.js b/UI/Mixins/AutoComplete.js index 6da4c82b1..90856cb20 100644 --- a/UI/Mixins/AutoComplete.js +++ b/UI/Mixins/AutoComplete.js @@ -6,7 +6,7 @@ define(function () { $(this).typeahead({ source : function (filter, callback) { $.ajax({ - url : window.ApiRoot + resource, + url : window.NzbDrone.ApiRoot + resource, dataType: 'json', type : 'GET', data : { query: filter }, diff --git a/UI/Mixins/backbone.ajax.js b/UI/Mixins/backbone.ajax.js index 5bfbe6d13..0544b2e28 100644 --- a/UI/Mixins/backbone.ajax.js +++ b/UI/Mixins/backbone.ajax.js @@ -11,7 +11,7 @@ define(function () { //check if ajax call was made with data option if (xhr && xhr.data && xhr.type === 'DELETE') { - if (xhr.url.indexOf('?') === -1) { + if (!xhr.url.contains('?')) { xhr.url = xhr.url + '?' + $.param(xhr.data); } else { diff --git a/UI/Mixins/backbone.signalr.mixin.js b/UI/Mixins/backbone.signalr.mixin.js index 39b309f2a..8f5c695ae 100644 --- a/UI/Mixins/backbone.signalr.mixin.js +++ b/UI/Mixins/backbone.signalr.mixin.js @@ -5,59 +5,29 @@ define( ], function () { _.extend(Backbone.Collection.prototype, { - bindSignalR: function (options) { + bindSignalR: function () { - if (!options) { - options = {}; - } + var collection = this; - if (!options.url) { - console.assert(this.url, 'url must be provided or collection must have url'); - options.url = this.url.replace('api', 'signalr'); - } + var processMessage = function (options) { - var self = this; - - var _getStatus = function (status) { - switch (status) { - case 0: - return 'connecting'; - case 1: - return 'connected'; - case 2: - return 'reconnecting'; - case 4: - return 'disconnected'; - default: - throw 'invalid status ' + status; - } + var model = new collection.model(options.resource, {parse: true}); + collection.add(model, {merge: true}); + console.log(options.action + ": %O", options.resource); }; - this.signalRconnection = $.connection(options.url); - - this.signalRconnection.stateChanged(function (change) { - console.debug('{0} [{1}]'.format(options.url, _getStatus(change.newState))); - }); - - this.signalRconnection.received(function (message) { - console.debug(message); - self.fetch(); - }); - - this.signalRconnection.start({ transport: + require( [ - 'longPolling' - ] }); + 'app' + ], function (app) { + collection.listenTo(app.vent, 'server:' + collection.url.replace('/api/', ''), processMessage) + }); return this; }, unbindSignalR: function () { - if(this.signalRconnection){ - this.signalRconnection.stop(); - delete this.signalRconnection; - } }}); }); diff --git a/UI/ProgressMessaging/ProgressMessageCollection.js b/UI/ProgressMessaging/ProgressMessageCollection.js new file mode 100644 index 000000000..ca74fb638 --- /dev/null +++ b/UI/ProgressMessaging/ProgressMessageCollection.js @@ -0,0 +1,47 @@ +'use strict'; +define( + [ + 'app', + 'backbone', + 'Shared/Messenger', + 'Mixins/backbone.signalr.mixin' + ], function (App, Backbone, Messenger) { + + var ProgressMessageCollection = Backbone.Collection.extend({ + url : window.NzbDrone.ApiRoot + '/progressmessage', + model: Backbone.Model, + + initialize: function(){ + + } + + }); + + var collection = new ProgressMessageCollection();//.bindSignalR(); + + /* collection.signalRconnection.received(function (message) { + + var type = getMessengerType(message.status); + var hideAfter = type === 'info' ? 60 :5; + + Messenger.show({ + id : message.commandId, + message : message.message, + type : type, + hideAfter: hideAfter + }); + });*/ + + var getMessengerType = function (status) { + switch (status) { + case 'completed': + return 'success'; + case 'failed': + return 'error'; + default: + return 'info'; + } + }; + + return collection; + }); diff --git a/UI/Quality/QualityProfileCollection.js b/UI/Quality/QualityProfileCollection.js index db24cd24b..dbed578d5 100644 --- a/UI/Quality/QualityProfileCollection.js +++ b/UI/Quality/QualityProfileCollection.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict'; define( [ 'backbone', @@ -7,7 +7,7 @@ define( var QualityProfileCollection = Backbone.Collection.extend({ model: QualityProfileModel, - url : window.ApiRoot + '/qualityprofiles' + url : window.NzbDrone.ApiRoot + '/qualityprofiles' }); var profiles = new QualityProfileCollection(); diff --git a/UI/Quality/QualitySizeCollection.js b/UI/Quality/QualitySizeCollection.js index 4919f5ac7..b29fc62f7 100644 --- a/UI/Quality/QualitySizeCollection.js +++ b/UI/Quality/QualitySizeCollection.js @@ -5,6 +5,6 @@ define( ], function (QualitySizeModel) { return Backbone.Collection.extend({ model: QualitySizeModel, - url : window.ApiRoot + '/qualitysize' + url : window.NzbDrone.ApiRoot + '/qualitysize' }); }); diff --git a/UI/Release/Collection.js b/UI/Release/Collection.js index e584f35d0..93ec60c15 100644 --- a/UI/Release/Collection.js +++ b/UI/Release/Collection.js @@ -5,7 +5,7 @@ define( 'backbone.pageable' ], function (ReleaseModel, PagableCollection) { return PagableCollection.extend({ - url : window.ApiRoot + '/release', + url : window.NzbDrone.ApiRoot + '/release', model: ReleaseModel, mode: 'client', diff --git a/UI/Router.js b/UI/Router.js index 76513c471..de2111644 100644 --- a/UI/Router.js +++ b/UI/Router.js @@ -5,10 +5,12 @@ require( 'marionette', 'Controller', 'Series/SeriesCollection', + 'ProgressMessaging/ProgressMessageCollection', + 'Commands/CommandMessengerCollectionView', 'Navbar/NavbarView', 'jQuery/RouteBinder', 'jquery' - ], function (App, Marionette, Controller, SeriesCollection, NavbarView, RouterBinder, $) { + ], function (App, Marionette, Controller, SeriesCollection, ProgressMessageCollection, CommandMessengerCollectionView, NavbarView, RouterBinder, $) { var Router = Marionette.AppRouter.extend({ @@ -38,11 +40,11 @@ require( App.Router = new Router(); SeriesCollection.fetch().done(function () { - Backbone.history.start({ pushState: true }); - RouterBinder.bind(App.Router); - App.navbarRegion.show(new NavbarView()); - $('body').addClass('started'); - }) + Backbone.history.start({ pushState: true }); + RouterBinder.bind(App.Router); + App.navbarRegion.show(new NavbarView()); + $('body').addClass('started'); + }); }); return App.Router; diff --git a/UI/SeasonPass/Layout.js b/UI/SeasonPass/Layout.js index cb4500daf..ebf2609cf 100644 --- a/UI/SeasonPass/Layout.js +++ b/UI/SeasonPass/Layout.js @@ -24,16 +24,10 @@ define( this.series.show(new LoadingView()); this.seriesCollection = SeriesCollection; - this.seasonCollection = new SeasonCollection(); - var promise = this.seasonCollection.fetch(); - - promise.done(function () { - self.series.show(new SeriesCollectionView({ - collection: self.seriesCollection, - seasonCollection: self.seasonCollection - })); - }); + self.series.show(new SeriesCollectionView({ + collection: self.seriesCollection + })); } }); }); diff --git a/UI/SeasonPass/SeriesCollectionView.js b/UI/SeasonPass/SeriesCollectionView.js index 48cf5c39e..d5de2530e 100644 --- a/UI/SeasonPass/SeriesCollectionView.js +++ b/UI/SeasonPass/SeriesCollectionView.js @@ -5,22 +5,6 @@ define( 'SeasonPass/SeriesLayout' ], function (Marionette, SeriesLayout) { return Marionette.CollectionView.extend({ - - itemView: SeriesLayout, - - initialize: function (options) { - - if (!options.seasonCollection) { - throw 'seasonCollection is needed'; - } - - this.seasonCollection = options.seasonCollection; - }, - - itemViewOptions: function () { - return { - seasonCollection: this.seasonCollection - }; - } + itemView: SeriesLayout }); }); diff --git a/UI/SeasonPass/SeriesLayout.js b/UI/SeasonPass/SeriesLayout.js index 5de36f404..1eae0ce0e 100644 --- a/UI/SeasonPass/SeriesLayout.js +++ b/UI/SeasonPass/SeriesLayout.js @@ -3,10 +3,8 @@ define( [ 'marionette', 'backgrid', - 'Series/SeasonCollection', - 'Cells/ToggleCell', - 'Shared/Actioneer' - ], function (Marionette, Backgrid, SeasonCollection, ToggleCell, Actioneer) { + 'Series/SeasonCollection' + ], function (Marionette, Backgrid, SeasonCollection) { return Marionette.Layout.extend({ template: 'SeasonPass/SeriesLayoutTemplate', @@ -19,48 +17,22 @@ define( events: { 'change .x-season-select': '_seasonSelected', 'click .x-expander' : '_expand', - 'click .x-latest' : '_latest' + 'click .x-latest' : '_latest', + 'click .x-monitored' : '_toggleSeasonMonitored' }, regions: { seasonGrid: '.x-season-grid' }, - columns: - [ - { - name : 'monitored', - label : '', - cell : ToggleCell, - trueClass : 'icon-bookmark', - falseClass: 'icon-bookmark-empty', - tooltip : 'Toggle monitored status', - sortable : false - }, - { - name : 'seasonNumber', - label: 'Season', - cell : Backgrid.IntegerCell.extend({ - className: 'season-number-cell' - }) - } - ], - - initialize: function (options) { - this.seasonCollection = options.seasonCollection.bySeries(this.model.get('id')); - this.model.set('seasons', this.seasonCollection); + initialize: function () { + this.seasonCollection = new SeasonCollection(this.model.get('seasons')); this.expanded = false; }, onRender: function () { - this.seasonGrid.show(new Backgrid.Grid({ - columns : this.columns, - collection: this.seasonCollection, - className : 'table table-condensed season-grid span5' - })); - if (!this.expanded) { - this.seasonGrid.$el.hide(); + this.ui.seasonGrid.hide(); } this._setExpanderIcon(); @@ -103,31 +75,49 @@ define( }, _latest: function () { - var season = _.max(this.seasonCollection.models, function (model) { - return model.get('seasonNumber'); + var season = _.max(this.model.get('seasons'), function (s) { + return s.seasonNumber; }); - //var seasonNumber = season.get('seasonNumber'); - - this._setMonitored(season.get('seasonNumber')) + this._setMonitored(season.seasonNumber); }, _setMonitored: function (seasonNumber) { var self = this; - var promise = $.ajax({ - url: this.seasonCollection.url + '/pass', - type: 'POST', - data: { - seriesId: this.model.get('id'), - seasonNumber: seasonNumber - } - }); + this.model.setSeasonPass(seasonNumber); + + var promise = this.model.save(); promise.done(function (data) { self.seasonCollection = new SeasonCollection(data); self.render(); }); + }, + + _toggleSeasonMonitored: function (e) { + var seasonNumber = 0; + var element; + + if (e.target.localName === 'i') { + seasonNumber = parseInt($(e.target).parent('td').attr('data-season-number')); + element = $(e.target); + } + + else { + seasonNumber = parseInt($(e.target).attr('data-season-number')); + element = $(e.target).children('i'); + } + + this.model.setSeasonMonitored(seasonNumber); + + var savePromise =this.model.save() + .always(this.render.bind(this)); + element.spinForPromise(savePromise); + }, + + _afterToggleSeasonMonitored: function () { + this.render(); } }); }); diff --git a/UI/SeasonPass/SeriesLayoutTemplate.html b/UI/SeasonPass/SeriesLayoutTemplate.html index 026718493..9c12a4483 100644 --- a/UI/SeasonPass/SeriesLayoutTemplate.html +++ b/UI/SeasonPass/SeriesLayoutTemplate.html @@ -12,12 +12,12 @@ <span class="span3"> <select class="x-season-select season-select"> <option value="-1">Select season...</option> - {{#each seasons.models}} - {{#if_eq attributes.seasonNumber compare="0"}} - <option value="{{attributes.seasonNumber}}">Specials</option> - {{else}} - <option value="{{attributes.seasonNumber}}">Season {{attributes.seasonNumber}}</option> - {{/if_eq}} + {{#each seasons}} + {{#if_eq seasonNumber compare="0"}} + <option value="{{seasonNumber}}">Specials</option> + {{else}} + <option value="{{seasonNumber}}">Season {{seasonNumber}}</option> + {{/if_eq}} {{/each}} </select> @@ -36,7 +36,36 @@ <div class="row"> <div class="span11"> - <div class="x-season-grid season-grid"></div> + <div class="x-season-grid season-grid"> + <table class="table table-striped"> + <thead> + <tr> + <th></th> + <th class="sortable">Season</th> + </tr> + </thead> + <tbody> + {{#each seasons}} + <tr> + <td class="toggle-cell x-monitored" data-season-number="{{seasonNumber}}"> + {{#if monitored}} + <i class="icon-nd-monitored"></i> + {{else}} + <i class="icon-nd-unmonitored"></i> + {{/if}} + </td> + <td> + {{#if_eq seasonNumber compare="0"}} + Specials + {{else}} + Season {{seasonNumber}} + {{/if_eq}} + </td> + </tr> + {{/each}} + </tbody> + </table> + </div> </div> </div> </div> diff --git a/UI/Series/Details/InfoView.js b/UI/Series/Details/InfoView.js index c3b5a6a31..60389b17d 100644 --- a/UI/Series/Details/InfoView.js +++ b/UI/Series/Details/InfoView.js @@ -5,6 +5,10 @@ define( ], function (Marionette) { return Marionette.ItemView.extend({ - template: 'Series/Details/InfoViewTemplate' + template: 'Series/Details/InfoViewTemplate', + + initialize: function () { + this.listenTo(this.model, 'change', this.render); + } }); }); diff --git a/UI/Series/Details/SeasonLayout.js b/UI/Series/Details/SeasonLayout.js index 1244050e8..91b4d8d1c 100644 --- a/UI/Series/Details/SeasonLayout.js +++ b/UI/Series/Details/SeasonLayout.js @@ -1,6 +1,7 @@ 'use strict'; define( [ + 'app', 'marionette', 'backgrid', 'Cells/ToggleCell', @@ -8,8 +9,8 @@ define( 'Cells/RelativeDateCell', 'Cells/EpisodeStatusCell', 'Commands/CommandController', - 'Shared/Actioneer' - ], function ( Marionette, Backgrid, ToggleCell, EpisodeTitleCell, RelativeDateCell, EpisodeStatusCell, CommandController, Actioneer) { + 'moment' + ], function (App, Marionette, Backgrid, ToggleCell, EpisodeTitleCell, RelativeDateCell, EpisodeStatusCell, CommandController, Moment) { return Marionette.Layout.extend({ template: 'Series/Details/SeasonLayoutTemplate', @@ -20,13 +21,15 @@ define( }, events: { - 'click .x-season-search' : '_seasonSearch', - 'click .x-season-monitored': '_seasonMonitored', - 'click .x-season-rename' : '_seasonRename' + 'click .x-season-monitored' : '_seasonMonitored', + 'click .x-season-search' : '_seasonSearch', + 'click .x-season-rename' : '_seasonRename', + 'click .x-show-hide-episodes': '_showHideEpisodes', + 'dblclick .series-season h2' : '_showHideEpisodes' }, regions: { - episodeGrid: '#x-episode-grid' + episodeGrid: '.x-episode-grid' }, columns: @@ -48,11 +51,11 @@ define( }) }, { - name : 'this', - label : 'Title', - hideSeriesLink : true, - cell : EpisodeTitleCell, - sortable: false + name : 'this', + label : 'Title', + hideSeriesLink: true, + cell : EpisodeTitleCell, + sortable : false }, { name : 'airDateUtc', @@ -74,48 +77,68 @@ define( } this.episodeCollection = options.episodeCollection.bySeason(this.model.get('seasonNumber')); + this.series = options.series; - this.listenTo(this.model, 'sync', function () { - this._afterSeasonMonitored(); - }, this); + this.showingEpisodes = this._shouldShowEpisodes(); - this.listenTo(this.episodeCollection, 'sync', function () { - this.render(); - }, this); + this.listenTo(this.model, 'sync', this._afterSeasonMonitored); + this.listenTo(this.episodeCollection, 'sync', this.render); }, onRender: function () { - this.episodeGrid.show(new Backgrid.Grid({ - columns : this.columns, - collection: this.episodeCollection, - className : 'table table-hover season-grid' - })); + + + if (this.showingEpisodes) { + this._showEpisodes(); + } this._setSeasonMonitoredState(); + + CommandController.bindToCommand({ + element: this.ui.seasonSearch, + command: { + name : 'seasonSearch', + seriesId : this.series.id, + seasonNumber: this.model.get('seasonNumber') + } + }); + + CommandController.bindToCommand({ + element: this.ui.seasonRename, + command: { + name : 'renameSeason', + seriesId : this.series.id, + seasonNumber: this.model.get('seasonNumber') + } + }); }, _seasonSearch: function () { - Actioneer.ExecuteCommand({ - command : 'seasonSearch', - properties : { - seriesId : this.model.get('seriesId'), - seasonNumber: this.model.get('seasonNumber') - }, - element : this.ui.seasonSearch, - failMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')), - startMessage: 'Search for season {0} started'.format(this.model.get('seasonNumber')) + + CommandController.Execute('seasonSearch', { + name : 'seasonSearch', + seriesId : this.series.id, + seasonNumber: this.model.get('seasonNumber') + }); + }, + + _seasonRename: function () { + + CommandController.Execute('renameSeason', { + name : 'renameSeason', + seriesId : this.series.id, + seasonNumber: this.model.get('seasonNumber') }); }, _seasonMonitored: function () { var name = 'monitored'; this.model.set(name, !this.model.get(name)); + this.series.setSeasonMonitored(this.model.get('seasonNumber')); - Actioneer.SaveModel({ - context: this, - element: this.ui.seasonMonitored, - always : this._afterSeasonMonitored - }); + var savePromise = this.series.save().always(this._afterSeasonMonitored.bind(this)); + + this.ui.seasonMonitored.spinForPromise(savePromise); }, _afterSeasonMonitored: function () { @@ -141,16 +164,72 @@ define( } }, - _seasonRename: function () { - Actioneer.ExecuteCommand({ - command : 'renameSeason', - properties : { - seriesId : this.model.get('seriesId'), - seasonNumber: this.model.get('seasonNumber') - }, - element : this.ui.seasonRename, - failMessage: 'Season rename failed' + + _afterRename: function () { + App.vent.trigger(App.Events.SeasonRenamed, { series: this.series, seasonNumber: this.model.get('seasonNumber') }); + }, + + _showEpisodes: function () { + this.episodeGrid.show(new Backgrid.Grid({ + columns : this.columns, + collection: this.episodeCollection, + className : 'table table-hover season-grid' + })); + }, + + _shouldShowEpisodes: function () { + var startDate = Moment().add('month', -1); + var endDate = Moment().add('year', 1); + + return this.episodeCollection.some(function (episode) { + + var airDate = episode.get('airDateUtc'); + + if (airDate) { + var airDateMoment = Moment(airDate); + + if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) { + return true; + } + } + + return false; }); + }, + + templateHelpers: function () { + + var episodeCount = this.episodeCollection.filter(function (episode) { + return episode.get('hasFile') || (episode.get('monitored') && Moment(episode.get('airDateUtc')).isBefore(Moment())); + }).length; + + var episodeFileCount = this.episodeCollection.where({ hasFile: true }).length; + var percentOfEpisodes = 100; + + if (episodeCount > 0) { + percentOfEpisodes = episodeFileCount / episodeCount * 100; + } + + return { + showingEpisodes : this.showingEpisodes, + episodeCount : episodeCount, + episodeFileCount : episodeFileCount, + percentOfEpisodes: percentOfEpisodes + }; + }, + + _showHideEpisodes: function () { + if (this.showingEpisodes) { + this.showingEpisodes = false; + this.episodeGrid.close(); + } + else { + this.showingEpisodes = true; + this._showEpisodes(); + } + + this.templateHelpers.showingEpisodes = this.showingEpisodes; + this.render(); } }); }); diff --git a/UI/Series/Details/SeasonLayoutTemplate.html b/UI/Series/Details/SeasonLayoutTemplate.html index eb02bf938..4b401c2e6 100644 --- a/UI/Series/Details/SeasonLayoutTemplate.html +++ b/UI/Series/Details/SeasonLayoutTemplate.html @@ -1,15 +1,42 @@ <div class="series-season" id="season-{{seasonNumber}}"> <h2> <i class="x-season-monitored clickable" title="Toggle season monitored status"/> + {{#if seasonNumber}} - Season {{seasonNumber}} + Season {{seasonNumber}} {{else}} - Specials + Specials {{/if}} + + {{#if_eq episodeCount compare=0}} + <i class="icon-nd-status season-status status-primary" title="No aired episodes"/> + {{else}} + {{#if_eq percentOfEpisodes compare=100}} + <i class="icon-nd-status season-status status-success" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/> + {{else}} + <i class="icon-nd-status season-status status-danger" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/> + {{/if_eq}} + {{/if_eq}} + <span class="season-actions pull-right"> - <i class="icon-nd-rename x-season-rename" title="Rename all episodes in season {{seasonNumber}}"/> - <i class="icon-search x-season-search" title="Search for all episodes in season {{seasonNumber}}"/> + <div class="x-season-rename"> + <i class="icon-nd-rename" title="Rename all episodes in season {{seasonNumber}}"/> + </div> + <div class="x-season-search"> + <i class="icon-search" title="Search for all episodes in season {{seasonNumber}}"/> + </div> </span> </h2> - <div id="x-episode-grid"/> + <div class="x-episode-grid"></div> + <div class="show-hide-episodes x-show-hide-episodes"> + <h4> + {{#if showingEpisodes}} + <i class="icon-chevron-sign-up"/> + Hide Episodes + {{else}} + <i class="icon-chevron-sign-down"/> + Show Episodes + {{/if}} + </h4> + </div> </div> diff --git a/UI/Series/Details/SeasonMenu/CollectionView.js b/UI/Series/Details/SeasonMenu/CollectionView.js deleted file mode 100644 index 32cfafd60..000000000 --- a/UI/Series/Details/SeasonMenu/CollectionView.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; -define( - [ - 'marionette', - 'Series/Details/SeasonMenu/ItemView' - ], function (Marionette, ItemView) { - return Marionette.CollectionView.extend({ - - itemView: ItemView, - - initialize: function (options) { - - if (!options.episodeCollection) { - throw 'episodeCollection is needed'; - } - - this.episodeCollection = options.episodeCollection; - }, - - itemViewOptions: function () { - return { - episodeCollection: this.episodeCollection - }; - }, - - appendHtml: function(collectionView, itemView){ - var childrenContainer = $(collectionView.childrenContainer || collectionView.el); - childrenContainer.prepend(itemView.el); - } - }); - }); diff --git a/UI/Series/Details/SeasonMenu/ItemView.js b/UI/Series/Details/SeasonMenu/ItemView.js deleted file mode 100644 index 2ffe52418..000000000 --- a/UI/Series/Details/SeasonMenu/ItemView.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict'; -define( - [ - 'marionette', - 'Shared/Actioneer' - ], function (Marionette, Actioneer) { - return Marionette.ItemView.extend({ - template: 'Series/Details/SeasonMenu/ItemViewTemplate', - tagName : 'span', - - ui: { - seasonMonitored: '.x-season-monitored' - }, - - events: { - 'click .x-season-monitored': '_seasonMonitored', - 'click .x-text': '_gotoSeason' - }, - - initialize: function (options) { - - if (!options.episodeCollection) { - throw 'episodeCollection is needed'; - } - - this.episodeCollection = options.episodeCollection.bySeason(this.model.get('seasonNumber')); - - var allDownloaded = _.all(this.episodeCollection.models, function (model) { - var hasFile = model.get('hasFile'); - return hasFile; - }); - - this.model.set({ - allFilesDownloaded: allDownloaded - }); - - this.listenTo(this.model, 'sync', function () { - this.render(); - }, this); - }, - - onRender: function () { - this._setSeasonMonitoredState(); - }, - - _seasonSearch: function () { - Actioneer.ExecuteCommand({ - command : 'seasonSearch', - properties : { - seriesId : this.model.get('seriesId'), - seasonNumber: this.model.get('seasonNumber') - }, - element : this.ui.seasonSearch, - failMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')), - startMessage: 'Search for season {0} started'.format(this.model.get('seasonNumber')) - }); - }, - - _seasonMonitored: function (e) { - e.preventDefault(); - - var name = 'monitored'; - this.model.set(name, !this.model.get(name)); - - Actioneer.SaveModel({ - context : this, - element : this.ui.seasonMonitored - }); - }, - - _setSeasonMonitoredState: function () { - this.ui.seasonMonitored.removeClass('icon-spinner icon-spin'); - - if (this.model.get('monitored')) { - this.ui.seasonMonitored.addClass('icon-bookmark'); - this.ui.seasonMonitored.removeClass('icon-bookmark-empty'); - } - else { - this.ui.seasonMonitored.addClass('icon-bookmark-empty'); - this.ui.seasonMonitored.removeClass('icon-bookmark'); - } - }, - - _gotoSeason: function () { - window.location.hash = '#season-' + this.model.get('seasonNumber'); - } - }); - }); diff --git a/UI/Series/Details/SeasonMenu/ItemViewTemplate.html b/UI/Series/Details/SeasonMenu/ItemViewTemplate.html deleted file mode 100644 index 8b0283ff6..000000000 --- a/UI/Series/Details/SeasonMenu/ItemViewTemplate.html +++ /dev/null @@ -1,14 +0,0 @@ -{{#if allFilesDownloaded}} -<span class="label label-info season-menu-item"> -{{else}} -<span class="label label-white season-menu-item"> -{{/if}} - <i class="x-season-monitored clickable" title="Toggle season monitored status"/> - <span class="x-text text" title="Go to season"> - {{#if_eq seasonNumber compare=0}} - Specials - {{else}} - S{{Pad2 seasonNumber}} - {{/if_eq}} - </span> -</span> \ No newline at end of file diff --git a/UI/Series/Details/SeriesDetailsLayout.js b/UI/Series/Details/SeriesDetailsLayout.js index 8f073cc96..ec6fc38c9 100644 --- a/UI/Series/Details/SeriesDetailsLayout.js +++ b/UI/Series/Details/SeriesDetailsLayout.js @@ -7,31 +7,20 @@ define( 'Series/EpisodeFileCollection', 'Series/SeasonCollection', 'Series/Details/SeasonCollectionView', - 'Series/Details/SeasonMenu/CollectionView', 'Series/Details/InfoView', + 'Commands/CommandController', 'Shared/LoadingView', - 'Shared/Actioneer', 'backstrech', 'Mixins/backbone.signalr.mixin' - ], function (App, - Marionette, - EpisodeCollection, - EpisodeFileCollection, - SeasonCollection, - SeasonCollectionView, - SeasonMenuCollectionView, - InfoView, - LoadingView, - Actioneer) { + ], function (App, Marionette, EpisodeCollection, EpisodeFileCollection, SeasonCollection, SeasonCollectionView, InfoView, CommandController, LoadingView) { return Marionette.Layout.extend({ itemViewContainer: '.x-series-seasons', template : 'Series/Details/SeriesDetailsTemplate', regions: { - seasonMenu: '#season-menu', - seasons : '#seasons', - info : '#info' + seasons: '#seasons', + info : '#info' }, ui: { @@ -54,12 +43,9 @@ define( initialize: function () { $('body').addClass('backdrop'); - this.listenTo(this.model, 'sync', function () { - this._setMonitoredState(); - this._showInfo(); - }, this); - + this.listenTo(this.model, 'change:monitored', this._setMonitoredState); this.listenTo(App.vent, App.Events.SeriesDeleted, this._onSeriesDeleted); + this.listenTo(App.vent, App.Events.SeasonRenamed, this._onSeasonRenamed); }, onShow: function () { @@ -75,6 +61,31 @@ define( this._showSeasons(); this._setMonitoredState(); this._showInfo(); + + + }, + + onRender: function () { + CommandController.bindToCommand({ + element: this.ui.refresh, + command: { + name: 'refreshSeries' + } + }); + + CommandController.bindToCommand({ + element: this.ui.search, + command: { + name: 'seriesSearch' + } + }); + + CommandController.bindToCommand({ + element: this.ui.rename, + command: { + name: 'renameSeries' + } + }); }, _getFanArt: function () { @@ -99,28 +110,25 @@ define( }, _toggleMonitored: function () { - var name = 'monitored'; - this.model.set(name, !this.model.get(name), { silent: true }); - - Actioneer.SaveModel({ - context: this, - element: this.ui.monitored, - always : this._setMonitoredState() + var savePromise = this.model.save('monitored', !this.model.get('monitored'), { + wait: true }); + + this.ui.monitored.spinForPromise(savePromise); }, _setMonitoredState: function () { var monitored = this.model.get('monitored'); - this.ui.monitored.removeClass('icon-spin icon-spinner'); + this.ui.monitored.removeAttr('data-idle-icon'); - if (this.model.get('monitored')) { - this.ui.monitored.addClass('icon-bookmark'); - this.ui.monitored.removeClass('icon-bookmark-empty'); + if (monitored) { + this.ui.monitored.addClass('icon-nd-monitored'); + this.ui.monitored.removeClass('icon-nd-unmonitored'); } else { - this.ui.monitored.addClass('icon-bookmark-empty'); - this.ui.monitored.removeClass('icon-bookmark'); + this.ui.monitored.addClass('icon-nd-unmonitored'); + this.ui.monitored.removeClass('icon-nd-monitored'); } }, @@ -129,15 +137,9 @@ define( }, _refreshSeries: function () { - Actioneer.ExecuteCommand({ - command : 'refreshSeries', - properties: { - seriesId: this.model.get('id') - }, - element : this.ui.refresh, - leaveIcon : true, - context : this, - onSuccess : this._showSeasons + CommandController.Execute('refreshSeries', { + name : 'refreshSeries', + seriesId: this.model.id }); }, @@ -149,27 +151,18 @@ define( }, _renameSeries: function () { - Actioneer.ExecuteCommand({ - command : 'renameSeries', - properties : { - seriesId: this.model.get('id') - }, - element : this.ui.rename, - context : this, - onSuccess : this._refetchEpisodeFiles, - failMessage: 'Series search failed' + + CommandController.Execute('renameSeries', { + name : 'renameSeries', + seriesId: this.model.id }); + }, _seriesSearch: function () { - Actioneer.ExecuteCommand({ - command : 'seriesSearch', - properties : { - seriesId: this.model.get('id') - }, - element : this.ui.search, - failMessage : 'Series search failed', - startMessage: 'Search for {0} started'.format(this.model.get('title')) + CommandController.Execute('seriesSearch', { + name : 'seriesSearch', + seriesId: this.model.id }); }, @@ -178,32 +171,22 @@ define( this.seasons.show(new LoadingView()); - this.seasonCollection = new SeasonCollection(); + this.seasonCollection = new SeasonCollection(this.model.get('seasons')); this.episodeCollection = new EpisodeCollection({ seriesId: this.model.id }); this.episodeFileCollection = new EpisodeFileCollection({ seriesId: this.model.id }); - $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch(), this.seasonCollection.fetch({data: { seriesId: this.model.id }})).done(function () { + $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function () { var seasonCollectionView = new SeasonCollectionView({ collection : self.seasonCollection, episodeCollection: self.episodeCollection, series : self.model }); - App.reqres.setHandler(App.Reqres.GetEpisodeFileById, function(episodeFileId){ + App.reqres.setHandler(App.Reqres.GetEpisodeFileById, function (episodeFileId) { return self.episodeFileCollection.get(episodeFileId); }); - /* self.episodeCollection.bindSignalR({ - onReceived: seasonCollectionView.onEpisodeGrabbed, - context : seasonCollectionView - });*/ - self.seasons.show(seasonCollectionView); - - self.seasonMenu.show(new SeasonMenuCollectionView({ - collection : self.seasonCollection, - episodeCollection: self.episodeCollection - })); }); }, @@ -211,8 +194,10 @@ define( this.info.show(new InfoView({ model: this.model })); }, - _refetchEpisodeFiles: function () { - this.episodeFileCollection.fetch(); + _onSeasonRenamed: function (event) { + if (this.model.get('id') === event.series.get('id')) { + this.episodeFileCollection.fetch(); + } } }); }); diff --git a/UI/Series/Details/SeriesDetailsTemplate.html b/UI/Series/Details/SeriesDetailsTemplate.html index cef95f4e3..1aa4ed9fe 100644 --- a/UI/Series/Details/SeriesDetailsTemplate.html +++ b/UI/Series/Details/SeriesDetailsTemplate.html @@ -2,14 +2,23 @@ <div class="span11"> <div class="row"> <h1> - <i class="icon-bookmark x-monitored clickable" title="Toggle monitored state for entire series"/> + <i class="x-monitored clickable series-monitor-toggle" title="Toggle monitored state for entire series"/> {{title}} - <span class="series-actions pull-right"> - <i class="icon-refresh x-refresh" title="Update series info and scan disk"/> - <i class="icon-nd-rename x-rename" title="Rename all episodes in this series"/> - <i class="icon-search x-search" title="Search for all episodes in this series"/> - <i class="icon-nd-edit x-edit" title="Edit series"/> - </span> + <div class="series-actions pull-right"> + <div class="x-refresh"> + <i class="icon-refresh icon-can-spin" title="Update series info and scan disk"/> + </div> + <div class="x-rename"> + <i class="icon-nd-rename" title="Rename all episodes in this series"/> + </div> + <div class="x-search"> + <i class="icon-search" title="Search for all episodes in this series"/> + </div> + <div class="x-edit"> + <i class="icon-nd-edit" title="Edit series"/> + </div> + + </div> </h1> </div> <div class="row series-detail-overview"> @@ -18,9 +27,6 @@ <div id="info" class="row"> </div> - <div class="row"> - <div id="season-menu" class="season-menu"></div> - </div> </div> </div> <div id="seasons"></div> diff --git a/UI/Series/EpisodeCollection.js b/UI/Series/EpisodeCollection.js index 52aa024e9..3bd6d9979 100644 --- a/UI/Series/EpisodeCollection.js +++ b/UI/Series/EpisodeCollection.js @@ -5,7 +5,7 @@ define( 'Series/EpisodeModel' ], function (Backbone, EpisodeModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/episodes', + url : window.NzbDrone.ApiRoot + '/episodes', model: EpisodeModel, state: { diff --git a/UI/Series/EpisodeFileCollection.js b/UI/Series/EpisodeFileCollection.js index 2cac898ea..5cd65aaad 100644 --- a/UI/Series/EpisodeFileCollection.js +++ b/UI/Series/EpisodeFileCollection.js @@ -5,7 +5,7 @@ define( 'Series/EpisodeFileModel' ], function (Backbone, EpisodeFileModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/episodefile', + url : window.NzbDrone.ApiRoot + '/episodefile', model: EpisodeFileModel, originalFetch: Backbone.Collection.prototype.fetch, diff --git a/UI/Series/Index/EpisodeProgressPartial.html b/UI/Series/Index/EpisodeProgressPartial.html index b4f5931e8..51ff6588d 100644 --- a/UI/Series/Index/EpisodeProgressPartial.html +++ b/UI/Series/Index/EpisodeProgressPartial.html @@ -1,12 +1,12 @@ {{#if_eq episodeFileCount compare=episodeCount}} {{#if_eq status compare="continuing"}} - <div class="progress"> + <div class="progress episode-progress"> {{else}} - <div class="progress progress-success"> + <div class="progress progress-success episode-progress"> {{/if_eq}} {{else}} - <div class="progress progress-danger"> + <div class="progress progress-danger episode-progress"> {{/if_eq}} <span class="progressbar-back-text">{{episodeFileCount}} / {{episodeCount}}</span> diff --git a/UI/Series/Index/SeriesIndexLayout.js b/UI/Series/Index/SeriesIndexLayout.js index b35234589..8b7a48c12 100644 --- a/UI/Series/Index/SeriesIndexLayout.js +++ b/UI/Series/Index/SeriesIndexLayout.js @@ -101,11 +101,15 @@ define( icon : 'icon-plus', route: 'addseries' }, + { + title : 'Season Pass', + icon : 'icon-bookmark', + route : 'seasonpass' + }, { title : 'RSS Sync', icon : 'icon-rss', command : 'rsssync', - successMessage: 'RSS Sync Completed', errorMessage : 'RSS Sync Failed!' }, { @@ -140,7 +144,6 @@ define( this._fetchCollection(); }, - initialize: function () { this.seriesCollection = SeriesCollection; @@ -148,7 +151,6 @@ define( this.listenTo(SeriesCollection, 'remove', this._renderView); }, - _renderView: function () { if (SeriesCollection.length === 0) { @@ -164,7 +166,6 @@ define( } }, - onShow: function () { this._showToolbar(); this._renderView(); diff --git a/UI/Series/SeasonCollection.js b/UI/Series/SeasonCollection.js index 68e312ff1..35984204a 100644 --- a/UI/Series/SeasonCollection.js +++ b/UI/Series/SeasonCollection.js @@ -5,21 +5,10 @@ define( 'Series/SeasonModel' ], function (Backbone, SeasonModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/season', model: SeasonModel, comparator: function (season) { return -season.get('seasonNumber'); - }, - - bySeries: function (series) { - var filtered = this.filter(function (season) { - return season.get('seriesId') === series; - }); - - var SeasonCollection = require('Series/SeasonCollection'); - - return new SeasonCollection(filtered); } }); }); diff --git a/UI/Series/SeriesCollection.js b/UI/Series/SeriesCollection.js index 7c61ac73a..188382775 100644 --- a/UI/Series/SeriesCollection.js +++ b/UI/Series/SeriesCollection.js @@ -5,7 +5,7 @@ define( 'Series/SeriesModel' ], function (Backbone, SeriesModel) { var Collection = Backbone.Collection.extend({ - url : window.ApiRoot + '/series', + url : window.NzbDrone.ApiRoot + '/series', model: SeriesModel, comparator: function (model) { diff --git a/UI/Series/SeriesModel.js b/UI/Series/SeriesModel.js index 075e0fc80..e73d9cc33 100644 --- a/UI/Series/SeriesModel.js +++ b/UI/Series/SeriesModel.js @@ -7,13 +7,32 @@ define( ], function (Backbone, _) { return Backbone.Model.extend({ - urlRoot: ApiRoot + '/series', + urlRoot: window.NzbDrone.ApiRoot + '/series', defaults: { episodeFileCount: 0, episodeCount : 0, isExisting : false, status : 0 + }, + + setSeasonMonitored: function (seasonNumber) { + _.each(this.get('seasons'), function (season) { + if (season.seasonNumber === seasonNumber) { + season.monitored = !season.monitored; + } + }); + }, + + setSeasonPass: function (seasonNumber) { + _.each(this.get('seasons'), function (season) { + if (season.seasonNumber >= seasonNumber) { + season.monitored = true; + } + else { + season.monitored = false; + } + }); } }); }); diff --git a/UI/Series/series.less b/UI/Series/series.less index f405ea210..e80f978b1 100644 --- a/UI/Series/series.less +++ b/UI/Series/series.less @@ -6,18 +6,18 @@ overflow : visible; .series-poster { - padding-left: 20px; - width : 168px; + padding-left : 20px; + width : 168px; } .form-horizontal { - margin-top: 10px; + margin-top : 10px; } } .delete-series-modal { .path { - margin-left: 30px; + margin-left : 30px; } } @@ -48,26 +48,16 @@ .series-season { .card; - margin : 80px 10px; - padding : 20px 40px; + margin : 30px 10px; + padding : 10px 25px; .opacity(0.9); -} - -.season-menu { - margin-top: 5px; - - .season-menu-item { - - .text { - .clickable; - } - a:hover { - text-decoration: none; - } + .show-hide-episodes { + .clickable(); + text-align : center; - i:before { - width: 10px; + i { + .clickable(); } } } @@ -94,24 +84,24 @@ text-align : center; .progress { - text-align: left; + text-align : left; margin-top : 5px; - left: 0px; - width: 170px; + left : 0px; + width : 170px; .progressbar-front-text { - width: 170px; + width : 170px; } } } .title-container { - position: relative; + position : relative; .title { position : absolute; - top : -100px; - opacity: 0.0; + top : -100px; + opacity : 0.0; } } @@ -198,7 +188,7 @@ .episode-detail-modal { .episode-info { - margin-bottom: 10px; + margin-bottom : 10px; } .episode-overview { @@ -231,44 +221,56 @@ } .season-actions, .series-actions { - font-size : 24px; + + div { + display : inline-block + } + text-transform : none; i { .clickable; + font-size : 24px; + padding-left : 5px; } } .series-stats { - font-size: 11px; + font-size : 11px; } .series-legend { - padding-top: 5px; + padding-top : 5px; } .seasonpass-series { .card; - margin : 20px 0px; + margin : 20px 0px; .title { - font-weight: 300; - font-size: 24px; - line-height: 30px; - margin-left: 5px; + font-weight : 300; + font-size : 24px; + line-height : 30px; + margin-left : 5px; } .season-select { - margin-bottom: 0px; + margin-bottom : 0px; } .expander { .clickable; line-height: 30px; margin-left: 8px; + width: 16px; } .season-grid { - margin-top: 10px; + margin-top : 10px; } -} \ No newline at end of file +} + +.season-status { + font-size : 16px; + vertical-align : middle !important; +} diff --git a/UI/ServerStatus.js b/UI/ServerStatus.js index ff01fb00e..1a5da687b 100644 --- a/UI/ServerStatus.js +++ b/UI/ServerStatus.js @@ -1,18 +1,19 @@ -window.ApiRoot = '/api'; +window.NzbDrone = {}; +window.NzbDrone.ApiRoot = '/api'; var statusText = $.ajax({ type : 'GET', - url : window.ApiRoot + '/system/status', + url : window.NzbDrone.ApiRoot + '/system/status', async: false }).responseText; -window.ServerStatus = JSON.parse(statusText); +window.NzbDrone.ServerStatus = JSON.parse(statusText); -var footerText = window.ServerStatus.version; +var footerText = window.NzbDrone.ServerStatus.version; $(document).ready(function () { - if (window.ServerStatus.branch != 'master') { - footerText += '</br>' + window.ServerStatus.branch; + if (window.NzbDrone.ServerStatus.branch != 'master') { + footerText += '</br>' + window.NzbDrone.ServerStatus.branch; } $('#footer-region .version').html(footerText); }); diff --git a/UI/Settings/General/GeneralSettingsModel.js b/UI/Settings/General/GeneralSettingsModel.js index f8854135e..c458cec87 100644 --- a/UI/Settings/General/GeneralSettingsModel.js +++ b/UI/Settings/General/GeneralSettingsModel.js @@ -5,7 +5,7 @@ define( ], function (SettingsModelBase) { return SettingsModelBase.extend({ - url : window.ApiRoot + '/settings/host', + url : window.NzbDrone.ApiRoot + '/settings/host', successMessage: 'General settings saved', errorMessage : 'Failed to save general settings' diff --git a/UI/Settings/General/GeneralTemplate.html b/UI/Settings/General/GeneralTemplate.html index 5c9cf3ebc..bb9de7626 100644 --- a/UI/Settings/General/GeneralTemplate.html +++ b/UI/Settings/General/GeneralTemplate.html @@ -90,4 +90,21 @@ </div> </div> </fieldset> + + {{#unless_eq branch compare="master"}} + <fieldset> + <legend>Development</legend> + <div class="alert"> + <i class="icon-nd-warning"></i> + Don't change anything here unless you know what you are doing. + </div> + <div class="control-group"> + <label class="control-label">Branch</label> + + <div class="controls"> + <input type="text" placeholder="master" name="branch"/> + </div> + </div> + </fieldset> + {{/unless_eq}} </div> diff --git a/UI/Settings/Indexers/Collection.js b/UI/Settings/Indexers/Collection.js index 76a8fbe0e..234c97da7 100644 --- a/UI/Settings/Indexers/Collection.js +++ b/UI/Settings/Indexers/Collection.js @@ -5,7 +5,7 @@ define( 'Form/FormBuilder' ], function (IndexerModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/indexer', + url : window.NzbDrone.ApiRoot + '/indexer', model: IndexerModel }); }); diff --git a/UI/Settings/Indexers/ItemTemplate.html b/UI/Settings/Indexers/ItemTemplate.html index 617fb94aa..87acb9e0b 100644 --- a/UI/Settings/Indexers/ItemTemplate.html +++ b/UI/Settings/Indexers/ItemTemplate.html @@ -3,7 +3,9 @@ <h3>{{name}}</h3> {{#if_eq implementation compare="Newznab"}} <span class="btn-group pull-right"> - <button class="btn btn-mini btn-icon-only x-delete"><i class="icon-nd-delete"/></button> + <button class="btn btn-mini btn-icon-only x-delete"> + <i class="icon-nd-delete"/> + </button> </span> {{/if_eq}} </div> @@ -27,8 +29,9 @@ {{formBuilder}} {{#if_eq name compare="WomblesIndex"}} - <div class="alert"> - <strong>Warning!</strong> Does not support searching - </div> + <div class="alert"> + <i class="icon-nd-warning"></i> + Does not support searching + </div> {{/if_eq}} </div> diff --git a/UI/Settings/MediaManagement/Naming/Model.js b/UI/Settings/MediaManagement/Naming/Model.js index cb8028556..dbb1e0bf7 100644 --- a/UI/Settings/MediaManagement/Naming/Model.js +++ b/UI/Settings/MediaManagement/Naming/Model.js @@ -4,7 +4,7 @@ define( 'Settings/SettingsModelBase' ], function (ModelBase) { return ModelBase.extend({ - url : window.ApiRoot + '/config/naming', + url : window.NzbDrone.ApiRoot + '/config/naming', successMessage: 'MediaManagement settings saved', errorMessage : 'Couldn\'t save naming settings' }); diff --git a/UI/Settings/MediaManagement/Naming/View.js b/UI/Settings/MediaManagement/Naming/View.js index d33b0bbc0..8cd0027d2 100644 --- a/UI/Settings/MediaManagement/Naming/View.js +++ b/UI/Settings/MediaManagement/Naming/View.js @@ -45,7 +45,7 @@ define( var promise = $.ajax({ type: 'GET', - url : window.ApiRoot + '/config/naming/samples', + url : window.NzbDrone.ApiRoot + '/config/naming/samples', data: this.model.toJSON() }); diff --git a/UI/Settings/Notifications/Collection.js b/UI/Settings/Notifications/Collection.js index a8dd48449..00367ffdf 100644 --- a/UI/Settings/Notifications/Collection.js +++ b/UI/Settings/Notifications/Collection.js @@ -4,7 +4,7 @@ define( 'Settings/Notifications/Model' ], function (NotificationModel) { return Backbone.Collection.extend({ - url : window.ApiRoot + '/notification', + url : window.NzbDrone.ApiRoot + '/notification', model: NotificationModel }); }); diff --git a/UI/Settings/Notifications/EditTemplate.html b/UI/Settings/Notifications/EditTemplate.html index dba9e30f9..91e9ecdb7 100644 --- a/UI/Settings/Notifications/EditTemplate.html +++ b/UI/Settings/Notifications/EditTemplate.html @@ -66,7 +66,7 @@ <button class="btn pull-left x-back">back</button> {{/if}} - <button class="btn x-test">test <i class="x-test-icon"/></button> + <button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button> <button class="btn" data-dismiss="modal">cancel</button> <div class="btn-group"> diff --git a/UI/Settings/Notifications/EditView.js b/UI/Settings/Notifications/EditView.js index 312e48b88..244ea8e18 100644 --- a/UI/Settings/Notifications/EditView.js +++ b/UI/Settings/Notifications/EditView.js @@ -10,7 +10,7 @@ define([ 'Mixins/AsModelBoundView', 'Form/FormBuilder' -], function (App, Marionette, NotificationModel, DeleteView, Messenger, CommandController, AsModelBoundView) { +], function (App, Marionette, NotificationModel, DeleteView, Messenger, CommandController, AsModelBoundView) { var model = Marionette.ItemView.extend({ template: 'Settings/Notifications/EditTemplate', @@ -70,41 +70,20 @@ define([ var testCommand = this.model.get('testCommand'); if (testCommand) { this.idle = false; - this.ui.testButton.addClass('disabled'); - this.ui.testIcon.addClass('icon-spinner icon-spin'); - var properties = {}; _.each(this.model.get('fields'), function (field) { properties[field.name] = field.value; }); - var self = this; - var commandPromise = CommandController.Execute(testCommand, properties); - commandPromise.done(function () { - Messenger.show({ - message: 'Notification settings tested successfully' - }); - }); - - commandPromise.fail(function (options) { - if (options.readyState === 0 || options.status === 0) { - return; - } - Messenger.show({ - message: 'Failed to test notification settings', - type : 'error' - }); - }); + CommandController.Execute(testCommand, properties); + } + }, - commandPromise.always(function () { - if (!self.isClosed) { - self.ui.testButton.removeClass('disabled'); - self.ui.testIcon.removeClass('icon-spinner icon-spin'); - self.idle = true; - } - }); + _testOnAlways: function () { + if (!this.isClosed) { + this.idle = true; } } }); diff --git a/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js b/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js index 2a5b80a39..47c3e232d 100644 --- a/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js +++ b/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js @@ -8,6 +8,6 @@ define( return Backbone.Collection.extend({ model: QualityProfileModel, - url : window.ApiRoot + '/qualityprofiles/schema' + url : window.NzbDrone.ApiRoot + '/qualityprofiles/schema' }); }); diff --git a/UI/Settings/SettingsModel.js b/UI/Settings/SettingsModel.js index 4e0c142ec..1fbccdb74 100644 --- a/UI/Settings/SettingsModel.js +++ b/UI/Settings/SettingsModel.js @@ -2,7 +2,7 @@ define(['app', 'Settings/SettingsModelBase'], function (App, SettingsModelBase) { return SettingsModelBase.extend({ - url : window.ApiRoot + '/settings', + url : window.NzbDrone.ApiRoot + '/settings', successMessage: 'Settings saved', errorMessage : 'Failed to save settings' }); diff --git a/UI/Shared/Actioneer.js b/UI/Shared/Actioneer.js deleted file mode 100644 index 929147ff1..000000000 --- a/UI/Shared/Actioneer.js +++ /dev/null @@ -1,95 +0,0 @@ -'use strict'; -define(['Commands/CommandController', 'Shared/Messenger'], - function(CommandController, Messenger) { - return { - ExecuteCommand: function (options) { - options.iconClass = this._getIconClass(options.element); - - this._showStartMessage(options); - this._setSpinnerOnElement(options); - - var promise = CommandController.Execute(options.command, options.properties); - this._handlePromise(promise, options); - }, - - SaveModel: function (options) { - options.iconClass = this._getIconClass(options.element); - - this._showStartMessage(options); - this._setSpinnerOnElement(options); - - var promise = options.context.model.save(); - this._handlePromise(promise, options); - }, - - _handlePromise: function (promise, options) { - promise.done(function () { - if (options.successMessage) { - Messenger.show({ - message: options.successMessage - }); - } - - if (options.onSuccess) { - options.onSuccess.call(options.context); - } - }); - - promise.fail(function (ajaxOptions) { - if (ajaxOptions.readyState === 0 || ajaxOptions.status === 0) { - return; - } - - if (options.failMessage) { - Messenger.show({ - message: options.failMessage, - type : 'error' - }); - } - - if (options.onError) { - options.onError.call(options.context); - } - }); - - promise.always(function () { - - if (options.leaveIcon) { - options.element.removeClass('icon-spin'); - } - - else { - options.element.addClass(options.iconClass); - options.element.removeClass('icon-nd-spinner'); - } - - if (options.always) { - options.always.call(options.context); - } - }); - }, - - _getIconClass: function(element) { - return element.attr('class').match(/(?:^|\s)icon\-.+?(?:$|\s)/)[0]; - }, - - _setSpinnerOnElement: function (options) { - if (options.leaveIcon) { - options.element.addClass('icon-spin'); - } - - else { - options.element.removeClass(options.iconClass); - options.element.addClass('icon-nd-spinner'); - } - }, - - _showStartMessage: function (options) { - if (options.startMessage) { - Messenger.show({ - message: options.startMessage - }); - } - } - } -}); diff --git a/UI/Shared/LoadingView.js b/UI/Shared/LoadingView.js index a10261a42..e551583f2 100644 --- a/UI/Shared/LoadingView.js +++ b/UI/Shared/LoadingView.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict'; define( [ 'marionette' diff --git a/UI/Shared/Messenger.js b/UI/Shared/Messenger.js index 8f04009d9..ab53a619a 100644 --- a/UI/Shared/Messenger.js +++ b/UI/Shared/Messenger.js @@ -1,20 +1,25 @@ 'use strict'; define(function () { return { + show: function (options) { if (!options.type) { options.type = 'info'; } - if (!options.hideAfter) { + if (options.hideAfter === undefined) { switch (options.type) { case 'info': options.hideAfter = 5; break; + case 'success': + options.hideAfter = 5; + break; + default : - options.hideAfter = 0; + options.hideAfter = 5; } } @@ -22,7 +27,8 @@ define(function () { message : options.message, type : options.type, showCloseButton: true, - hideAfter : options.hideAfter + hideAfter : options.hideAfter, + id : options.id }); }, diff --git a/UI/Shared/SignalRBroadcaster.js b/UI/Shared/SignalRBroadcaster.js new file mode 100644 index 000000000..5a58dcb1d --- /dev/null +++ b/UI/Shared/SignalRBroadcaster.js @@ -0,0 +1,51 @@ +'use strict'; +define( + [ + 'app', + 'signalR' + ], function () { + return { + + appInitializer: function () { + console.log('starting signalR'); + + var getStatus = function (status) { + switch (status) { + case 0: + return 'connecting'; + case 1: + return 'connected'; + case 2: + return 'reconnecting'; + case 4: + return 'disconnected'; + default: + throw 'invalid status ' + status; + } + }; + + this.signalRconnection = $.connection("/signalr"); + + this.signalRconnection.stateChanged(function (change) { + console.debug('SignalR: [{0}]'.format(getStatus(change.newState))); + }); + + this.signalRconnection.received(function (message) { + require( + [ + 'app' + ], function (app) { + app.vent.trigger('server:' + message.name, message.body); + }) + }); + + this.signalRconnection.start({ transport: + [ + 'longPolling' + ] }); + + return this; + } + }; + }); + diff --git a/UI/Shared/StringHelpers.js b/UI/Shared/StringHelpers.js deleted file mode 100644 index e5fafb55a..000000000 --- a/UI/Shared/StringHelpers.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -define( - [ - ], function () { - - return { - - startsWith: function (str, starts) { - if (starts === '') { - return true; - } - if (str == null || starts == null) { - return false; - } - str = String(str); - starts = String(starts); - return str.length >= starts.length && str.slice(0, starts.length) === starts; - }, - - endsWith: function (str, ends) { - if (ends === '') { - return true; - } - if (str == null || ends == null) { - return false; - } - str = String(str); - ends = String(ends); - return str.length >= ends.length && str.slice(str.length - ends.length) === ends; - } - } - }); diff --git a/UI/Shared/Toolbar/Button/ButtonView.js b/UI/Shared/Toolbar/Button/ButtonView.js index 32e8b3fbc..1bcd36679 100644 --- a/UI/Shared/Toolbar/Button/ButtonView.js +++ b/UI/Shared/Toolbar/Button/ButtonView.js @@ -3,9 +3,8 @@ define( [ 'app', 'marionette', - 'Commands/CommandController', - 'Shared/Messenger' - ], function (App, Marionette, CommandController, Messenger) { + 'Commands/CommandController' + ], function (App, Marionette, CommandController) { return Marionette.ItemView.extend({ template : 'Shared/Toolbar/ButtonTemplate', @@ -15,14 +14,8 @@ define( 'click': 'onClick' }, - ui: { - icon: '.x-icon' - }, - - initialize: function () { this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key'); - this.idle = true; }, onRender: function () { @@ -31,82 +24,34 @@ define( this.invokeCallback(); } - if(!this.model.get('title')){ + if (!this.model.get('title')) { this.$el.addClass('btn-icon-only'); } + + var command = this.model.get('command'); + if (command) { + CommandController.bindToCommand({ + command: {name: command}, + element: this.$el + }); + } }, onClick: function () { - if (this.idle) { - this.invokeCallback(); - this.invokeRoute(); - this.invokeCommand(); + + if (this.$el.hasClass('disabled')) { + return; } + + this.invokeCallback(); + this.invokeRoute(); + this.invokeCommand(); }, invokeCommand: function () { - //TODO: Use Actioneer to handle icon swapping - var command = this.model.get('command'); if (command) { - this.idle = false; - this.$el.addClass('disabled'); - this.ui.icon.addClass('icon-spinner icon-spin'); - - var self = this; - var commandPromise = CommandController.Execute(command); - commandPromise.done(function () { - if (self.model.get('successMessage')) { - Messenger.show({ - message: self.model.get('successMessage') - }); - } - - if (self.model.get('onSuccess')) { - if (!self.model.ownerContext) { - throw 'ownerContext must be set.'; - } - - self.model.get('onSuccess').call(self.model.ownerContext); - } - }); - - commandPromise.fail(function (options) { - if (options.readyState === 0 || options.status === 0) { - return; - } - - if (self.model.get('errorMessage')) { - Messenger.show({ - message: self.model.get('errorMessage'), - type : 'error' - }); - } - - if (self.model.get('onError')) { - if (!self.model.ownerContext) { - throw 'ownerContext must be set.'; - } - - self.model.get('onError').call(self.model.ownerContext); - } - }); - - commandPromise.always(function () { - if (!self.isClosed) { - self.$el.removeClass('disabled'); - self.ui.icon.removeClass('icon-spinner icon-spin'); - self.idle = true; - } - }); - - if (self.model.get('always')) { - if (!self.model.ownerContext) { - throw 'ownerContext must be set.'; - } - - self.model.get('always').call(self.model.ownerContext); - } + CommandController.Execute(command); } }, @@ -134,7 +79,6 @@ define( callback.call(this.model.ownerContext); } } - }); }); diff --git a/UI/Shared/Toolbar/ToolbarLayout.js b/UI/Shared/Toolbar/ToolbarLayout.js index 701fdf07d..8945337a2 100644 --- a/UI/Shared/Toolbar/ToolbarLayout.js +++ b/UI/Shared/Toolbar/ToolbarLayout.js @@ -30,10 +30,8 @@ define( this.left = options.left; this.right = options.right; this.toolbarContext = options.context; - }, - onShow: function () { if (this.left) { _.each(this.left, this._showToolbarLeft, this); @@ -51,7 +49,6 @@ define( this._showToolbar(element, index, 'right'); }, - _showToolbar: function (buttonGroup, index, position) { var groupCollection = new ButtonCollection(); diff --git a/UI/System/Layout.js b/UI/System/Layout.js index f1d499dc1..390ce48d8 100644 --- a/UI/System/Layout.js +++ b/UI/System/Layout.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict'; define( [ 'app', @@ -6,15 +6,13 @@ define( 'System/StatusModel', 'System/About/View', 'Logs/Layout', - 'Shared/Toolbar/ToolbarLayout', - 'Shared/LoadingView' + 'Shared/Toolbar/ToolbarLayout' ], function (App, Marionette, StatusModel, AboutView, LogsLayout, - ToolbarLayout, - LoadingView) { + ToolbarLayout) { return Marionette.Layout.extend({ template: 'System/LayoutTemplate', @@ -38,21 +36,7 @@ define( title : 'Check for Update', icon : 'icon-nd-update', command: 'applicationUpdate' - }, -// { -// title : 'Restart', -// icon : 'icon-repeat', -// command : 'restart', -// successMessage: 'NzbDrone restart has been triggered', -// errorMessage : 'Failed to restart NzbDrone' -// }, -// { -// title : 'Shutdown', -// icon : 'icon-power-off', -// command : 'shutdown', -// successMessage: 'NzbDrone shutdown has started', -// errorMessage : 'Failed to shutdown NzbDrone' -// } + } ] }, diff --git a/UI/System/StatusModel.js b/UI/System/StatusModel.js index 8fabd1e55..20805cec7 100644 --- a/UI/System/StatusModel.js +++ b/UI/System/StatusModel.js @@ -5,7 +5,7 @@ define( ], function (Backbone) { var model = Backbone.Model.extend({ - url: window.ApiRoot + '/system/status' + url: window.NzbDrone.ApiRoot + '/system/status' }); diff --git a/UI/app.js b/UI/app.js index c3ba25d6f..280a16d39 100644 --- a/UI/app.js +++ b/UI/app.js @@ -1,7 +1,7 @@ 'use strict'; require.config({ - urlArgs: 'v=' + window.ServerStatus.version, + urlArgs: 'v=' + window.NzbDrone.ServerStatus.version, paths: { 'backbone' : 'JsLibraries/backbone', @@ -181,14 +181,16 @@ require.config({ define( [ 'marionette', + 'Shared/SignalRBroadcaster', 'Instrumentation/StringFormat' - ], function (Marionette) { + ], function (Marionette, SignalRBroadcaster) { var app = new Marionette.Application(); app.Events = { SeriesAdded : 'series:added', - SeriesDeleted: 'series:deleted' + SeriesDeleted: 'series:deleted', + SeasonRenamed: 'season:renamed' }; app.Commands = { @@ -209,6 +211,8 @@ define( console.log('starting application'); }); + app.addInitializer(SignalRBroadcaster.appInitializer, {app: app}); + app.addRegions({ navbarRegion: '#nav-region', mainRegion : '#main-region', diff --git a/UI/index.html b/UI/index.html index 5a4659fa5..5ffff693e 100644 --- a/UI/index.html +++ b/UI/index.html @@ -60,6 +60,7 @@ </div> </footer> </body> +<script src="/polyfills.js"></script> <script src="/JsLibraries/jquery.js"></script> <script src="/JsLibraries/messenger.js"></script> <script src="/ServerStatus.js"></script> diff --git a/UI/jQuery/RouteBinder.js b/UI/jQuery/RouteBinder.js index 7b599cc88..70ffc8742 100644 --- a/UI/jQuery/RouteBinder.js +++ b/UI/jQuery/RouteBinder.js @@ -1,5 +1,5 @@ 'use strict'; -define(['Shared/StringHelpers'],function (StringHelpers) { +define(function () { //This module will automatically route all relative links through backbone router rather than //causing links to reload pages. @@ -40,7 +40,7 @@ define(['Shared/StringHelpers'],function (StringHelpers) { } - if (!StringHelpers.startsWith(href, 'http')) { + if (!href.startsWith('http')) { router.navigate(href, { trigger: true }); } diff --git a/UI/jQuery/jquery.spin.js b/UI/jQuery/jquery.spin.js new file mode 100644 index 000000000..17fe52edf --- /dev/null +++ b/UI/jQuery/jquery.spin.js @@ -0,0 +1,58 @@ +define( + [ + 'jquery' + ], function ($) { + 'use strict'; + + $.fn.spinForPromise = function (promise) { + var self = this; + + if (!promise || promise.state() !== 'pending') { + return this; + } + promise.always(function () { + self.stopSpin(); + }); + + return this.startSpin(); + }; + + $.fn.startSpin = function () { + + var icon = this.find('i').andSelf('i'); + + var iconClasses = icon.attr('class').match(/(?:^|\s)icon\-.+?(?:$|\s)/); + + if (iconClasses.length === 0) { + return this; + } + + var iconClass = $.trim(iconClasses[0]); + + this.addClass('disabled'); + + if (icon.hasClass('icon-can-spin')) { + icon.addClass('icon-spin'); + } + else { + icon.attr('data-idle-icon', iconClass); + icon.removeClass(iconClass); + icon.addClass('icon-nd-spinner'); + } + + return this; + }; + + $.fn.stopSpin = function () { + var icon = this.find('i').andSelf('i'); + + this.removeClass('disabled'); + icon.removeClass('icon-spin icon-nd-spinner'); + var idleIcon = icon.attr('data-idle-icon'); + + if (idleIcon) { + icon.addClass(idleIcon); + } + return this; + }; + }); diff --git a/UI/polyfills.js b/UI/polyfills.js new file mode 100644 index 000000000..3e6797bf8 --- /dev/null +++ b/UI/polyfills.js @@ -0,0 +1,32 @@ +if (!String.prototype.startsWith) { + Object.defineProperty(String.prototype, 'startsWith', { + enumerable: false, + configurable: false, + writable: false, + value: function (searchString, position) { + position = position || 0; + return this.indexOf(searchString, position) === position; + } + }); +} + +if (!String.prototype.endsWith) { + Object.defineProperty(String.prototype, 'endsWith', { + enumerable: false, + configurable: false, + writable: false, + value: function (searchString, position) { + position = position || this.length; + position = position - searchString.length; + var lastIndex = this.lastIndexOf(searchString); + return lastIndex !== -1 && lastIndex === position; + } + }); +} + +if(!('contains' in String.prototype)) +{ + String.prototype.contains = function(str, startIndex) { + return -1 !== String.prototype.indexOf.call(this, str, startIndex); + }; +} \ No newline at end of file