Purge V2 API

This commit is contained in:
Qstick 2021-07-28 22:16:15 -04:00 committed by Mark McDowall
parent fdecd1fea4
commit b4ca4908fc
110 changed files with 28 additions and 5964 deletions
frontend/src/Calendar/iCal
src/NzbDrone.Api
Blacklist
Blocklist
Calendar
Commands
Config
DiskSpace
DownloadClient
EpisodeFiles
Episodes
FileSystem
Health
History
Indexers
Logs
ManualImport
MediaCovers
Metadata
Notifications
NzbDroneApiModule.csNzbDroneFeedModule.cs
Parse
Profiles
ProviderModuleBase.csProviderResource.cs
Qualities
Queue
RemotePathMappings
Restrictions
RootFolders
SeasonPass
Series
Sonarr.Api.csproj
System
Tags

View File

@ -22,7 +22,7 @@ function getUrls(state) {
tags
} = state;
let icalUrl = `${window.location.host}${window.Sonarr.urlBase}/feed/calendar/Sonarr.ics?`;
let icalUrl = `${window.location.host}${window.Sonarr.urlBase}/feed/v3/calendar/Sonarr.ics?`;
if (unmonitored) {
icalUrl += 'unmonitored=true&';

View File

@ -1,32 +0,0 @@
// Blacklist has been deprecated for blocklist.
using NzbDrone.Api.Blocklist;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Datastore;
using Sonarr.Http;
namespace NzbDrone.Api.Blacklist
{
public class BlacklistModule : SonarrRestModule<BlocklistResource>
{
private readonly BlocklistService _blocklistService;
public BlacklistModule(BlocklistService blocklistService)
{
_blocklistService = blocklistService;
GetResourcePaged = Blocklist;
DeleteResource = DeleteBlockList;
}
private PagingResource<BlocklistResource> Blocklist(PagingResource<BlocklistResource> pagingResource)
{
var pagingSpec = pagingResource.MapToPagingSpec<BlocklistResource, Core.Blocklisting.Blocklist>("id", SortDirection.Ascending);
return ApplyToPage(_blocklistService.Paged, pagingSpec, BlocklistResourceMapper.MapToResource);
}
private void DeleteBlockList(int id)
{
_blocklistService.Delete(id);
}
}
}

View File

@ -1,30 +0,0 @@
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Datastore;
using Sonarr.Http;
namespace NzbDrone.Api.Blocklist
{
public class BlocklistModule : SonarrRestModule<BlocklistResource>
{
private readonly BlocklistService _blocklistService;
public BlocklistModule(BlocklistService blocklistService)
{
_blocklistService = blocklistService;
GetResourcePaged = Blocklist;
DeleteResource = DeleteBlockList;
}
private PagingResource<BlocklistResource> Blocklist(PagingResource<BlocklistResource> pagingResource)
{
var pagingSpec = pagingResource.MapToPagingSpec<BlocklistResource, Core.Blocklisting.Blocklist>("id", SortDirection.Ascending);
return ApplyToPage(_blocklistService.Paged, pagingSpec, BlocklistResourceMapper.MapToResource);
}
private void DeleteBlockList(int id)
{
_blocklistService.Delete(id);
}
}
}

View File

@ -1,49 +0,0 @@
using System;
using System.Collections.Generic;
using Sonarr.Http.REST;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Languages;
namespace NzbDrone.Api.Blocklist
{
public class BlocklistResource : RestResource
{
public int SeriesId { get; set; }
public List<int> EpisodeIds { get; set; }
public string SourceTitle { get; set; }
public QualityModel Quality { get; set; }
public DateTime Date { get; set; }
public DownloadProtocol Protocol { get; set; }
public string Indexer { get; set; }
public string Message { get; set; }
public Language Language { get; set; }
public SeriesResource Series { get; set; }
}
public static class BlocklistResourceMapper
{
public static BlocklistResource MapToResource(this Core.Blocklisting.Blocklist model)
{
if (model == null) return null;
return new BlocklistResource
{
Id = model.Id,
SeriesId = model.SeriesId,
EpisodeIds = model.EpisodeIds,
SourceTitle = model.SourceTitle,
Quality = model.Quality,
Date = model.Date,
Protocol = model.Protocol,
Indexer = model.Indexer,
Message = model.Message,
Series = model.Series.ToResource()
};
}
}
}

View File

@ -1,150 +0,0 @@
using Nancy;
using System;
using System.Collections.Generic;
using System.Linq;
using Ical.Net;
using Ical.Net.CalendarComponents;
using Ical.Net.DataTypes;
using Ical.Net.Serialization;
using NzbDrone.Core.Tv;
using Nancy.Responses;
using NzbDrone.Core.Tags;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.Calendar
{
public class CalendarFeedModule : NzbDroneFeedModule
{
private readonly IEpisodeService _episodeService;
private readonly ITagService _tagService;
public CalendarFeedModule(IEpisodeService episodeService, ITagService tagService)
: base("calendar")
{
_episodeService = episodeService;
_tagService = tagService;
Get("/NzbDrone.ics", options => GetCalendarFeed());
Get("/Sonarr.ics", options => GetCalendarFeed());
}
private object GetCalendarFeed()
{
var pastDays = 7;
var futureDays = 28;
var start = DateTime.Today.AddDays(-pastDays);
var end = DateTime.Today.AddDays(futureDays);
var unmonitored = false;
var premieresOnly = false;
var asAllDay = false;
var tags = new List<int>();
// TODO: Remove start/end parameters in v3, they don't work well for iCal
var queryStart = Request.Query.Start;
var queryEnd = Request.Query.End;
var queryPastDays = Request.Query.PastDays;
var queryFutureDays = Request.Query.FutureDays;
var queryUnmonitored = Request.Query.Unmonitored;
var queryPremieresOnly = Request.Query.PremieresOnly;
var queryPremiersOnly = Request.Query.PremiersOnly;
var queryAsAllDay = Request.Query.AsAllDay;
var queryTags = Request.Query.Tags;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
if (queryPastDays.HasValue)
{
pastDays = int.Parse(queryPastDays.Value);
start = DateTime.Today.AddDays(-pastDays);
}
if (queryFutureDays.HasValue)
{
futureDays = int.Parse(queryFutureDays.Value);
end = DateTime.Today.AddDays(futureDays);
}
if (queryUnmonitored.HasValue)
{
unmonitored = bool.Parse(queryUnmonitored.Value);
}
if (queryPremieresOnly.HasValue)
{
premieresOnly = bool.Parse(queryPremieresOnly.Value);
}
else if (queryPremiersOnly.HasValue)
{
// There was a typo, recognize mistyped 'premiersOnly' boolean too for background compat.
premieresOnly = bool.Parse(queryPremiersOnly.Value);
}
if (queryAsAllDay.HasValue)
{
asAllDay = bool.Parse(queryAsAllDay.Value);
}
if (queryTags.HasValue)
{
var tagInput = (string)queryTags.Value.ToString();
tags.AddRange(tagInput.Split(',').Select(_tagService.GetTag).Select(t => t.Id));
}
var episodes = _episodeService.EpisodesBetweenDates(start, end, unmonitored);
var calendar = new Ical.Net.Calendar
{
ProductId = "-//sonarr.tv//Sonarr//EN"
};
var calendarName = "Sonarr TV Schedule";
calendar.AddProperty(new CalendarProperty("NAME", calendarName));
calendar.AddProperty(new CalendarProperty("X-WR-CALNAME", calendarName));
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
{
if (premieresOnly && (episode.SeasonNumber == 0 || episode.EpisodeNumber != 1))
{
continue;
}
if (tags.Any() && tags.None(episode.Series.Tags.Contains))
{
continue;
}
var occurrence = calendar.Create<CalendarEvent>();
occurrence.Uid = "NzbDrone_episode_" + episode.Id;
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
occurrence.Description = episode.Overview;
occurrence.Categories = new List<string>() { episode.Series.Network };
if (asAllDay)
{
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value.ToLocalTime()) { HasTime = false };
}
else
{
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = true };
occurrence.End = new CalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime)) { HasTime = true };
}
switch (episode.Series.SeriesType)
{
case SeriesTypes.Daily:
occurrence.Summary = $"{episode.Series.Title} - {episode.Title}";
break;
default:
occurrence.Summary =$"{episode.Series.Title} - {episode.SeasonNumber}x{episode.EpisodeNumber:00} - {episode.Title}";
break;
}
}
var serializer = (IStringSerializer) new SerializerFactory().Build(calendar.GetType(), new SerializationContext());
var icalendar = serializer.SerializeToString(calendar);
return new TextResponse(icalendar, "text/calendar");
}
}
}

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.Episodes;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Calendar
{
public class CalendarModule : EpisodeModuleWithSignalR
{
public CalendarModule(IEpisodeService episodeService,
ISeriesService seriesService,
IUpgradableSpecification upgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster, "calendar")
{
GetResourceAll = GetCalendar;
}
private List<EpisodeResource> GetCalendar()
{
var start = DateTime.Today;
var end = DateTime.Today.AddDays(2);
var includeUnmonitored = false;
var queryStart = Request.Query.Start;
var queryEnd = Request.Query.End;
var queryIncludeUnmonitored = Request.Query.Unmonitored;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.Value);
var resources = MapToResource(_episodeService.EpisodesBetweenDates(start, end, includeUnmonitored), true, true);
return resources.OrderBy(e => e.AirDateUtc).ToList();
}
}
}

View File

@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Sonarr.Http.Extensions;
using NzbDrone.Common;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ProgressMessaging;
using NzbDrone.SignalR;
using Sonarr.Http;
using Sonarr.Http.Validation;
namespace NzbDrone.Api.Commands
{
public class CommandModule : SonarrRestModuleWithSignalR<CommandResource, CommandModel>, IHandle<CommandUpdatedEvent>
{
private readonly IManageCommandQueue _commandQueueManager;
private readonly IServiceFactory _serviceFactory;
private readonly Debouncer _debouncer;
private readonly Dictionary<int, CommandResource> _pendingUpdates;
public CommandModule(IManageCommandQueue commandQueueManager,
IBroadcastSignalRMessage signalRBroadcaster,
IServiceFactory serviceFactory)
: base(signalRBroadcaster)
{
_commandQueueManager = commandQueueManager;
_serviceFactory = serviceFactory;
GetResourceById = GetCommand;
CreateResource = StartCommand;
GetResourceAll = GetStartedCommands;
PostValidator.RuleFor(c => c.Name).NotBlank();
_debouncer = new Debouncer(SendUpdates, TimeSpan.FromSeconds(0.1));
_pendingUpdates = new Dictionary<int, CommandResource>();
}
private CommandResource GetCommand(int id)
{
return _commandQueueManager.Get(id).ToResource();
}
private int StartCommand(CommandResource commandResource)
{
var commandType = _serviceFactory.GetImplementations(typeof(Command))
.Single(c => c.Name.Replace("Command", "").Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
dynamic command = Request.Body.FromJson(commandType);
command.Trigger = CommandTrigger.Manual;
var trackedCommand = _commandQueueManager.Push(command, CommandPriority.Normal, CommandTrigger.Manual);
return trackedCommand.Id;
}
private List<CommandResource> GetStartedCommands()
{
return _commandQueueManager.GetStarted().ToResource();
}
public void Handle(CommandUpdatedEvent message)
{
if (message.Command.Body.SendUpdatesToClient)
{
lock (_pendingUpdates)
{
_pendingUpdates[message.Command.Id] = message.Command.ToResource();
}
_debouncer.Execute();
}
}
private void SendUpdates()
{
lock (_pendingUpdates)
{
var pendingUpdates = _pendingUpdates.Values.ToArray();
_pendingUpdates.Clear();
foreach (var pendingUpdate in pendingUpdates)
{
BroadcastResourceChange(ModelAction.Updated, pendingUpdate);
}
}
}
}
}

View File

@ -1,135 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Sonarr.Http.REST;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Common.Http;
namespace NzbDrone.Api.Commands
{
public class CommandResource : RestResource
{
public string Name { get; set; }
public string Message { get; set; }
public object Body { get; set; }
public CommandPriority Priority { get; set; }
public CommandStatus Status { get; set; }
public DateTime Queued { get; set; }
public DateTime? Started { get; set; }
public DateTime? Ended { get; set; }
public TimeSpan? Duration { get; set; }
public string Exception { get; set; }
public CommandTrigger Trigger { get; set; }
public string ClientUserAgent { get; set; }
[JsonIgnore]
public string CompletionMessage { get; set; }
//Legacy
public CommandStatus State
{
get
{
return Status;
}
set { }
}
public bool Manual
{
get
{
return Trigger == CommandTrigger.Manual;
}
set { }
}
public DateTime StartedOn
{
get
{
return Queued;
}
set { }
}
public DateTime? StateChangeTime
{
get
{
if (Started.HasValue) return Started.Value;
return Ended;
}
set { }
}
public bool SendUpdatesToClient
{
get
{
if (Body != null) return (Body as Command).SendUpdatesToClient;
return false;
}
set { }
}
public bool UpdateScheduledTask
{
get
{
if (Body != null) return (Body as Command).UpdateScheduledTask;
return false;
}
set { }
}
public DateTime? LastExecutionTime { get; set; }
}
public static class CommandResourceMapper
{
public static CommandResource ToResource(this CommandModel model)
{
if (model == null) return null;
return new CommandResource
{
Id = model.Id,
Name = model.Name,
Message = model.Message,
Body = model.Body,
Priority = model.Priority,
Status = model.Status,
Queued = model.QueuedAt,
Started = model.StartedAt,
Ended = model.EndedAt,
Duration = model.Duration,
Exception = model.Exception,
Trigger = model.Trigger,
ClientUserAgent = UserAgentParser.SimplifyUserAgent(model.Body.ClientUserAgent),
CompletionMessage = model.Body.CompletionMessage,
LastExecutionTime = model.Body.LastExecutionTime
};
}
public static List<CommandResource> ToResource(this IEnumerable<CommandModel> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,20 +0,0 @@
using FluentValidation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Api.Config
{
public class DownloadClientConfigModule : NzbDroneConfigModule<DownloadClientConfigResource>
{
public DownloadClientConfigModule(IConfigService configService)
: base(configService)
{
}
protected override DownloadClientConfigResource ToResource(IConfigService model)
{
return DownloadClientConfigResourceMapper.ToResource(model);
}
}
}

View File

@ -1,27 +0,0 @@
using Sonarr.Http.REST;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Config
{
public class DownloadClientConfigResource : RestResource
{
public string DownloadClientWorkingFolders { get; set; }
public bool EnableCompletedDownloadHandling { get; set; }
public bool AutoRedownloadFailed { get; set; }
}
public static class DownloadClientConfigResourceMapper
{
public static DownloadClientConfigResource ToResource(IConfigService model)
{
return new DownloadClientConfigResource
{
DownloadClientWorkingFolders = model.DownloadClientWorkingFolders,
EnableCompletedDownloadHandling = model.EnableCompletedDownloadHandling,
AutoRedownloadFailed = model.AutoRedownloadFailed
};
}
}
}

View File

@ -1,91 +0,0 @@
using System.IO;
using System.Linq;
using System.Reflection;
using FluentValidation;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Update;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using Sonarr.Http;
namespace NzbDrone.Api.Config
{
public class HostConfigModule : SonarrRestModule<HostConfigResource>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IConfigService _configService;
private readonly IUserService _userService;
public HostConfigModule(IConfigFileProvider configFileProvider, IConfigService configService, IUserService userService)
: base("/config/host")
{
_configFileProvider = configFileProvider;
_configService = configService;
_userService = userService;
GetResourceSingle = GetHostConfig;
GetResourceById = GetHostConfig;
UpdateResource = SaveHostConfig;
SharedValidator.RuleFor(c => c.BindAddress)
.ValidIp4Address()
.NotListenAllIp4Address()
.When(c => c.BindAddress != "*");
SharedValidator.RuleFor(c => c.Port).ValidPort();
SharedValidator.RuleFor(c => c.UrlBase).ValidUrlBase();
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None);
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None);
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script);
SharedValidator.RuleFor(c => c.BackupFolder).IsValidPath().When(c => Path.IsPathRooted(c.BackupFolder));
SharedValidator.RuleFor(c => c.BackupInterval).InclusiveBetween(1, 7);
SharedValidator.RuleFor(c => c.BackupRetention).InclusiveBetween(1, 90);
}
private HostConfigResource GetHostConfig()
{
var resource = _configFileProvider.ToResource(_configService);
resource.Id = 1;
var user = _userService.FindUser();
if (user != null)
{
resource.Username = user.Username;
resource.Password = user.Password;
}
return resource;
}
private HostConfigResource GetHostConfig(int id)
{
return GetHostConfig();
}
private void SaveHostConfig(HostConfigResource resource)
{
var dictionary = resource.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
_configFileProvider.SaveConfigDictionary(dictionary);
_configService.SaveConfigDictionary(dictionary);
if (resource.Username.IsNotNullOrWhiteSpace() && resource.Password.IsNotNullOrWhiteSpace())
{
_userService.Upsert(resource.Username, resource.Password);
}
}
}
}

View File

@ -1,81 +0,0 @@
using Sonarr.Http.REST;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Update;
using NzbDrone.Common.Http.Proxy;
namespace NzbDrone.Api.Config
{
public class HostConfigResource : RestResource
{
public string BindAddress { get; set; }
public int Port { get; set; }
public int SslPort { get; set; }
public bool EnableSsl { get; set; }
public bool LaunchBrowser { get; set; }
public AuthenticationType AuthenticationMethod { get; set; }
public bool AnalyticsEnabled { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string LogLevel { get; set; }
public string ConsoleLogLevel { get; set; }
public string Branch { get; set; }
public string ApiKey { get; set; }
public string SslCertHash { get; set; }
public string UrlBase { get; set; }
public bool UpdateAutomatically { get; set; }
public UpdateMechanism UpdateMechanism { get; set; }
public string UpdateScriptPath { get; set; }
public bool ProxyEnabled { get; set; }
public ProxyType ProxyType { get; set; }
public string ProxyHostname { get; set; }
public int ProxyPort { get; set; }
public string ProxyUsername { get; set; }
public string ProxyPassword { get; set; }
public string ProxyBypassFilter { get; set; }
public bool ProxyBypassLocalAddresses { get; set; }
public string BackupFolder { get; set; }
public int BackupInterval { get; set; }
public int BackupRetention { get; set; }
}
public static class HostConfigResourceMapper
{
public static HostConfigResource ToResource(this IConfigFileProvider model, IConfigService configService)
{
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead?
return new HostConfigResource
{
BindAddress = model.BindAddress,
Port = model.Port,
SslPort = model.SslPort,
EnableSsl = model.EnableSsl,
LaunchBrowser = model.LaunchBrowser,
AuthenticationMethod = model.AuthenticationMethod,
AnalyticsEnabled = model.AnalyticsEnabled,
//Username
//Password
LogLevel = model.LogLevel,
ConsoleLogLevel = model.ConsoleLogLevel,
Branch = model.Branch,
ApiKey = model.ApiKey,
SslCertHash = model.SslCertHash,
UrlBase = model.UrlBase,
UpdateAutomatically = model.UpdateAutomatically,
UpdateMechanism = model.UpdateMechanism,
UpdateScriptPath = model.UpdateScriptPath,
ProxyEnabled = configService.ProxyEnabled,
ProxyType = configService.ProxyType,
ProxyHostname = configService.ProxyHostname,
ProxyPort = configService.ProxyPort,
ProxyUsername = configService.ProxyUsername,
ProxyPassword = configService.ProxyPassword,
ProxyBypassFilter = configService.ProxyBypassFilter,
ProxyBypassLocalAddresses = configService.ProxyBypassLocalAddresses,
BackupFolder = configService.BackupFolder,
BackupInterval = configService.BackupInterval,
BackupRetention = configService.BackupRetention
};
}
}
}

View File

@ -1,31 +0,0 @@
using FluentValidation;
using NzbDrone.Core.Configuration;
using Sonarr.Http.Validation;
namespace NzbDrone.Api.Config
{
public class IndexerConfigModule : NzbDroneConfigModule<IndexerConfigResource>
{
public IndexerConfigModule(IConfigService configService)
: base(configService)
{
SharedValidator.RuleFor(c => c.MinimumAge)
.GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(c => c.MaximumSize)
.GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(c => c.Retention)
.GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(c => c.RssSyncInterval)
.IsValidRssSyncInterval();
}
protected override IndexerConfigResource ToResource(IConfigService model)
{
return IndexerConfigResourceMapper.ToResource(model);
}
}
}

View File

@ -1,27 +0,0 @@
using Sonarr.Http.REST;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Config
{
public class IndexerConfigResource : RestResource
{
public int MinimumAge { get; set; }
public int MaximumSize { get; set; }
public int Retention { get; set; }
public int RssSyncInterval { get; set; }
}
public static class IndexerConfigResourceMapper
{
public static IndexerConfigResource ToResource(IConfigService model)
{
return new IndexerConfigResource
{
MinimumAge = model.MinimumAge,
MaximumSize = model.MaximumSize,
Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval,
};
}
}
}

View File

@ -1,23 +0,0 @@
using FluentValidation;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Api.Config
{
public class MediaManagementConfigModule : NzbDroneConfigModule<MediaManagementConfigResource>
{
public MediaManagementConfigModule(IConfigService configService, PathExistsValidator pathExistsValidator, FolderChmodValidator folderChmodValidator)
: base(configService)
{
SharedValidator.RuleFor(c => c.ChmodFolder).SetValidator(folderChmodValidator).When(c => !string.IsNullOrEmpty(c.ChmodFolder) && PlatformInfo.IsMono);
SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath().SetValidator(pathExistsValidator).When(c => !string.IsNullOrWhiteSpace(c.RecycleBin));
}
protected override MediaManagementConfigResource ToResource(IConfigService model)
{
return MediaManagementConfigResourceMapper.ToResource(model);
}
}
}

View File

@ -1,53 +0,0 @@
using Sonarr.Http.REST;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Config
{
public class MediaManagementConfigResource : RestResource
{
public bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
public string RecycleBin { get; set; }
public ProperDownloadTypes DownloadPropersAndRepacks { get; set; }
public bool CreateEmptySeriesFolders { get; set; }
public bool DeleteEmptyFolders { get; set; }
public FileDateType FileDate { get; set; }
public bool SetPermissionsLinux { get; set; }
public string ChmodFolder { get; set; }
public string ChownGroup { get; set; }
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
public bool CopyUsingHardlinks { get; set; }
public bool ImportExtraFiles { get; set; }
public string ExtraFileExtensions { get; set; }
public bool EnableMediaInfo { get; set; }
}
public static class MediaManagementConfigResourceMapper
{
public static MediaManagementConfigResource ToResource(IConfigService model)
{
return new MediaManagementConfigResource
{
AutoUnmonitorPreviouslyDownloadedEpisodes = model.AutoUnmonitorPreviouslyDownloadedEpisodes,
RecycleBin = model.RecycleBin,
DownloadPropersAndRepacks = model.DownloadPropersAndRepacks,
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
DeleteEmptyFolders = model.DeleteEmptyFolders,
FileDate = model.FileDate,
SetPermissionsLinux = model.SetPermissionsLinux,
ChmodFolder = model.ChmodFolder,
ChownGroup = model.ChownGroup,
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
CopyUsingHardlinks = model.CopyUsingHardlinks,
ImportExtraFiles = model.ImportExtraFiles,
ExtraFileExtensions = model.ExtraFileExtensions,
EnableMediaInfo = model.EnableMediaInfo
};
}
}
}

View File

@ -1,145 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Organizer;
using Nancy.ModelBinding;
using Sonarr.Http;
namespace NzbDrone.Api.Config
{
public class NamingConfigModule : SonarrRestModule<NamingConfigResource>
{
private readonly INamingConfigService _namingConfigService;
private readonly IFilenameSampleService _filenameSampleService;
private readonly IFilenameValidationService _filenameValidationService;
private readonly IBuildFileNames _filenameBuilder;
public NamingConfigModule(INamingConfigService namingConfigService,
IFilenameSampleService filenameSampleService,
IFilenameValidationService filenameValidationService,
IBuildFileNames filenameBuilder)
: base("config/naming")
{
_namingConfigService = namingConfigService;
_filenameSampleService = filenameSampleService;
_filenameValidationService = filenameValidationService;
_filenameBuilder = filenameBuilder;
GetResourceSingle = GetNamingConfig;
GetResourceById = GetNamingConfig;
UpdateResource = UpdateNamingConfig;
Get("/samples", x => GetExamples(this.Bind<NamingConfigResource>()));
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5);
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
SharedValidator.RuleFor(c => c.SpecialsFolderFormat).ValidSpecialsFolderFormat();
}
private void UpdateNamingConfig(NamingConfigResource resource)
{
var nameSpec = resource.ToModel();
ValidateFormatResult(nameSpec);
_namingConfigService.Save(nameSpec);
}
private NamingConfigResource GetNamingConfig()
{
var nameSpec = _namingConfigService.GetConfig();
var resource = nameSpec.ToResource();
if (resource.StandardEpisodeFormat.IsNotNullOrWhiteSpace())
{
var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec);
basicConfig.AddToResource(resource);
}
return resource;
}
private NamingConfigResource GetNamingConfig(int id)
{
return GetNamingConfig();
}
private object GetExamples(NamingConfigResource config)
{
var nameSpec = config.ToModel();
var sampleResource = new NamingSampleResource();
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
sampleResource.SingleEpisodeExample = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult) != null
? "Invalid format"
: singleEpisodeSampleResult.FileName;
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
? "Invalid format"
: multiEpisodeSampleResult.FileName;
sampleResource.DailyEpisodeExample = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult) != null
? "Invalid format"
: dailyEpisodeSampleResult.FileName;
sampleResource.AnimeEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult) != null
? "Invalid format"
: animeEpisodeSampleResult.FileName;
sampleResource.AnimeMultiEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult) != null
? "Invalid format"
: animeMultiEpisodeSampleResult.FileName;
sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"
: _filenameSampleService.GetSeriesFolderSample(nameSpec);
sampleResource.SeasonFolderExample = nameSpec.SeasonFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
sampleResource.SpecialsFolderExample = nameSpec.SpecialsFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"
: _filenameSampleService.GetSpecialsFolderSample(nameSpec);
return sampleResource;
}
private void ValidateFormatResult(NamingConfig nameSpec)
{
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult);
var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult);
var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult);
var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult);
var animeMultiEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult);
var validationFailures = new List<ValidationFailure>();
validationFailures.AddIfNotNull(singleEpisodeValidationResult);
validationFailures.AddIfNotNull(multiEpisodeValidationResult);
validationFailures.AddIfNotNull(dailyEpisodeValidationResult);
validationFailures.AddIfNotNull(animeEpisodeValidationResult);
validationFailures.AddIfNotNull(animeMultiEpisodeValidationResult);
if (validationFailures.Any())
{
throw new ValidationException(validationFailures.DistinctBy(v => v.PropertyName).ToArray());
}
}
}
}

View File

@ -1,79 +0,0 @@
using Sonarr.Http.REST;
using NzbDrone.Core.Organizer;
namespace NzbDrone.Api.Config
{
public class NamingConfigResource : RestResource
{
public bool RenameEpisodes { get; set; }
public bool ReplaceIllegalCharacters { get; set; }
public int MultiEpisodeStyle { get; set; }
public string StandardEpisodeFormat { get; set; }
public string DailyEpisodeFormat { get; set; }
public string AnimeEpisodeFormat { get; set; }
public string SeriesFolderFormat { get; set; }
public string SeasonFolderFormat { get; set; }
public string SpecialsFolderFormat { get; set; }
public bool IncludeSeriesTitle { get; set; }
public bool IncludeEpisodeTitle { get; set; }
public bool IncludeQuality { get; set; }
public bool ReplaceSpaces { get; set; }
public string Separator { get; set; }
public string NumberStyle { get; set; }
}
public static class NamingConfigResourceMapper
{
public static NamingConfigResource ToResource(this NamingConfig model)
{
return new NamingConfigResource
{
Id = model.Id,
RenameEpisodes = model.RenameEpisodes,
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
MultiEpisodeStyle = model.MultiEpisodeStyle,
StandardEpisodeFormat = model.StandardEpisodeFormat,
DailyEpisodeFormat = model.DailyEpisodeFormat,
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
SeriesFolderFormat = model.SeriesFolderFormat,
SeasonFolderFormat = model.SeasonFolderFormat,
SpecialsFolderFormat = model.SpecialsFolderFormat
//IncludeSeriesTitle
//IncludeEpisodeTitle
//IncludeQuality
//ReplaceSpaces
//Separator
//NumberStyle
};
}
public static void AddToResource(this BasicNamingConfig basicNamingConfig, NamingConfigResource resource)
{
resource.IncludeSeriesTitle = basicNamingConfig.IncludeSeriesTitle;
resource.IncludeEpisodeTitle = basicNamingConfig.IncludeEpisodeTitle;
resource.IncludeQuality = basicNamingConfig.IncludeQuality;
resource.ReplaceSpaces = basicNamingConfig.ReplaceSpaces;
resource.Separator = basicNamingConfig.Separator;
resource.NumberStyle = basicNamingConfig.NumberStyle;
}
public static NamingConfig ToModel(this NamingConfigResource resource)
{
return new NamingConfig
{
Id = resource.Id,
RenameEpisodes = resource.RenameEpisodes,
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
MultiEpisodeStyle = resource.MultiEpisodeStyle,
StandardEpisodeFormat = resource.StandardEpisodeFormat,
DailyEpisodeFormat = resource.DailyEpisodeFormat,
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
SeriesFolderFormat = resource.SeriesFolderFormat,
SeasonFolderFormat = resource.SeasonFolderFormat,
SpecialsFolderFormat = resource.SpecialsFolderFormat
};
}
}
}

View File

@ -1,14 +0,0 @@
namespace NzbDrone.Api.Config
{
public class NamingSampleResource
{
public string SingleEpisodeExample { get; set; }
public string MultiEpisodeExample { get; set; }
public string DailyEpisodeExample { get; set; }
public string AnimeEpisodeExample { get; set; }
public string AnimeMultiEpisodeExample { get; set; }
public string SeriesFolderExample { get; set; }
public string SeasonFolderExample { get; set; }
public string SpecialsFolderExample { get; set; }
}
}

View File

@ -1,52 +0,0 @@
using System.Linq;
using System.Reflection;
using Sonarr.Http.REST;
using NzbDrone.Core.Configuration;
using Sonarr.Http;
namespace NzbDrone.Api.Config
{
public abstract class NzbDroneConfigModule<TResource> : SonarrRestModule<TResource> where TResource : RestResource, new()
{
private readonly IConfigService _configService;
protected NzbDroneConfigModule(IConfigService configService)
: this(new TResource().ResourceName.Replace("config", ""), configService)
{
}
protected NzbDroneConfigModule(string resource, IConfigService configService) :
base("config/" + resource.Trim('/'))
{
_configService = configService;
GetResourceSingle = GetConfig;
GetResourceById = GetConfig;
UpdateResource = SaveConfig;
}
private TResource GetConfig()
{
var resource = ToResource(_configService);
resource.Id = 1;
return resource;
}
protected abstract TResource ToResource(IConfigService model);
private TResource GetConfig(int id)
{
return GetConfig();
}
private void SaveConfig(TResource resource)
{
var dictionary = resource.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
_configService.SaveConfigDictionary(dictionary);
}
}
}

View File

@ -1,18 +0,0 @@
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Config
{
public class UiConfigModule : NzbDroneConfigModule<UiConfigResource>
{
public UiConfigModule(IConfigService configService)
: base(configService)
{
}
protected override UiConfigResource ToResource(IConfigService model)
{
return UiConfigResourceMapper.ToResource(model);
}
}
}

View File

@ -1,39 +0,0 @@
using Sonarr.Http.REST;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Config
{
public class UiConfigResource : RestResource
{
//Calendar
public int FirstDayOfWeek { get; set; }
public string CalendarWeekColumnHeader { get; set; }
//Dates
public string ShortDateFormat { get; set; }
public string LongDateFormat { get; set; }
public string TimeFormat { get; set; }
public bool ShowRelativeDates { get; set; }
public bool EnableColorImpairedMode { get; set; }
}
public static class UiConfigResourceMapper
{
public static UiConfigResource ToResource(IConfigService model)
{
return new UiConfigResource
{
FirstDayOfWeek = model.FirstDayOfWeek,
CalendarWeekColumnHeader = model.CalendarWeekColumnHeader,
ShortDateFormat = model.ShortDateFormat,
LongDateFormat = model.LongDateFormat,
TimeFormat = model.TimeFormat,
ShowRelativeDates = model.ShowRelativeDates,
EnableColorImpairedMode = model.EnableColorImpairedMode,
};
}
}
}

View File

@ -1,24 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.DiskSpace;
using Sonarr.Http;
namespace NzbDrone.Api.DiskSpace
{
public class DiskSpaceModule :SonarrRestModule<DiskSpaceResource>
{
private readonly IDiskSpaceService _diskSpaceService;
public DiskSpaceModule(IDiskSpaceService diskSpaceService)
: base("diskspace")
{
_diskSpaceService = diskSpaceService;
GetResourceAll = GetFreeSpace;
}
public List<DiskSpaceResource> GetFreeSpace()
{
return _diskSpaceService.GetFreeSpace().ConvertAll(DiskSpaceResourceMapper.MapToResource);
}
}
}

View File

@ -1,28 +0,0 @@
using Sonarr.Http.REST;
namespace NzbDrone.Api.DiskSpace
{
public class DiskSpaceResource : RestResource
{
public string Path { get; set; }
public string Label { get; set; }
public long FreeSpace { get; set; }
public long TotalSpace { get; set; }
}
public static class DiskSpaceResourceMapper
{
public static DiskSpaceResource MapToResource(this Core.DiskSpace.DiskSpace model)
{
if (model == null) return null;
return new DiskSpaceResource
{
Path = model.Path,
Label = model.Label,
FreeSpace = model.FreeSpace,
TotalSpace = model.TotalSpace
};
}
}
}

View File

@ -1,34 +0,0 @@
using NzbDrone.Core.Download;
namespace NzbDrone.Api.DownloadClient
{
public class DownloadClientModule : ProviderModuleBase<DownloadClientResource, IDownloadClient, DownloadClientDefinition>
{
public DownloadClientModule(IDownloadClientFactory downloadClientFactory)
: base(downloadClientFactory, "downloadclient")
{
}
protected override void MapToResource(DownloadClientResource resource, DownloadClientDefinition definition)
{
base.MapToResource(resource, definition);
resource.Enable = definition.Enable;
resource.Protocol = definition.Protocol;
resource.Priority = definition.Priority;
resource.RemoveCompletedDownloads = definition.RemoveCompletedDownloads;
resource.RemoveFailedDownloads = definition.RemoveFailedDownloads;
}
protected override void MapToModel(DownloadClientDefinition definition, DownloadClientResource resource)
{
base.MapToModel(definition, resource);
definition.Enable = resource.Enable;
definition.Protocol = resource.Protocol;
definition.Priority = resource.Priority;
definition.RemoveCompletedDownloads = resource.RemoveCompletedDownloads;
definition.RemoveFailedDownloads = resource.RemoveFailedDownloads;
}
}
}

View File

@ -1,13 +0,0 @@
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.DownloadClient
{
public class DownloadClientResource : ProviderResource
{
public bool Enable { get; set; }
public DownloadProtocol Protocol { get; set; }
public int Priority { get; set; }
public bool RemoveCompletedDownloads { get; set; }
public bool RemoveFailedDownloads { get; set; }
}
}

View File

@ -1,90 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Exceptions;
using NzbDrone.SignalR;
using Sonarr.Http;
using HttpStatusCode = System.Net.HttpStatusCode;
namespace NzbDrone.Api.EpisodeFiles
{
public class EpisodeFileModule : SonarrRestModuleWithSignalR<EpisodeFileResource, EpisodeFile>,
IHandle<EpisodeFileAddedEvent>
{
private readonly IMediaFileService _mediaFileService;
private readonly IDeleteMediaFiles _mediaFileDeletionService;
private readonly ISeriesService _seriesService;
private readonly IUpgradableSpecification _upgradableSpecification;
public EpisodeFileModule(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService,
IDeleteMediaFiles mediaFileDeletionService,
ISeriesService seriesService,
IUpgradableSpecification upgradableSpecification)
: base(signalRBroadcaster)
{
_mediaFileService = mediaFileService;
_mediaFileDeletionService = mediaFileDeletionService;
_seriesService = seriesService;
_upgradableSpecification = upgradableSpecification;
GetResourceById = GetEpisodeFile;
GetResourceAll = GetEpisodeFiles;
UpdateResource = SetQuality;
DeleteResource = DeleteEpisodeFile;
}
private EpisodeFileResource GetEpisodeFile(int id)
{
var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
return episodeFile.ToResource(series, _upgradableSpecification);
}
private List<EpisodeFileResource> GetEpisodeFiles()
{
if (!Request.Query.SeriesId.HasValue)
{
throw new BadRequestException("seriesId is missing");
}
var seriesId = (int)Request.Query.SeriesId;
var series = _seriesService.GetSeries(seriesId);
return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _upgradableSpecification));
}
private void SetQuality(EpisodeFileResource episodeFileResource)
{
var episodeFile = _mediaFileService.Get(episodeFileResource.Id);
episodeFile.Quality = episodeFileResource.Quality;
episodeFile.Language = episodeFileResource.Language;
_mediaFileService.Update(episodeFile);
}
private void DeleteEpisodeFile(int id)
{
var episodeFile = _mediaFileService.Get(id);
if (episodeFile == null)
{
throw new NzbDroneClientException(HttpStatusCode.NotFound, "Episode file not found");
}
var series = _seriesService.GetSeries(episodeFile.SeriesId);
_mediaFileDeletionService.DeleteEpisodeFile(series, episodeFile);
}
public void Handle(EpisodeFileAddedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.Id);
}
}
}

View File

@ -1,75 +0,0 @@
using System;
using System.IO;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles;
using Sonarr.Http.REST;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Languages;
namespace NzbDrone.Api.EpisodeFiles
{
public class EpisodeFileResource : RestResource
{
public int SeriesId { get; set; }
public int SeasonNumber { get; set; }
public string RelativePath { get; set; }
public string Path { get; set; }
public long Size { get; set; }
public DateTime DateAdded { get; set; }
public string SceneName { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }
public MediaInfoResource MediaInfo { get; set; }
public string OriginalFilePath { get; set; }
public bool QualityCutoffNotMet { get; set; }
}
public static class EpisodeFileResourceMapper
{
private static EpisodeFileResource ToResource(this EpisodeFile model)
{
if (model == null) return null;
return new EpisodeFileResource
{
Id = model.Id,
SeriesId = model.SeriesId,
SeasonNumber = model.SeasonNumber,
RelativePath = model.RelativePath,
//Path
Size = model.Size,
DateAdded = model.DateAdded,
SceneName = model.SceneName,
Quality = model.Quality,
MediaInfo = model.MediaInfo.ToResource(model.SceneName),
OriginalFilePath = model.OriginalFilePath
};
}
public static EpisodeFileResource ToResource(this EpisodeFile model, Core.Tv.Series series, IUpgradableSpecification upgradableSpecification)
{
if (model == null) return null;
return new EpisodeFileResource
{
Id = model.Id,
SeriesId = model.SeriesId,
SeasonNumber = model.SeasonNumber,
RelativePath = model.RelativePath,
Path = Path.Combine(series.Path, model.RelativePath),
Size = model.Size,
DateAdded = model.DateAdded,
SceneName = model.SceneName,
Quality = model.Quality,
Language = model.Language,
QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(series.QualityProfile.Value, model.Quality),
MediaInfo = model.MediaInfo.ToResource(model.SceneName),
OriginalFilePath = model.OriginalFilePath
};
}
}
}

View File

@ -1,30 +0,0 @@
using NzbDrone.Core.MediaFiles.MediaInfo;
using Sonarr.Http.REST;
namespace NzbDrone.Api.EpisodeFiles
{
public class MediaInfoResource : RestResource
{
public decimal AudioChannels { get; set; }
public string AudioCodec { get; set; }
public string VideoCodec { get; set; }
}
public static class MediaInfoResourceMapper
{
public static MediaInfoResource ToResource(this MediaInfoModel model, string sceneName)
{
if (model == null)
{
return null;
}
return new MediaInfoResource
{
AudioChannels = MediaInfoFormatter.FormatAudioChannels(model),
AudioCodec = MediaInfoFormatter.FormatAudioCodec(model, sceneName),
VideoCodec = MediaInfoFormatter.FormatVideoCodec(model, sceneName)
};
}
}
}

View File

@ -1,41 +0,0 @@
using System.Collections.Generic;
using Sonarr.Http.REST;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Episodes
{
public class EpisodeModule : EpisodeModuleWithSignalR
{
public EpisodeModule(ISeriesService seriesService,
IEpisodeService episodeService,
IUpgradableSpecification upgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster)
{
GetResourceAll = GetEpisodes;
UpdateResource = SetMonitored;
}
private List<EpisodeResource> GetEpisodes()
{
if (!Request.Query.SeriesId.HasValue)
{
throw new BadRequestException("seriesId is missing");
}
var seriesId = (int)Request.Query.SeriesId;
var resources = MapToResource(_episodeService.GetEpisodeBySeries(seriesId), false, true);
return resources;
}
private void SetMonitored(EpisodeResource episodeResource)
{
_episodeService.SetEpisodeMonitored(episodeResource.Id, episodeResource.Monitored);
}
}
}

View File

@ -1,133 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Api.EpisodeFiles;
using NzbDrone.Api.Series;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
using Sonarr.Http;
namespace NzbDrone.Api.Episodes
{
public abstract class EpisodeModuleWithSignalR : SonarrRestModuleWithSignalR<EpisodeResource, Episode>,
IHandle<EpisodeGrabbedEvent>,
IHandle<EpisodeImportedEvent>
{
protected readonly IEpisodeService _episodeService;
protected readonly ISeriesService _seriesService;
protected readonly IUpgradableSpecification _upgradableSpecification;
protected EpisodeModuleWithSignalR(IEpisodeService episodeService,
ISeriesService seriesService,
IUpgradableSpecification upgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(signalRBroadcaster)
{
_episodeService = episodeService;
_seriesService = seriesService;
_upgradableSpecification = upgradableSpecification;
GetResourceById = GetEpisode;
}
protected EpisodeModuleWithSignalR(IEpisodeService episodeService,
ISeriesService seriesService,
IUpgradableSpecification upgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster,
string resource)
: base(signalRBroadcaster, resource)
{
_episodeService = episodeService;
_seriesService = seriesService;
_upgradableSpecification = upgradableSpecification;
GetResourceById = GetEpisode;
}
protected EpisodeResource GetEpisode(int id)
{
var episode = _episodeService.GetEpisode(id);
var resource = MapToResource(episode, true, true);
return resource;
}
protected EpisodeResource MapToResource(Episode episode, bool includeSeries, bool includeEpisodeFile)
{
var resource = episode.ToResource();
if (includeSeries || includeEpisodeFile)
{
var series = episode.Series ?? _seriesService.GetSeries(episode.SeriesId);
if (includeSeries)
{
resource.Series = series.ToResource();
}
if (includeEpisodeFile && episode.EpisodeFileId != 0)
{
resource.EpisodeFile = episode.EpisodeFile.Value.ToResource(series, _upgradableSpecification);
}
}
return resource;
}
protected List<EpisodeResource> MapToResource(List<Episode> episodes, bool includeSeries, bool includeEpisodeFile)
{
var result = episodes.ToResource();
if (includeSeries || includeEpisodeFile)
{
var seriesDict = new Dictionary<int, Core.Tv.Series>();
for (var i = 0; i < episodes.Count; i++)
{
var episode = episodes[i];
var resource = result[i];
var series = episode.Series ?? seriesDict.GetValueOrDefault(episodes[i].SeriesId) ?? _seriesService.GetSeries(episodes[i].SeriesId);
seriesDict[series.Id] = series;
if (includeSeries)
{
resource.Series = series.ToResource();
}
if (includeEpisodeFile && episodes[i].EpisodeFileId != 0)
{
resource.EpisodeFile = episodes[i].EpisodeFile.Value.ToResource(series, _upgradableSpecification);
}
}
}
return result;
}
public void Handle(EpisodeGrabbedEvent message)
{
foreach (var episode in message.Episode.Episodes)
{
var resource = episode.ToResource();
resource.Grabbed = true;
BroadcastResourceChange(ModelAction.Updated, resource);
}
}
public void Handle(EpisodeImportedEvent message)
{
if (!message.NewDownload)
{
return;
}
foreach (var episode in message.EpisodeInfo.Episodes)
{
BroadcastResourceChange(ModelAction.Updated, episode.Id);
}
}
}
}

View File

@ -1,80 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Api.EpisodeFiles;
using Sonarr.Http.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Episodes
{
public class EpisodeResource : RestResource
{
public int SeriesId { get; set; }
public int EpisodeFileId { get; set; }
public int SeasonNumber { get; set; }
public int EpisodeNumber { get; set; }
public string Title { get; set; }
public string AirDate { get; set; }
public DateTime? AirDateUtc { get; set; }
public string Overview { get; set; }
public EpisodeFileResource EpisodeFile { get; set; }
public bool HasFile { get; set; }
public bool Monitored { get; set; }
public int? AbsoluteEpisodeNumber { get; set; }
public int? SceneAbsoluteEpisodeNumber { get; set; }
public int? SceneEpisodeNumber { get; set; }
public int? SceneSeasonNumber { get; set; }
public bool UnverifiedSceneNumbering { get; set; }
public string SeriesTitle { get; set; }
public SeriesResource Series { get; set; }
public DateTime? LastSearchTime { get; set; }
//Hiding this so people don't think its usable (only used to set the initial state)
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool Grabbed { get; set; }
}
public static class EpisodeResourceMapper
{
public static EpisodeResource ToResource(this Episode model)
{
if (model == null) return null;
return new EpisodeResource
{
Id = model.Id,
SeriesId = model.SeriesId,
EpisodeFileId = model.EpisodeFileId,
SeasonNumber = model.SeasonNumber,
EpisodeNumber = model.EpisodeNumber,
Title = model.Title,
AirDate = model.AirDate,
AirDateUtc = model.AirDateUtc,
Overview = model.Overview,
//EpisodeFile
HasFile = model.HasFile,
Monitored = model.Monitored,
AbsoluteEpisodeNumber = model.AbsoluteEpisodeNumber,
SceneAbsoluteEpisodeNumber = model.SceneAbsoluteEpisodeNumber,
SceneEpisodeNumber = model.SceneEpisodeNumber,
SceneSeasonNumber = model.SceneSeasonNumber,
UnverifiedSceneNumbering = model.UnverifiedSceneNumbering,
SeriesTitle = model.SeriesTitle,
//Series = model.Series.MapToResource(),
LastSearchTime = model.LastSearchTime
};
}
public static List<EpisodeResource> ToResource(this IEnumerable<Episode> models)
{
if (models == null) return null;
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,38 +0,0 @@
using System.Collections.Generic;
using Sonarr.Http.REST;
using NzbDrone.Core.MediaFiles;
using Sonarr.Http;
namespace NzbDrone.Api.Episodes
{
public class RenameEpisodeModule : SonarrRestModule<RenameEpisodeResource>
{
private readonly IRenameEpisodeFileService _renameEpisodeFileService;
public RenameEpisodeModule(IRenameEpisodeFileService renameEpisodeFileService)
: base("rename")
{
_renameEpisodeFileService = renameEpisodeFileService;
GetResourceAll = GetEpisodes;
}
private List<RenameEpisodeResource> GetEpisodes()
{
if (!Request.Query.SeriesId.HasValue)
{
throw new BadRequestException("seriesId is missing");
}
var seriesId = (int)Request.Query.SeriesId;
if (Request.Query.SeasonNumber.HasValue)
{
var seasonNumber = (int)Request.Query.SeasonNumber;
return _renameEpisodeFileService.GetRenamePreviews(seriesId, seasonNumber).ToResource();
}
return _renameEpisodeFileService.GetRenamePreviews(seriesId).ToResource();
}
}
}

View File

@ -1,39 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Sonarr.Http.REST;
namespace NzbDrone.Api.Episodes
{
public class RenameEpisodeResource : RestResource
{
public int SeriesId { get; set; }
public int SeasonNumber { get; set; }
public List<int> EpisodeNumbers { get; set; }
public int EpisodeFileId { get; set; }
public string ExistingPath { get; set; }
public string NewPath { get; set; }
}
public static class RenameEpisodeResourceMapper
{
public static RenameEpisodeResource ToResource(this Core.MediaFiles.RenameEpisodeFilePreview model)
{
if (model == null) return null;
return new RenameEpisodeResource
{
SeriesId = model.SeriesId,
SeasonNumber = model.SeasonNumber,
EpisodeNumbers = model.EpisodeNumbers.ToList(),
EpisodeFileId = model.EpisodeFileId,
ExistingPath = model.ExistingPath,
NewPath = model.NewPath
};
}
public static List<RenameEpisodeResource> ToResource(this IEnumerable<Core.MediaFiles.RenameEpisodeFilePreview> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,71 +0,0 @@
using System;
using System.IO;
using System.Linq;
using Nancy;
using Sonarr.Http.Extensions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Api.FileSystem
{
public class FileSystemModule : NzbDroneApiModule
{
private readonly IFileSystemLookupService _fileSystemLookupService;
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
public FileSystemModule(IFileSystemLookupService fileSystemLookupService,
IDiskProvider diskProvider,
IDiskScanService diskScanService)
: base("/filesystem")
{
_fileSystemLookupService = fileSystemLookupService;
_diskProvider = diskProvider;
_diskScanService = diskScanService;
Get("/", x => GetContents());
Get("/type", x => GetEntityType());
Get("/mediafiles", x => GetMediaFiles());
}
private object GetContents()
{
var pathQuery = Request.Query.path;
var includeFiles = Request.GetBooleanQueryParameter("includeFiles");
var allowFoldersWithoutTrailingSlashes = Request.GetBooleanQueryParameter("allowFoldersWithoutTrailingSlashes");
return _fileSystemLookupService.LookupContents((string)pathQuery.Value, includeFiles, allowFoldersWithoutTrailingSlashes);
}
private object GetEntityType()
{
var pathQuery = Request.Query.path;
var path = (string)pathQuery.Value;
if (_diskProvider.FileExists(path))
{
return new { type = "file" };
}
//Return folder even if it doesn't exist on disk to avoid leaking anything from the UI about the underlying system
return new { type = "folder" };
}
private object GetMediaFiles()
{
var pathQuery = Request.Query.path;
var path = (string)pathQuery.Value;
if (!_diskProvider.FolderExists(path))
{
return new string[0];
}
return _diskScanService.GetVideoFiles(path).Select(f => new {
Path = f,
RelativePath = path.GetRelativePath(f),
Name = Path.GetFileName(f)
});
}
}
}

View File

@ -1,32 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.SignalR;
using Sonarr.Http;
namespace NzbDrone.Api.Health
{
public class HealthModule : SonarrRestModuleWithSignalR<HealthResource, HealthCheck>,
IHandle<HealthCheckCompleteEvent>
{
private readonly IHealthCheckService _healthCheckService;
public HealthModule(IBroadcastSignalRMessage signalRBroadcaster, IHealthCheckService healthCheckService)
: base(signalRBroadcaster)
{
_healthCheckService = healthCheckService;
GetResourceAll = GetHealth;
}
private List<HealthResource> GetHealth()
{
return _healthCheckService.Results().ToResource();
}
public void Handle(HealthCheckCompleteEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
}
}

View File

@ -1,37 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Http;
using NzbDrone.Core.HealthCheck;
using Sonarr.Http.REST;
namespace NzbDrone.Api.Health
{
public class HealthResource : RestResource
{
public HealthCheckResult Type { get; set; }
public string Message { get; set; }
public HttpUri WikiUrl { get; set; }
}
public static class HealthResourceMapper
{
public static HealthResource ToResource(this HealthCheck model)
{
if (model == null) return null;
return new HealthResource
{
Id = model.Id,
Type = model.Type,
Message = model.Message,
WikiUrl = model.WikiUrl
};
}
public static List<HealthResource> ToResource(this IEnumerable<HealthCheck> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,99 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Nancy;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.Series;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using Sonarr.Http;
using Sonarr.Http.REST;
namespace NzbDrone.Api.History
{
public class HistoryModule : SonarrRestModule<HistoryResource>
{
private readonly IHistoryService _historyService;
private readonly IUpgradableSpecification _upgradableSpecification;
private readonly IFailedDownloadService _failedDownloadService;
public HistoryModule(IHistoryService historyService,
IUpgradableSpecification upgradableSpecification,
IFailedDownloadService failedDownloadService)
{
_historyService = historyService;
_upgradableSpecification = upgradableSpecification;
_failedDownloadService = failedDownloadService;
GetResourcePaged = GetHistory;
Get("/since", x => GetHistorySince());
Post("/failed", x => MarkAsFailed());
}
protected HistoryResource MapToResource(EpisodeHistory model)
{
var resource = model.ToResource();
resource.Series = model.Series.ToResource();
resource.Episode = model.Episode.ToResource();
if (model.Series != null)
{
resource.QualityCutoffNotMet = _upgradableSpecification.QualityCutoffNotMet(model.Series.QualityProfile.Value, model.Quality);
}
return resource;
}
private PagingResource<HistoryResource> GetHistory(PagingResource<HistoryResource> pagingResource)
{
var episodeId = Request.Query.EpisodeId;
var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, EpisodeHistory>("date", SortDirection.Descending);
var filter = pagingResource.Filters.FirstOrDefault();
if (filter != null && filter.Key == "eventType")
{
var filterValue = (EpisodeHistoryEventType)Convert.ToInt32(filter.Value);
pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue);
}
if (episodeId.HasValue)
{
int i = (int)episodeId;
pagingSpec.FilterExpressions.Add(h => h.EpisodeId == i);
}
return ApplyToPage(_historyService.Paged, pagingSpec, MapToResource);
}
private List<HistoryResource> GetHistorySince()
{
var queryDate = Request.Query.Date;
var queryEventType = Request.Query.EventType;
if (!queryDate.HasValue)
{
throw new BadRequestException("date is missing");
}
DateTime date = DateTime.Parse(queryDate.Value);
EpisodeHistoryEventType? eventType = null;
if (queryEventType.HasValue)
{
eventType = (EpisodeHistoryEventType)Convert.ToInt32(queryEventType.Value);
}
return _historyService.Since(date, eventType).Select(MapToResource).ToList();
}
private object MarkAsFailed()
{
var id = (int)Request.Form.Id;
_failedDownloadService.MarkAsFailed(id);
return new object();
}
}
}

View File

@ -1,57 +0,0 @@
using System;
using System.Collections.Generic;
using NzbDrone.Api.Episodes;
using Sonarr.Http.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.History;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Languages;
namespace NzbDrone.Api.History
{
public class HistoryResource : RestResource
{
public int EpisodeId { get; set; }
public int SeriesId { get; set; }
public string SourceTitle { get; set; }
public QualityModel Quality { get; set; }
public bool QualityCutoffNotMet { get; set; }
public DateTime Date { get; set; }
public string DownloadId { get; set; }
public Language Language { get; set; }
public EpisodeHistoryEventType EventType { get; set; }
public Dictionary<string, string> Data { get; set; }
public EpisodeResource Episode { get; set; }
public SeriesResource Series { get; set; }
}
public static class HistoryResourceMapper
{
public static HistoryResource ToResource(this EpisodeHistory model)
{
if (model == null) return null;
return new HistoryResource
{
Id = model.Id,
EpisodeId = model.EpisodeId,
SeriesId = model.SeriesId,
SourceTitle = model.SourceTitle,
Quality = model.Quality,
//QualityCutoffNotMet
Date = model.Date,
DownloadId = model.DownloadId,
EventType = model.EventType,
Data = model.Data
//Episode
//Series
};
}
}
}

View File

@ -1,34 +0,0 @@
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Indexers
{
public class IndexerModule : ProviderModuleBase<IndexerResource, IIndexer, IndexerDefinition>
{
public IndexerModule(IndexerFactory indexerFactory)
: base(indexerFactory, "indexer")
{
}
protected override void MapToResource(IndexerResource resource, IndexerDefinition definition)
{
base.MapToResource(resource, definition);
resource.EnableRss = definition.EnableRss;
resource.EnableSearch = definition.EnableAutomaticSearch || definition.EnableInteractiveSearch;
resource.SupportsRss = definition.SupportsRss;
resource.SupportsSearch = definition.SupportsSearch;
resource.Protocol = definition.Protocol;
resource.Priority = definition.Priority;
}
protected override void MapToModel(IndexerDefinition definition, IndexerResource resource)
{
base.MapToModel(definition, resource);
definition.EnableRss = resource.EnableRss;
definition.EnableAutomaticSearch = resource.EnableSearch;
definition.EnableInteractiveSearch = resource.EnableSearch;
definition.Priority = resource.Priority;
}
}
}

View File

@ -1,14 +0,0 @@
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Indexers
{
public class IndexerResource : ProviderResource
{
public bool EnableRss { get; set; }
public bool EnableSearch { get; set; }
public bool SupportsRss { get; set; }
public bool SupportsSearch { get; set; }
public DownloadProtocol Protocol { get; set; }
public int Priority { get; set; }
}
}

View File

@ -1,125 +0,0 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using Nancy;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using Nancy.ModelBinding;
using NzbDrone.Common.Cache;
using HttpStatusCode = System.Net.HttpStatusCode;
namespace NzbDrone.Api.Indexers
{
public class ReleaseModule : ReleaseModuleBase
{
private readonly IFetchAndParseRss _rssFetcherAndParser;
private readonly ISearchForReleases _releaseSearchService;
private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision;
private readonly IDownloadService _downloadService;
private readonly Logger _logger;
private readonly ICached<RemoteEpisode> _remoteEpisodeCache;
public ReleaseModule(IFetchAndParseRss rssFetcherAndParser,
ISearchForReleases releaseSearchService,
IMakeDownloadDecision downloadDecisionMaker,
IPrioritizeDownloadDecision prioritizeDownloadDecision,
IDownloadService downloadService,
ICacheManager cacheManager,
Logger logger)
{
_rssFetcherAndParser = rssFetcherAndParser;
_releaseSearchService = releaseSearchService;
_downloadDecisionMaker = downloadDecisionMaker;
_prioritizeDownloadDecision = prioritizeDownloadDecision;
_downloadService = downloadService;
_logger = logger;
GetResourceAll = GetReleases;
Post("/", x => DownloadRelease(this.Bind<ReleaseResource>()));
PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true);
PostValidator.RuleFor(s => s.Guid).NotEmpty();
_remoteEpisodeCache = cacheManager.GetCache<RemoteEpisode>(GetType(), "remoteEpisodes");
}
private object DownloadRelease(ReleaseResource release)
{
var remoteEpisode = _remoteEpisodeCache.Find(GetCacheKey(release));
if (remoteEpisode == null)
{
_logger.Debug("Couldn't find requested release in cache, cache timeout probably expired.");
return new NotFoundResponse();
}
try
{
_downloadService.DownloadReport(remoteEpisode);
}
catch (ReleaseDownloadException ex)
{
_logger.Error(ex, ex.Message);
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Getting release from indexer failed");
}
return release;
}
private List<ReleaseResource> GetReleases()
{
if (Request.Query.episodeId != null)
{
return GetEpisodeReleases(Request.Query.episodeId);
}
return GetRss();
}
private List<ReleaseResource> GetEpisodeReleases(int episodeId)
{
try
{
var decisions = _releaseSearchService.EpisodeSearch(episodeId, true, true);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(prioritizedDecisions);
}
catch (Exception ex)
{
_logger.Error(ex, "Episode search failed");
}
return new List<ReleaseResource>();
}
private List<ReleaseResource> GetRss()
{
var reports = _rssFetcherAndParser.Fetch();
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(prioritizedDecisions);
}
protected override ReleaseResource MapDecision(DownloadDecision decision, int initialWeight)
{
var resource = base.MapDecision(decision, initialWeight);
_remoteEpisodeCache.Set(GetCacheKey(resource), decision.RemoteEpisode, TimeSpan.FromMinutes(30));
return resource;
}
private string GetCacheKey(ReleaseResource resource)
{
return string.Concat(resource.IndexerId, "_", resource.Guid);
}
}
}

View File

@ -1,42 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.DecisionEngine;
using Sonarr.Http;
namespace NzbDrone.Api.Indexers
{
public abstract class ReleaseModuleBase : SonarrRestModule<ReleaseResource>
{
protected virtual List<ReleaseResource> MapDecisions(IEnumerable<DownloadDecision> decisions)
{
var result = new List<ReleaseResource>();
foreach (var downloadDecision in decisions)
{
var release = MapDecision(downloadDecision, result.Count);
result.Add(release);
}
return result;
}
protected virtual ReleaseResource MapDecision(DownloadDecision decision, int initialWeight)
{
var release = decision.ToResource();
release.ReleaseWeight = initialWeight;
if (decision.RemoteEpisode.Series != null)
{
release.QualityWeight = decision.RemoteEpisode.Series
.QualityProfile.Value
.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 100;
}
release.QualityWeight += release.Quality.Revision.Real * 10;
release.QualityWeight += release.Quality.Revision.Version;
return release;
}
}
}

View File

@ -1,90 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Api.Indexers
{
public class ReleasePushModule : ReleaseModuleBase
{
private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IProcessDownloadDecisions _downloadDecisionProcessor;
private readonly IIndexerFactory _indexerFactory;
private readonly Logger _logger;
public ReleasePushModule(IMakeDownloadDecision downloadDecisionMaker,
IProcessDownloadDecisions downloadDecisionProcessor,
IIndexerFactory indexerFactory,
Logger logger)
{
_downloadDecisionMaker = downloadDecisionMaker;
_downloadDecisionProcessor = downloadDecisionProcessor;
_indexerFactory = indexerFactory;
_logger = logger;
Post("/push", x => ProcessRelease(ReadResourceFromRequest()));
PostValidator.RuleFor(s => s.Title).NotEmpty();
PostValidator.RuleFor(s => s.DownloadUrl).NotEmpty();
PostValidator.RuleFor(s => s.Protocol).NotEmpty();
PostValidator.RuleFor(s => s.PublishDate).NotEmpty();
}
private object ProcessRelease(ReleaseResource release)
{
_logger.Info("Release pushed: {0} - {1}", release.Title, release.DownloadUrl);
var info = release.ToModel();
info.Guid = "PUSH-" + info.DownloadUrl;
ResolveIndexer(info);
var decisions = _downloadDecisionMaker.GetRssDecision(new List<ReleaseInfo> { info });
_downloadDecisionProcessor.ProcessDecisions(decisions);
return MapDecisions(decisions).First();
}
private void ResolveIndexer(ReleaseInfo release)
{
if (release.IndexerId == 0 && release.Indexer.IsNotNullOrWhiteSpace())
{
var indexer = _indexerFactory.All().FirstOrDefault(v => v.Name == release.Indexer);
if (indexer != null)
{
release.IndexerId = indexer.Id;
_logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer);
}
else
{
_logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.Indexer);
}
}
else if (release.IndexerId != 0 && release.Indexer.IsNullOrWhiteSpace())
{
try
{
var indexer = _indexerFactory.Get(release.IndexerId);
release.Indexer = indexer.Name;
_logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer);
}
catch (ModelNotFoundException)
{
_logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.IndexerId);
release.IndexerId = 0;
}
}
else
{
_logger.Debug("Push Release {0} not associated with an indexer.", release.Title);
}
}
}
}

View File

@ -1,174 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Sonarr.Http.REST;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.DecisionEngine;
using System.Linq;
namespace NzbDrone.Api.Indexers
{
public class ReleaseResource : RestResource
{
public string Guid { get; set; }
public QualityModel Quality { get; set; }
public int QualityWeight { get; set; }
public int Age { get; set; }
public double AgeHours { get; set; }
public double AgeMinutes { get; set; }
public long Size { get; set; }
public int IndexerId { get; set; }
public string Indexer { get; set; }
public string ReleaseGroup { get; set; }
public string ReleaseHash { get; set; }
public string Title { get; set; }
public bool FullSeason { get; set; }
public int SeasonNumber { get; set; }
public Language Language { get; set; }
public string AirDate { get; set; }
public string SeriesTitle { get; set; }
public int[] EpisodeNumbers { get; set; }
public int[] AbsoluteEpisodeNumbers { get; set; }
public bool Approved { get; set; }
public bool TemporarilyRejected { get; set; }
public bool Rejected { get; set; }
public int TvdbId { get; set; }
public int TvRageId { get; set; }
public IEnumerable<string> Rejections { get; set; }
public DateTime PublishDate { get; set; }
public string CommentUrl { get; set; }
public string DownloadUrl { get; set; }
public string InfoUrl { get; set; }
public bool DownloadAllowed { get; set; }
public int ReleaseWeight { get; set; }
public string MagnetUrl { get; set; }
public string InfoHash { get; set; }
public int? Seeders { get; set; }
public int? Leechers { get; set; }
public DownloadProtocol Protocol { get; set; }
// Used to support the original Release Push implementation, removed in v3
// JsonIgnore so we don't serialize it, but can still parse it
[JsonIgnore]
public DownloadProtocol DownloadProtocol
{
get
{
return Protocol;
}
set
{
if (value > 0 && Protocol == 0)
{
Protocol = value;
}
}
}
public bool IsDaily { get; set; }
public bool IsAbsoluteNumbering { get; set; }
public bool IsPossibleSpecialEpisode { get; set; }
public bool Special { get; set; }
}
public static class ReleaseResourceMapper
{
public static ReleaseResource ToResource(this DownloadDecision model)
{
var releaseInfo = model.RemoteEpisode.Release;
var parsedEpisodeInfo = model.RemoteEpisode.ParsedEpisodeInfo;
var remoteEpisode = model.RemoteEpisode;
var torrentInfo = (model.RemoteEpisode.Release as TorrentInfo) ?? new TorrentInfo();
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?)
return new ReleaseResource
{
Guid = releaseInfo.Guid,
Quality = parsedEpisodeInfo?.Quality,
//QualityWeight
Age = releaseInfo.Age,
AgeHours = releaseInfo.AgeHours,
AgeMinutes = releaseInfo.AgeMinutes,
Size = releaseInfo.Size,
IndexerId = releaseInfo.IndexerId,
Indexer = releaseInfo.Indexer,
ReleaseGroup = parsedEpisodeInfo?.ReleaseGroup,
ReleaseHash = parsedEpisodeInfo?.ReleaseHash,
Title = releaseInfo.Title,
FullSeason = parsedEpisodeInfo?.FullSeason ?? false,
SeasonNumber = parsedEpisodeInfo?.SeasonNumber ?? 0,
Language = parsedEpisodeInfo?.Language,
AirDate = parsedEpisodeInfo?.AirDate,
SeriesTitle = parsedEpisodeInfo?.SeriesTitle,
EpisodeNumbers = parsedEpisodeInfo?.EpisodeNumbers,
AbsoluteEpisodeNumbers = parsedEpisodeInfo?.AbsoluteEpisodeNumbers,
Approved = model.Approved,
TemporarilyRejected = model.TemporarilyRejected,
Rejected = model.Rejected,
TvdbId = releaseInfo.TvdbId,
TvRageId = releaseInfo.TvRageId,
Rejections = model.Rejections.Select(r => r.Reason).ToList(),
PublishDate = releaseInfo.PublishDate,
CommentUrl = releaseInfo.CommentUrl,
DownloadUrl = releaseInfo.DownloadUrl,
InfoUrl = releaseInfo.InfoUrl,
DownloadAllowed = remoteEpisode.DownloadAllowed,
//ReleaseWeight
MagnetUrl = torrentInfo.MagnetUrl,
InfoHash = torrentInfo.InfoHash,
Seeders = torrentInfo.Seeders,
Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null,
Protocol = releaseInfo.DownloadProtocol,
IsDaily = parsedEpisodeInfo?.IsDaily ?? false,
IsAbsoluteNumbering = parsedEpisodeInfo?.IsAbsoluteNumbering ?? false,
IsPossibleSpecialEpisode = parsedEpisodeInfo?.IsPossibleSpecialEpisode ?? false,
Special = parsedEpisodeInfo?.Special ?? false,
};
}
public static ReleaseInfo ToModel(this ReleaseResource resource)
{
ReleaseInfo model;
if (resource.Protocol == DownloadProtocol.Torrent)
{
model = new TorrentInfo
{
MagnetUrl = resource.MagnetUrl,
InfoHash = resource.InfoHash,
Seeders = resource.Seeders,
Peers = (resource.Seeders.HasValue && resource.Leechers.HasValue) ? (resource.Seeders + resource.Leechers) : null
};
}
else
{
model = new ReleaseInfo();
}
model.Guid = resource.Guid;
model.Title = resource.Title;
model.Size = resource.Size;
model.DownloadUrl = resource.DownloadUrl;
model.InfoUrl = resource.InfoUrl;
model.CommentUrl = resource.CommentUrl;
model.IndexerId = resource.IndexerId;
model.Indexer = resource.Indexer;
model.DownloadProtocol = resource.DownloadProtocol;
model.TvdbId = resource.TvdbId;
model.TvRageId = resource.TvRageId;
model.PublishDate = resource.PublishDate.ToUniversalTime();
return model;
}
}
}

View File

@ -1,36 +0,0 @@
using System.Collections.Generic;
using System.IO;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Logs
{
public class LogFileModule : LogFileModuleBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
public LogFileModule(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider)
: base(diskProvider, configFileProvider, "")
{
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;
}
protected override IEnumerable<string> GetLogFiles()
{
return _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), SearchOption.TopDirectoryOnly);
}
protected override string GetLogFilePath(string filename)
{
return Path.Combine(_appFolderInfo.GetLogFolder(), filename);
}
protected override string DownloadUrlRoot => "logfile";
}
}

View File

@ -1,75 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Nancy;
using Nancy.Responses;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using Sonarr.Http;
namespace NzbDrone.Api.Logs
{
public abstract class LogFileModuleBase : SonarrRestModule<LogFileResource>
{
protected const string LOGFILE_ROUTE = @"/(?<filename>[-.a-zA-Z0-9]+?\.txt)";
private readonly IDiskProvider _diskProvider;
private readonly IConfigFileProvider _configFileProvider;
public LogFileModuleBase(IDiskProvider diskProvider,
IConfigFileProvider configFileProvider,
string route)
: base("log/file" + route)
{
_diskProvider = diskProvider;
_configFileProvider = configFileProvider;
GetResourceAll = GetLogFilesResponse;
Get(LOGFILE_ROUTE, options => GetLogFileResponse(options.filename));
}
private List<LogFileResource> GetLogFilesResponse()
{
var result = new List<LogFileResource>();
var files = GetLogFiles().ToList();
for (int i = 0; i < files.Count; i++)
{
var file = files[i];
var filename = Path.GetFileName(file);
result.Add(new LogFileResource
{
Id = i + 1,
Filename = filename,
LastWriteTime = _diskProvider.FileGetLastWrite(file),
ContentsUrl = string.Format("{0}/api/{1}/{2}", _configFileProvider.UrlBase, Resource, filename),
DownloadUrl = string.Format("{0}/{1}/{2}", _configFileProvider.UrlBase, DownloadUrlRoot, filename)
});
}
return result.OrderByDescending(l => l.LastWriteTime).ToList();
}
private object GetLogFileResponse(string filename)
{
LogManager.Flush();
var filePath = GetLogFilePath(filename);
if (!_diskProvider.FileExists(filePath))
return new NotFoundResponse();
var data = _diskProvider.ReadAllText(filePath);
return new TextResponse(data);
}
protected abstract IEnumerable<string> GetLogFiles();
protected abstract string GetLogFilePath(string filename);
protected abstract string DownloadUrlRoot { get; }
}
}

View File

@ -1,13 +0,0 @@
using System;
using Sonarr.Http.REST;
namespace NzbDrone.Api.Logs
{
public class LogFileResource : RestResource
{
public string Filename { get; set; }
public DateTime LastWriteTime { get; set; }
public string ContentsUrl { get; set; }
public string DownloadUrl { get; set; }
}
}

View File

@ -1,56 +0,0 @@
using System.Linq;
using NzbDrone.Core.Instrumentation;
using Sonarr.Http;
namespace NzbDrone.Api.Logs
{
public class LogModule : SonarrRestModule<LogResource>
{
private readonly ILogService _logService;
public LogModule(ILogService logService)
{
_logService = logService;
GetResourcePaged = GetLogs;
}
private PagingResource<LogResource> GetLogs(PagingResource<LogResource> pagingResource)
{
var pageSpec = pagingResource.MapToPagingSpec<LogResource, Log>();
if (pageSpec.SortKey == "time")
{
pageSpec.SortKey = "id";
}
var filter = pagingResource.Filters.FirstOrDefault();
if (filter != null && filter.Key == "level")
{
switch (filter.Value)
{
case "Fatal":
pageSpec.FilterExpressions.Add(h => h.Level == "Fatal");
break;
case "Error":
pageSpec.FilterExpressions.Add(h => h.Level == "Fatal" || h.Level == "Error");
break;
case "Warn":
pageSpec.FilterExpressions.Add(h => h.Level == "Fatal" || h.Level == "Error" || h.Level == "Warn");
break;
case "Info":
pageSpec.FilterExpressions.Add(h => h.Level == "Fatal" || h.Level == "Error" || h.Level == "Warn" || h.Level == "Info");
break;
case "Debug":
pageSpec.FilterExpressions.Add(h => h.Level == "Fatal" || h.Level == "Error" || h.Level == "Warn" || h.Level == "Info" || h.Level == "Debug");
break;
case "Trace":
pageSpec.FilterExpressions.Add(h => h.Level == "Fatal" || h.Level == "Error" || h.Level == "Warn" || h.Level == "Info" || h.Level == "Debug" || h.Level == "Trace");
break;
}
}
return ApplyToPage(_logService.Paged, pageSpec, LogResourceMapper.ToResource);
}
}
}

View File

@ -1,35 +0,0 @@
using System;
using Sonarr.Http.REST;
namespace NzbDrone.Api.Logs
{
public class LogResource : RestResource
{
public DateTime Time { get; set; }
public string Exception { get; set; }
public string ExceptionType { get; set; }
public string Level { get; set; }
public string Logger { get; set; }
public string Message { get; set; }
}
public static class LogResourceMapper
{
public static LogResource ToResource(this Core.Instrumentation.Log model)
{
if (model == null) return null;
return new LogResource
{
Id = model.Id,
Time = model.Time,
Exception = model.Exception,
ExceptionType = model.ExceptionType,
Level = model.Level,
Logger = model.Logger,
Message = model.Message
};
}
}
}

View File

@ -1,42 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Logs
{
public class UpdateLogFileModule : LogFileModuleBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
public UpdateLogFileModule(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider)
: base(diskProvider, configFileProvider, "/update")
{
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;
}
protected override IEnumerable<string> GetLogFiles()
{
if (!_diskProvider.FolderExists(_appFolderInfo.GetUpdateLogFolder())) return Enumerable.Empty<string>();
return _diskProvider.GetFiles(_appFolderInfo.GetUpdateLogFolder(), SearchOption.TopDirectoryOnly)
.Where(f => Regex.IsMatch(Path.GetFileName(f), LOGFILE_ROUTE.TrimStart('/'), RegexOptions.IgnoreCase))
.ToList();
}
protected override string GetLogFilePath(string filename)
{
return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), filename);
}
protected override string DownloadUrlRoot => "updatelogfile";
}
}

View File

@ -1,48 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
using NzbDrone.Core.Qualities;
using Sonarr.Http;
using Sonarr.Http.Extensions;
namespace NzbDrone.Api.ManualImport
{
public class ManualImportModule : SonarrRestModule<ManualImportResource>
{
private readonly IManualImportService _manualImportService;
public ManualImportModule(IManualImportService manualImportService)
: base("/manualimport")
{
_manualImportService = manualImportService;
GetResourceAll = GetMediaFiles;
}
private List<ManualImportResource> GetMediaFiles()
{
var folderQuery = Request.Query.folder;
var folder = (string)folderQuery.Value;
var downloadIdQuery = Request.Query.downloadId;
var downloadId = (string)downloadIdQuery.Value;
var filterExistingFiles = Request.GetBooleanQueryParameter("filterExistingFiles", true);
return _manualImportService.GetMediaFiles(folder, downloadId, null, filterExistingFiles)
.ToResource()
.Select(AddQualityWeight).ToList();
}
private ManualImportResource AddQualityWeight(ManualImportResource item)
{
if (item.Quality != null)
{
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
item.QualityWeight += item.Quality.Revision.Real * 10;
item.QualityWeight += item.Quality.Revision.Version;
}
return item;
}
}
}

View File

@ -1,58 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.Episodes;
using Sonarr.Http.REST;
using NzbDrone.Api.Series;
using NzbDrone.Common.Crypto;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.ManualImport
{
public class ManualImportResource : RestResource
{
public string Path { get; set; }
public string RelativePath { get; set; }
public string FolderName { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public SeriesResource Series { get; set; }
public int? SeasonNumber { get; set; }
public List<EpisodeResource> Episodes { get; set; }
public QualityModel Quality { get; set; }
public int QualityWeight { get; set; }
public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
}
public static class ManualImportResourceMapper
{
public static ManualImportResource ToResource(this Core.MediaFiles.EpisodeImport.Manual.ManualImportItem model)
{
if (model == null) return null;
return new ManualImportResource
{
Id = HashConverter.GetHashInt31(model.Path),
Path = model.Path,
RelativePath = model.RelativePath,
FolderName = model.FolderName,
Name = model.Name,
Size = model.Size,
Series = model.Series.ToResource(),
SeasonNumber = model.SeasonNumber,
Episodes = model.Episodes.ToResource(),
Quality = model.Quality,
//QualityWeight
DownloadId = model.DownloadId,
Rejections = model.Rejections
};
}
public static List<ManualImportResource> ToResource(this IEnumerable<Core.MediaFiles.EpisodeImport.Manual.ManualImportItem> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,47 +0,0 @@
using System.IO;
using System.Text.RegularExpressions;
using Nancy;
using Nancy.Responses;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.MediaCovers
{
public class MediaCoverModule : NzbDroneApiModule
{
private static readonly Regex RegexResizedImage = new Regex(@"-\d+\.jpg$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private const string MEDIA_COVER_ROUTE = @"/(?<seriesId>\d+)/(?<filename>(.+)\.(jpg|png|gif))";
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
public MediaCoverModule(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider) : base("MediaCover")
{
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;
Get(MEDIA_COVER_ROUTE, options => GetMediaCover(options.seriesId, options.filename));
}
private object GetMediaCover(int seriesId, string filename)
{
var filePath = Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover", seriesId.ToString(), filename);
if (!_diskProvider.FileExists(filePath) || _diskProvider.GetFileSize(filePath) == 0)
{
// Return the full sized image if someone requests a non-existing resized one.
// TODO: This code can be removed later once everyone had the update for a while.
var basefilePath = RegexResizedImage.Replace(filePath, ".jpg");
if (basefilePath == filePath || !_diskProvider.FileExists(basefilePath))
{
return new NotFoundResponse();
}
filePath = basefilePath;
}
return new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath));
}
}
}

View File

@ -1,26 +0,0 @@
using NzbDrone.Core.Extras.Metadata;
namespace NzbDrone.Api.Metadata
{
public class MetadataModule : ProviderModuleBase<MetadataResource, IMetadata, MetadataDefinition>
{
public MetadataModule(IMetadataFactory metadataFactory)
: base(metadataFactory, "metadata")
{
}
protected override void MapToResource(MetadataResource resource, MetadataDefinition definition)
{
base.MapToResource(resource, definition);
resource.Enable = definition.Enable;
}
protected override void MapToModel(MetadataDefinition definition, MetadataResource resource)
{
base.MapToModel(definition, resource);
definition.Enable = resource.Enable;
}
}
}

View File

@ -1,7 +0,0 @@
namespace NzbDrone.Api.Metadata
{
public class MetadataResource : ProviderResource
{
public bool Enable { get; set; }
}
}

View File

@ -1,42 +0,0 @@
using NzbDrone.Core.Notifications;
namespace NzbDrone.Api.Notifications
{
public class NotificationModule : ProviderModuleBase<NotificationResource, INotification, NotificationDefinition>
{
public NotificationModule(NotificationFactory notificationFactory)
: base(notificationFactory, "notification")
{
}
protected override void MapToResource(NotificationResource resource, NotificationDefinition definition)
{
base.MapToResource(resource, definition);
resource.OnGrab = definition.OnGrab;
resource.OnDownload = definition.OnDownload;
resource.OnUpgrade = definition.OnUpgrade;
resource.OnRename = definition.OnRename;
resource.SupportsOnGrab = definition.SupportsOnGrab;
resource.SupportsOnDownload = definition.SupportsOnDownload;
resource.SupportsOnUpgrade = definition.SupportsOnUpgrade;
resource.SupportsOnRename = definition.SupportsOnRename;
resource.Tags = definition.Tags;
}
protected override void MapToModel(NotificationDefinition definition, NotificationResource resource)
{
base.MapToModel(definition, resource);
definition.OnGrab = resource.OnGrab;
definition.OnDownload = resource.OnDownload;
definition.OnUpgrade = resource.OnUpgrade;
definition.OnRename = resource.OnRename;
definition.SupportsOnGrab = resource.SupportsOnGrab;
definition.SupportsOnDownload = resource.SupportsOnDownload;
definition.SupportsOnUpgrade = resource.SupportsOnUpgrade;
definition.SupportsOnRename = resource.SupportsOnRename;
definition.Tags = resource.Tags;
}
}
}

View File

@ -1,17 +0,0 @@
using System.Collections.Generic;
namespace NzbDrone.Api.Notifications
{
public class NotificationResource : ProviderResource
{
public bool OnGrab { get; set; }
public bool OnDownload { get; set; }
public bool OnUpgrade { get; set; }
public bool OnRename { get; set; }
public bool SupportsOnGrab { get; set; }
public bool SupportsOnDownload { get; set; }
public bool SupportsOnUpgrade { get; set; }
public bool SupportsOnRename { get; set; }
public HashSet<int> Tags { get; set; }
}
}

View File

@ -1,12 +0,0 @@
using Sonarr.Http;
namespace NzbDrone.Api
{
public abstract class NzbDroneApiModule : SonarrModule
{
protected NzbDroneApiModule(string resource)
: base("/api/" + resource.Trim('/'))
{
}
}
}

View File

@ -1,12 +0,0 @@
using Sonarr.Http;
namespace NzbDrone.Api
{
public abstract class NzbDroneFeedModule : SonarrModule
{
protected NzbDroneFeedModule(string resource)
: base("/feed/" + resource.Trim('/'))
{
}
}
}

View File

@ -1,63 +0,0 @@
using NzbDrone.Api.Episodes;
using NzbDrone.Api.Series;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser;
using Sonarr.Http;
using Sonarr.Http.REST;
namespace NzbDrone.Api.Parse
{
public class ParseModule : SonarrRestModule<ParseResource>
{
private readonly IParsingService _parsingService;
public ParseModule(IParsingService parsingService)
{
_parsingService = parsingService;
GetResourceSingle = Parse;
}
private ParseResource Parse()
{
var title = Request.Query.Title.Value as string;
var path = Request.Query.Path.Value as string;
if (path.IsNullOrWhiteSpace() && title.IsNullOrWhiteSpace())
{
throw new BadRequestException("title or path is missing");
}
var parsedEpisodeInfo = path.IsNotNullOrWhiteSpace() ? Parser.ParsePath(path) : Parser.ParseTitle(title);
if (parsedEpisodeInfo == null)
{
return new ParseResource
{
Title = title
};
}
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0);
if (remoteEpisode != null)
{
return new ParseResource
{
Title = title,
ParsedEpisodeInfo = remoteEpisode.ParsedEpisodeInfo,
Series = remoteEpisode.Series.ToResource(),
Episodes = remoteEpisode.Episodes.ToResource()
};
}
else
{
return new ParseResource
{
Title = title,
ParsedEpisodeInfo = parsedEpisodeInfo
};
}
}
}
}

View File

@ -1,16 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Api.Episodes;
using Sonarr.Http.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Api.Parse
{
public class ParseResource : RestResource
{
public string Title { get; set; }
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
public SeriesResource Series { get; set; }
public List<EpisodeResource> Episodes { get; set; }
}
}

View File

@ -1,74 +0,0 @@
using System.Collections.Generic;
using FluentValidation;
using FluentValidation.Results;
using Sonarr.Http.REST;
using NzbDrone.Core.Profiles.Delay;
using Sonarr.Http;
using Sonarr.Http.Validation;
namespace NzbDrone.Api.Profiles.Delay
{
public class DelayProfileModule : SonarrRestModule<DelayProfileResource>
{
private readonly IDelayProfileService _delayProfileService;
public DelayProfileModule(IDelayProfileService delayProfileService, DelayProfileTagInUseValidator tagInUseValidator)
{
_delayProfileService = delayProfileService;
GetResourceAll = GetAll;
GetResourceById = GetById;
UpdateResource = Update;
CreateResource = Create;
DeleteResource = DeleteProfile;
SharedValidator.RuleFor(d => d.Tags).NotEmpty().When(d => d.Id != 1);
SharedValidator.RuleFor(d => d.Tags).EmptyCollection<DelayProfileResource, int>().When(d => d.Id == 1);
SharedValidator.RuleFor(d => d.Tags).SetValidator(tagInUseValidator);
SharedValidator.RuleFor(d => d.UsenetDelay).GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(d => d.TorrentDelay).GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(d => d).Custom((delayProfile, context) =>
{
if (!delayProfile.EnableUsenet && !delayProfile.EnableTorrent)
{
context.AddFailure("Either Usenet or Torrent should be enabled");
}
});
}
private int Create(DelayProfileResource resource)
{
var model = resource.ToModel();
model = _delayProfileService.Add(model);
return model.Id;
}
private void DeleteProfile(int id)
{
if (id == 1)
{
throw new MethodNotAllowedException("Cannot delete global delay profile");
}
_delayProfileService.Delete(id);
}
private void Update(DelayProfileResource resource)
{
var model = resource.ToModel();
_delayProfileService.Update(model);
}
private DelayProfileResource GetById(int id)
{
return _delayProfileService.Get(id).ToResource();
}
private List<DelayProfileResource> GetAll()
{
return _delayProfileService.All().ToResource();
}
}
}

View File

@ -1,66 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Sonarr.Http.REST;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Profiles.Delay;
namespace NzbDrone.Api.Profiles.Delay
{
public class DelayProfileResource : RestResource
{
public bool EnableUsenet { get; set; }
public bool EnableTorrent { get; set; }
public DownloadProtocol PreferredProtocol { get; set; }
public int UsenetDelay { get; set; }
public int TorrentDelay { get; set; }
public bool BypassIfHighestQuality { get; set; }
public int Order { get; set; }
public HashSet<int> Tags { get; set; }
}
public static class DelayProfileResourceMapper
{
public static DelayProfileResource ToResource(this DelayProfile model)
{
if (model == null) return null;
return new DelayProfileResource
{
Id = model.Id,
EnableUsenet = model.EnableUsenet,
EnableTorrent = model.EnableTorrent,
PreferredProtocol = model.PreferredProtocol,
UsenetDelay = model.UsenetDelay,
TorrentDelay = model.TorrentDelay,
BypassIfHighestQuality = model.BypassIfHighestQuality,
Order = model.Order,
Tags = new HashSet<int>(model.Tags)
};
}
public static DelayProfile ToModel(this DelayProfileResource resource)
{
if (resource == null) return null;
return new DelayProfile
{
Id = resource.Id,
EnableUsenet = resource.EnableUsenet,
EnableTorrent = resource.EnableTorrent,
PreferredProtocol = resource.PreferredProtocol,
UsenetDelay = resource.UsenetDelay,
TorrentDelay = resource.TorrentDelay,
BypassIfHighestQuality = resource.BypassIfHighestQuality,
Order = resource.Order,
Tags = new HashSet<int>(resource.Tags)
};
}
public static List<DelayProfileResource> ToResource(this IEnumerable<DelayProfile> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Languages;
using Sonarr.Http;
namespace NzbDrone.Api.Profiles.Languages
{
public class LanguageModule : SonarrRestModule<LanguageResource>
{
public LanguageModule()
{
GetResourceAll = GetAll;
GetResourceById = GetById;
}
private LanguageResource GetById(int id)
{
var language = (Language)id;
return new LanguageResource
{
Id = (int)language,
Name = language.ToString()
};
}
private List<LanguageResource> GetAll()
{
return ((Language[])Enum.GetValues(typeof (Language)))
.Select(l => new LanguageResource
{
Id = (int) l,
Name = l.ToString()
})
.OrderBy(l => l.Name)
.ToList();
}
}
}

View File

@ -1,13 +0,0 @@
using Newtonsoft.Json;
using Sonarr.Http.REST;
namespace NzbDrone.Api.Profiles.Languages
{
public class LanguageResource : RestResource
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
public new int Id { get; set; }
public string Name { get; set; }
public string NameLower => Name.ToLowerInvariant();
}
}

View File

@ -1,34 +0,0 @@
using System.Text;
using Nancy;
namespace NzbDrone.Api.Profiles
{
public class LegacyProfileModule : NzbDroneApiModule
{
public LegacyProfileModule()
: base("qualityprofile")
{
Get("/", x =>
{
string queryString = ConvertQueryParams(Request.Query);
var url = string.Format("/api/profile?{0}", queryString);
return Response.AsRedirect(url);
});
}
private string ConvertQueryParams(DynamicDictionary query)
{
var sb = new StringBuilder();
foreach (var key in query)
{
var value = query[key];
sb.AppendFormat("&{0}={1}", key, value);
}
return sb.ToString().Trim('&');
}
}
}

View File

@ -1,55 +0,0 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Profiles.Qualities;
using Sonarr.Http;
namespace NzbDrone.Api.Profiles
{
public class ProfileModule : SonarrRestModule<ProfileResource>
{
private readonly IQualityProfileService _qualityProfileService;
public ProfileModule(IQualityProfileService qualityProfileService)
{
_qualityProfileService = qualityProfileService;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
SharedValidator.RuleFor(c => c.Cutoff).NotNull();
SharedValidator.RuleFor(c => c.Items).MustHaveAllowedQuality();
GetResourceAll = GetAll;
GetResourceById = GetById;
UpdateResource = Update;
CreateResource = Create;
DeleteResource = DeleteProfile;
}
private int Create(ProfileResource resource)
{
var model = resource.ToModel();
return _qualityProfileService.Add(model).Id;
}
private void DeleteProfile(int id)
{
_qualityProfileService.Delete(id);
}
private void Update(ProfileResource resource)
{
var model = resource.ToModel();
_qualityProfileService.Update(model);
}
private ProfileResource GetById(int id)
{
return _qualityProfileService.Get(id).ToResource();
}
private List<ProfileResource> GetAll()
{
return _qualityProfileService.All().ToResource();
}
}
}

View File

@ -1,111 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using Sonarr.Http.REST;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Profiles
{
public class ProfileResource : RestResource
{
public string Name { get; set; }
public bool UpgradeAllowed { get; set; }
public Quality Cutoff { get; set; }
public List<ProfileQualityItemResource> Items { get; set; }
}
public class ProfileQualityItemResource : RestResource
{
public Quality Quality { get; set; }
public bool Allowed { get; set; }
}
public static class ProfileResourceMapper
{
public static ProfileResource ToResource(this QualityProfile model)
{
if (model == null) return null;
var cutoffItem = model.Items.First(q =>
{
if (q.Id == model.Cutoff) return true;
if (q.Quality == null) return false;
return q.Quality.Id == model.Cutoff;
});
var cutoff = cutoffItem.Items == null || cutoffItem.Items.Empty()
? cutoffItem.Quality
: cutoffItem.Items.First().Quality;
return new ProfileResource
{
Id = model.Id,
Name = model.Name,
UpgradeAllowed = model.UpgradeAllowed,
Cutoff = cutoff,
// Flatten groups so things don't explode
Items = model.Items.SelectMany(i =>
{
if (i == null)
{
return null;
}
if (i.Items.Any())
{
return i.Items.ConvertAll(ToResource);
}
return new List<ProfileQualityItemResource> {ToResource(i)};
}).ToList()
};
}
public static ProfileQualityItemResource ToResource(this QualityProfileQualityItem model)
{
if (model == null) return null;
return new ProfileQualityItemResource
{
Quality = model.Quality,
Allowed = model.Allowed
};
}
public static QualityProfile ToModel(this ProfileResource resource)
{
if (resource == null) return null;
return new QualityProfile
{
Id = resource.Id,
Name = resource.Name,
UpgradeAllowed = resource.UpgradeAllowed,
Cutoff = resource.Cutoff.Id,
Items = resource.Items.ConvertAll(ToModel)
};
}
public static QualityProfileQualityItem ToModel(this ProfileQualityItemResource resource)
{
if (resource == null) return null;
return new QualityProfileQualityItem
{
Quality = (Quality)resource.Quality.Id,
Allowed = resource.Allowed
};
}
public static List<ProfileResource> ToResource(this IEnumerable<QualityProfile> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,36 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using Sonarr.Http;
using Sonarr.Http.Mapping;
namespace NzbDrone.Api.Profiles
{
public class ProfileSchemaModule : SonarrRestModule<ProfileResource>
{
private readonly IQualityDefinitionService _qualityDefinitionService;
public ProfileSchemaModule(IQualityDefinitionService qualityDefinitionService)
: base("/profile/schema")
{
_qualityDefinitionService = qualityDefinitionService;
GetResourceAll = GetAll;
}
private List<ProfileResource> GetAll()
{
var items = _qualityDefinitionService.All()
.OrderBy(v => v.Weight)
.Select(v => new QualityProfileQualityItem { Quality = v.Quality, Allowed = false })
.ToList();
var profile = new QualityProfile();
profile.Cutoff = Quality.Unknown.Id;
profile.Items = items;
return new List<ProfileResource> { profile.ToResource() };
}
}
}

View File

@ -1,43 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using FluentValidation.Validators;
namespace NzbDrone.Api.Profiles
{
public static class ProfileValidation
{
public static IRuleBuilderOptions<T, IList<ProfileQualityItemResource>> MustHaveAllowedQuality<T>(this IRuleBuilder<T, IList<ProfileQualityItemResource>> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new AllowedValidator<T>());
}
}
public class AllowedValidator<T> : PropertyValidator
{
public AllowedValidator()
: base("Must contain at least one allowed quality")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var list = context.PropertyValue as IList<ProfileQualityItemResource>;
if (list == null)
{
return false;
}
if (!list.Any(c => c.Allowed))
{
return false;
}
return true;
}
}
}

View File

@ -1,226 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using FluentValidation.Results;
using Nancy;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using NzbDrone.Common.Serializer;
using Sonarr.Http;
using Sonarr.Http.ClientSchema;
namespace NzbDrone.Api
{
public abstract class ProviderModuleBase<TProviderResource, TProvider, TProviderDefinition> : SonarrRestModule<TProviderResource>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
where TProviderResource : ProviderResource, new()
{
private readonly IProviderFactory<TProvider, TProviderDefinition> _providerFactory;
protected ProviderModuleBase(IProviderFactory<TProvider, TProviderDefinition> providerFactory, string resource)
: base(resource)
{
_providerFactory = providerFactory;
Get("schema", x => GetTemplates());
Post("test", x => Test(ReadResourceFromRequest(true)));
Post("action/{action}", x => RequestAction(x.action, ReadResourceFromRequest(true)));
GetResourceAll = GetAll;
GetResourceById = GetProviderById;
CreateResource = CreateProvider;
UpdateResource = UpdateProvider;
DeleteResource = DeleteProvider;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
SharedValidator.RuleFor(c => c.Name).Must((v,c) => !_providerFactory.All().Any(p => p.Name == c && p.Id != v.Id)).WithMessage("Should be unique");
SharedValidator.RuleFor(c => c.Implementation).NotEmpty();
SharedValidator.RuleFor(c => c.ConfigContract).NotEmpty();
PostValidator.RuleFor(c => c.Fields).NotNull();
}
private TProviderResource GetProviderById(int id)
{
var definition = _providerFactory.Get(id);
_providerFactory.SetProviderCharacteristics(definition);
var resource = new TProviderResource();
MapToResource(resource, definition);
return resource;
}
private List<TProviderResource> GetAll()
{
var providerDefinitions = _providerFactory.All().OrderBy(p => p.ImplementationName);
var result = new List<TProviderResource>(providerDefinitions.Count());
foreach (var definition in providerDefinitions)
{
_providerFactory.SetProviderCharacteristics(definition);
var providerResource = new TProviderResource();
MapToResource(providerResource, definition);
result.Add(providerResource);
}
return result.OrderBy(p => p.Name).ToList();
}
private int CreateProvider(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource, true, false, false);
if (providerDefinition.Enable)
{
Test(providerDefinition, false);
}
providerDefinition = _providerFactory.Create(providerDefinition);
return providerDefinition.Id;
}
private void UpdateProvider(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource, true, false, false);
_providerFactory.Update(providerDefinition);
}
private TProviderDefinition GetDefinition(TProviderResource providerResource, bool validate, bool includeWarnings, bool forceValidate)
{
var definition = new TProviderDefinition();
MapToModel(definition, providerResource);
if (validate && (definition.Enable || forceValidate))
{
Validate(definition, includeWarnings);
}
return definition;
}
protected virtual void MapToResource(TProviderResource resource, TProviderDefinition definition)
{
resource.Id = definition.Id;
resource.Name = definition.Name;
resource.ImplementationName = definition.ImplementationName;
resource.Implementation = definition.Implementation;
resource.ConfigContract = definition.ConfigContract;
resource.Message = definition.Message;
resource.Fields = SchemaBuilder.ToSchema(definition.Settings);
resource.InfoLink = $"https://wiki.servarr.com/sonarr/supported#{definition.Implementation.ToLower()}";
}
protected virtual void MapToModel(TProviderDefinition definition, TProviderResource resource)
{
definition.Id = resource.Id;
definition.Name = resource.Name;
definition.ImplementationName = resource.ImplementationName;
definition.Implementation = resource.Implementation;
definition.ConfigContract = resource.ConfigContract;
definition.Message = resource.Message;
var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract);
definition.Settings = (IProviderConfig)SchemaBuilder.ReadFromSchema(resource.Fields, configContract);
}
private void DeleteProvider(int id)
{
_providerFactory.Delete(id);
}
private object GetTemplates()
{
var defaultDefinitions = _providerFactory.GetDefaultDefinitions().OrderBy(p => p.ImplementationName).ToList();
var result = new List<TProviderResource>(defaultDefinitions.Count());
foreach (var providerDefinition in defaultDefinitions)
{
var providerResource = new TProviderResource();
MapToResource(providerResource, providerDefinition);
var presetDefinitions = _providerFactory.GetPresetDefinitions(providerDefinition);
providerResource.Presets = presetDefinitions.Select(v =>
{
var presetResource = new TProviderResource();
MapToResource(presetResource, v);
return presetResource as ProviderResource;
}).ToList();
result.Add(providerResource);
}
return result;
}
private object Test(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource, true, true, true);
Test(providerDefinition, true);
return "{}";
}
private object RequestAction(string action, TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource, false, false, false);
var query = ((IDictionary<string, object>)Request.Query.ToDictionary()).ToDictionary(k => k.Key, k => k.Value.ToString());
var data = _providerFactory.RequestAction(providerDefinition, action, query);
Response resp = data.ToJson();
resp.ContentType = "application/json";
return resp;
}
private void Validate(TProviderDefinition definition, bool includeWarnings)
{
var validationResult = definition.Settings.Validate();
VerifyValidationResult(validationResult, includeWarnings);
}
protected virtual void Test(TProviderDefinition definition, bool includeWarnings)
{
var validationResult = _providerFactory.Test(definition);
VerifyValidationResult(validationResult, includeWarnings);
}
protected void VerifyValidationResult(ValidationResult validationResult, bool includeWarnings)
{
var result = validationResult as NzbDroneValidationResult;
if (result == null)
{
result = new NzbDroneValidationResult(validationResult.Errors);
}
if (includeWarnings && (!result.IsValid || result.HasWarnings))
{
throw new ValidationException(result.Failures);
}
if (!result.IsValid)
{
throw new ValidationException(result.Errors);
}
}
}
}

View File

@ -1,20 +0,0 @@
using System.Collections.Generic;
using Sonarr.Http.REST;
using NzbDrone.Core.ThingiProvider;
using Sonarr.Http.ClientSchema;
namespace NzbDrone.Api
{
public class ProviderResource : RestResource
{
public string Name { get; set; }
public List<Field> Fields { get; set; }
public string ImplementationName { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public string InfoLink { get; set; }
public ProviderMessage Message { get; set; }
public List<ProviderResource> Presets { get; set; }
}
}

View File

@ -1,39 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Qualities;
using Sonarr.Http;
using Sonarr.Http.Mapping;
namespace NzbDrone.Api.Qualities
{
public class QualityDefinitionModule : SonarrRestModule<QualityDefinitionResource>
{
private readonly IQualityDefinitionService _qualityDefinitionService;
public QualityDefinitionModule(IQualityDefinitionService qualityDefinitionService)
{
_qualityDefinitionService = qualityDefinitionService;
GetResourceAll = GetAll;
GetResourceById = GetById;
UpdateResource = Update;
}
private void Update(QualityDefinitionResource resource)
{
var model = resource.ToModel();
_qualityDefinitionService.Update(model);
}
private QualityDefinitionResource GetById(int id)
{
return _qualityDefinitionService.GetById(id).ToResource();
}
private List<QualityDefinitionResource> GetAll()
{
return _qualityDefinitionService.All().ToResource();
}
}
}

View File

@ -1,65 +0,0 @@
using Sonarr.Http.REST;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Qualities
{
public class QualityDefinitionResource : RestResource
{
public Quality Quality { get; set; }
public string Title { get; set; }
public int Weight { get; set; }
public double? MinSize { get; set; }
public double? MaxSize { get; set; }
}
public static class QualityDefinitionResourceMapper
{
public static QualityDefinitionResource ToResource(this QualityDefinition model)
{
if (model == null) return null;
return new QualityDefinitionResource
{
Id = model.Id,
Quality = model.Quality,
Title = model.Title,
Weight = model.Weight,
MinSize = model.MinSize,
MaxSize = model.MaxSize
};
}
public static QualityDefinition ToModel(this QualityDefinitionResource resource)
{
if (resource == null) return null;
return new QualityDefinition
{
Id = resource.Id,
Quality = resource.Quality,
Title = resource.Title,
Weight = resource.Weight,
MinSize = resource.MinSize,
MaxSize = resource.MaxSize
};
}
public static List<QualityDefinitionResource> ToResource(this IEnumerable<QualityDefinition> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,134 +0,0 @@
using System;
using Nancy;
using Sonarr.Http.Extensions;
using Sonarr.Http.REST;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Queue;
using Sonarr.Http;
namespace NzbDrone.Api.Queue
{
public class QueueActionModule : SonarrRestModule<QueueResource>
{
private readonly IQueueService _queueService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly ICompletedDownloadService _completedDownloadService;
private readonly IFailedDownloadService _failedDownloadService;
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IPendingReleaseService _pendingReleaseService;
private readonly IDownloadService _downloadService;
public QueueActionModule(IQueueService queueService,
ITrackedDownloadService trackedDownloadService,
ICompletedDownloadService completedDownloadService,
IFailedDownloadService failedDownloadService,
IProvideDownloadClient downloadClientProvider,
IPendingReleaseService pendingReleaseService,
IDownloadService downloadService)
{
_queueService = queueService;
_trackedDownloadService = trackedDownloadService;
_completedDownloadService = completedDownloadService;
_failedDownloadService = failedDownloadService;
_downloadClientProvider = downloadClientProvider;
_pendingReleaseService = pendingReleaseService;
_downloadService = downloadService;
Delete(@"/(?<id>[\d]{1,10})", x => Remove((int)x.Id));
Post("/import", x => Import());
Post("/grab", x => Grab());
}
private object Remove(int id)
{
var blocklist = false;
var blocklistQuery = Request.Query.blocklist;
// blacklist maintained for backwards compatability, UI uses blocklist.
var blacklistQuery = Request.Query.blacklist;
if (blocklistQuery.HasValue)
{
blocklist = Convert.ToBoolean(blocklistQuery.Value);
}
else if (blacklistQuery.HasValue)
{
blocklist = Convert.ToBoolean(blacklistQuery.Value);
}
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
if (pendingRelease != null)
{
_pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id);
return new object();
}
var trackedDownload = GetTrackedDownload(id);
if (trackedDownload == null)
{
throw new NotFoundException();
}
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
if (downloadClient == null)
{
throw new BadRequestException();
}
downloadClient.RemoveItem(trackedDownload.DownloadItem, true);
if (blocklist)
{
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId);
}
return new object();
}
private object Import()
{
throw new BadRequestException("No longer available");
}
private object Grab()
{
var resource = Request.Body.FromJson<QueueResource>();
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(resource.Id);
if (pendingRelease == null)
{
throw new NotFoundException();
}
_downloadService.DownloadReport(pendingRelease.RemoteEpisode);
return resource;
}
private TrackedDownload GetTrackedDownload(int queueId)
{
var queueItem = _queueService.Find(queueId);
if (queueItem == null)
{
throw new NotFoundException();
}
var trackedDownload = _trackedDownloadService.Find(queueItem.DownloadId);
if (trackedDownload == null)
{
throw new NotFoundException();
}
return trackedDownload;
}
}
}

View File

@ -1,49 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Queue;
using NzbDrone.SignalR;
using Sonarr.Http;
namespace NzbDrone.Api.Queue
{
public class QueueModule : SonarrRestModuleWithSignalR<QueueResource, Core.Queue.Queue>,
IHandle<QueueUpdatedEvent>, IHandle<PendingReleasesUpdatedEvent>
{
private readonly IQueueService _queueService;
private readonly IPendingReleaseService _pendingReleaseService;
public QueueModule(IBroadcastSignalRMessage broadcastSignalRMessage, IQueueService queueService, IPendingReleaseService pendingReleaseService)
: base(broadcastSignalRMessage)
{
_queueService = queueService;
_pendingReleaseService = pendingReleaseService;
GetResourceAll = GetQueue;
}
private List<QueueResource> GetQueue()
{
return GetQueueItems().ToResource();
}
private IEnumerable<Core.Queue.Queue> GetQueueItems()
{
var queue = _queueService.GetQueue().Where(q => q.Series != null);
var pending = _pendingReleaseService.GetPendingQueue();
return queue.Concat(pending);
}
public void Handle(QueueUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
public void Handle(PendingReleasesUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
}
}

View File

@ -1,61 +0,0 @@
using System;
using System.Collections.Generic;
using Sonarr.Http.REST;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Api.Episodes;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Indexers;
using System.Linq;
namespace NzbDrone.Api.Queue
{
public class QueueResource : RestResource
{
public SeriesResource Series { get; set; }
public EpisodeResource Episode { get; set; }
public QualityModel Quality { get; set; }
public decimal Size { get; set; }
public string Title { get; set; }
public decimal Sizeleft { get; set; }
public TimeSpan? Timeleft { get; set; }
public DateTime? EstimatedCompletionTime { get; set; }
public string Status { get; set; }
public string TrackedDownloadStatus { get; set; }
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
public string DownloadId { get; set; }
public DownloadProtocol Protocol { get; set; }
}
public static class QueueResourceMapper
{
public static QueueResource ToResource(this Core.Queue.Queue model)
{
if (model == null) return null;
return new QueueResource
{
Id = model.Id,
Series = model.Series.ToResource(),
Episode = model.Episode.ToResource(),
Quality = model.Quality,
Size = model.Size,
Title = model.Title,
Sizeleft = model.Sizeleft,
Timeleft = model.Timeleft,
EstimatedCompletionTime = model.EstimatedCompletionTime,
Status = model.Status,
TrackedDownloadStatus = model.TrackedDownloadStatus.ToString(),
StatusMessages = model.StatusMessages,
DownloadId = model.DownloadId,
Protocol = model.Protocol
};
}
public static List<QueueResource> ToResource(this IEnumerable<Core.Queue.Queue> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,68 +0,0 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation.Paths;
using Sonarr.Http;
namespace NzbDrone.Api.RemotePathMappings
{
public class RemotePathMappingModule : SonarrRestModule<RemotePathMappingResource>
{
private readonly IRemotePathMappingService _remotePathMappingService;
public RemotePathMappingModule(IRemotePathMappingService remotePathMappingService,
PathExistsValidator pathExistsValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
{
_remotePathMappingService = remotePathMappingService;
GetResourceAll = GetMappings;
GetResourceById = GetMappingById;
CreateResource = CreateMapping;
DeleteResource = DeleteMapping;
UpdateResource = UpdateMapping;
SharedValidator.RuleFor(c => c.Host)
.NotEmpty();
// We cannot use IsValidPath here, because it's a remote path, possibly other OS.
SharedValidator.RuleFor(c => c.RemotePath)
.NotEmpty();
SharedValidator.RuleFor(c => c.LocalPath)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator);
}
private RemotePathMappingResource GetMappingById(int id)
{
return _remotePathMappingService.Get(id).ToResource();
}
private int CreateMapping(RemotePathMappingResource resource)
{
var model = resource.ToModel();
return _remotePathMappingService.Add(model).Id;
}
private List<RemotePathMappingResource> GetMappings()
{
return _remotePathMappingService.All().ToResource();
}
private void DeleteMapping(int id)
{
_remotePathMappingService.Remove(id);
}
private void UpdateMapping(RemotePathMappingResource resource)
{
var mapping = resource.ToModel();
_remotePathMappingService.Update(mapping);
}
}
}

View File

@ -1,50 +0,0 @@
using Sonarr.Http.REST;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.RemotePathMappings;
namespace NzbDrone.Api.RemotePathMappings
{
public class RemotePathMappingResource : RestResource
{
public string Host { get; set; }
public string RemotePath { get; set; }
public string LocalPath { get; set; }
}
public static class RemotePathMappingResourceMapper
{
public static RemotePathMappingResource ToResource(this RemotePathMapping model)
{
if (model == null) return null;
return new RemotePathMappingResource
{
Id = model.Id,
Host = model.Host,
RemotePath = model.RemotePath,
LocalPath = model.LocalPath
};
}
public static RemotePathMapping ToModel(this RemotePathMappingResource resource)
{
if (resource == null) return null;
return new RemotePathMapping
{
Id = resource.Id,
Host = resource.Host,
RemotePath = resource.RemotePath,
LocalPath = resource.LocalPath
};
}
public static List<RemotePathMappingResource> ToResource(this IEnumerable<RemotePathMapping> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,60 +0,0 @@
using System.Collections.Generic;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http;
using Sonarr.Http.Mapping;
namespace NzbDrone.Api.Restrictions
{
public class RestrictionModule : SonarrRestModule<RestrictionResource>
{
private readonly IReleaseProfileService _releaseProfileService;
public RestrictionModule(IReleaseProfileService releaseProfileService)
{
_releaseProfileService = releaseProfileService;
GetResourceById = GetRestriction;
GetResourceAll = GetAllRestrictions;
CreateResource = CreateRestriction;
UpdateResource = UpdateRestriction;
DeleteResource = DeleteRestriction;
SharedValidator.RuleFor(r => r).Custom((restriction, context) =>
{
if (restriction.Ignored.IsNullOrWhiteSpace() && restriction.Required.IsNullOrWhiteSpace())
{
context.AddFailure("Either 'Must contain' or 'Must not contain' is required");
}
});
}
private RestrictionResource GetRestriction(int id)
{
return _releaseProfileService.Get(id).ToResource();
}
private List<RestrictionResource> GetAllRestrictions()
{
return _releaseProfileService.All().ToResource();
}
private int CreateRestriction(RestrictionResource resource)
{
return _releaseProfileService.Add(resource.ToModel()).Id;
}
private void UpdateRestriction(RestrictionResource resource)
{
_releaseProfileService.Update(resource.ToModel());
}
private void DeleteRestriction(int id)
{
_releaseProfileService.Delete(id);
}
}
}

View File

@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http.REST;
namespace NzbDrone.Api.Restrictions
{
public class RestrictionResource : RestResource
{
public string Required { get; set; }
public string Ignored { get; set; }
public HashSet<int> Tags { get; set; }
public RestrictionResource()
{
Tags = new HashSet<int>();
}
}
public static class RestrictionResourceMapper
{
public static RestrictionResource ToResource(this ReleaseProfile model)
{
if (model == null) return null;
return new RestrictionResource
{
Id = model.Id,
Required = string.Join(",", model.Required),
Ignored = string.Join(",", model.Ignored),
Tags = new HashSet<int>(model.Tags)
};
}
public static ReleaseProfile ToModel(this RestrictionResource resource)
{
if (resource == null) return null;
return new ReleaseProfile
{
Id = resource.Id,
Required = resource.Required.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
Ignored = resource.Ignored.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
Tags = new HashSet<int>(resource.Tags)
};
}
public static List<RestrictionResource> ToResource(this IEnumerable<ReleaseProfile> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,66 +0,0 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.SignalR;
using Sonarr.Http;
using Sonarr.Http.Mapping;
namespace NzbDrone.Api.RootFolders
{
public class RootFolderModule : SonarrRestModuleWithSignalR<RootFolderResource, RootFolder>
{
private readonly IRootFolderService _rootFolderService;
public RootFolderModule(IRootFolderService rootFolderService,
IBroadcastSignalRMessage signalRBroadcaster,
RootFolderValidator rootFolderValidator,
PathExistsValidator pathExistsValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator,
StartupFolderValidator startupFolderValidator,
SystemFolderValidator systemFolderValidator,
FolderWritableValidator folderWritableValidator
)
: base(signalRBroadcaster)
{
_rootFolderService = rootFolderService;
GetResourceAll = GetRootFolders;
GetResourceById = GetRootFolder;
CreateResource = CreateRootFolder;
DeleteResource = DeleteFolder;
SharedValidator.RuleFor(c => c.Path)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(startupFolderValidator)
.SetValidator(pathExistsValidator)
.SetValidator(systemFolderValidator)
.SetValidator(folderWritableValidator);
}
private RootFolderResource GetRootFolder(int id)
{
return _rootFolderService.Get(id, true).ToResource();
}
private int CreateRootFolder(RootFolderResource rootFolderResource)
{
var model = rootFolderResource.ToModel();
return _rootFolderService.Add(model).Id;
}
private List<RootFolderResource> GetRootFolders()
{
return _rootFolderService.AllWithUnmappedFolders().ToResource();
}
private void DeleteFolder(int id)
{
_rootFolderService.Remove(id);
}
}
}

View File

@ -1,53 +0,0 @@
using System.Collections.Generic;
using Sonarr.Http.REST;
using System.Linq;
using NzbDrone.Core.RootFolders;
namespace NzbDrone.Api.RootFolders
{
public class RootFolderResource : RestResource
{
public string Path { get; set; }
public long? FreeSpace { get; set; }
public long? TotalSpace { get; set; }
public List<UnmappedFolder> UnmappedFolders { get; set; }
}
public static class RootFolderResourceMapper
{
public static RootFolderResource ToResource(this RootFolder model)
{
if (model == null) return null;
return new RootFolderResource
{
Id = model.Id,
Path = model.Path,
FreeSpace = model.FreeSpace,
TotalSpace = model.TotalSpace,
UnmappedFolders = model.UnmappedFolders
};
}
public static RootFolder ToModel(this RootFolderResource resource)
{
if (resource == null) return null;
return new RootFolder
{
Id = resource.Id,
Path = resource.Path,
//FreeSpace
//UnmappedFolders
};
}
public static List<RootFolderResource> ToResource(this IEnumerable<RootFolder> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@ -1,31 +0,0 @@
using Nancy;
using Sonarr.Http.Extensions;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.SeasonPass
{
public class SeasonPassModule : NzbDroneApiModule
{
private readonly IEpisodeMonitoredService _episodeMonitoredService;
public SeasonPassModule(IEpisodeMonitoredService episodeMonitoredService)
: base("/seasonpass")
{
_episodeMonitoredService = episodeMonitoredService;
Post("/", series => UpdateAll());
}
private object UpdateAll()
{
//Read from request
var request = Request.Body.FromJson<SeasonPassResource>();
foreach (var s in request.Series)
{
_episodeMonitoredService.SetEpisodeMonitoredStatus(s, request.MonitoringOptions);
}
return ResponseWithCode("ok", HttpStatusCode.Accepted);
}
}
}

View File

@ -1,11 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.SeasonPass
{
public class SeasonPassResource
{
public List<Core.Tv.Series> Series { get; set; }
public MonitoringOptions MonitoringOptions { get; set; }
}
}

View File

@ -1,33 +0,0 @@
using NzbDrone.Core.DataAugmentation.Scene;
namespace NzbDrone.Api.Series
{
public class AlternateTitleResource
{
public string Title { get; set; }
public int? SeasonNumber { get; set; }
public int? SceneSeasonNumber { get; set; }
public string SceneOrigin { get; set; }
public string Comment { get; set; }
}
public static class AlternateTitleResourceMapper
{
public static AlternateTitleResource ToResource(this SceneMapping sceneMapping)
{
if (sceneMapping == null)
{
return null;
}
return new AlternateTitleResource
{
Title = sceneMapping.Title,
SeasonNumber = sceneMapping.SeasonNumber,
SceneSeasonNumber = sceneMapping.SceneSeasonNumber,
SceneOrigin = sceneMapping.SceneOrigin,
Comment = sceneMapping.Comment
};
}
}
}

View File

@ -1,51 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Series
{
public class SeasonResource
{
public int SeasonNumber { get; set; }
public bool Monitored { get; set; }
public SeasonStatisticsResource Statistics { get; set; }
public List<MediaCover> Images { get; set; }
}
public static class SeasonResourceMapper
{
public static SeasonResource ToResource(this Season model, bool includeImages = false)
{
if (model == null) return null;
return new SeasonResource
{
SeasonNumber = model.SeasonNumber,
Monitored = model.Monitored,
Images = includeImages ? model.Images : null
};
}
public static Season ToModel(this SeasonResource resource)
{
if (resource == null) return null;
return new Season
{
SeasonNumber = resource.SeasonNumber,
Monitored = resource.Monitored,
Images = resource.Images
};
}
public static List<SeasonResource> ToResource(this IEnumerable<Season> models, bool includeImages = false)
{
return models.Select(s => ToResource(s, includeImages)).ToList();
}
public static List<Season> ToModel(this IEnumerable<SeasonResource> resources)
{
return resources?.Select(ToModel).ToList() ?? new List<Season>();
}
}
}

View File

@ -1,43 +0,0 @@
using System;
using NzbDrone.Core.SeriesStats;
namespace NzbDrone.Api.Series
{
public class SeasonStatisticsResource
{
public DateTime? NextAiring { get; set; }
public DateTime? PreviousAiring { get; set; }
public int EpisodeFileCount { get; set; }
public int EpisodeCount { get; set; }
public int TotalEpisodeCount { get; set; }
public long SizeOnDisk { get; set; }
public decimal PercentOfEpisodes
{
get
{
if (EpisodeCount == 0) return 0;
return (decimal)EpisodeFileCount / (decimal)EpisodeCount * 100;
}
}
}
public static class SeasonStatisticsResourceMapper
{
public static SeasonStatisticsResource ToResource(this SeasonStatistics model)
{
if (model == null) return null;
return new SeasonStatisticsResource
{
NextAiring = model.NextAiring,
PreviousAiring = model.PreviousAiring,
EpisodeFileCount = model.EpisodeFileCount,
EpisodeCount = model.EpisodeFileCount,
TotalEpisodeCount = model.TotalEpisodeCount,
SizeOnDisk = model.SizeOnDisk
};
}
}
}

View File

@ -1,49 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Nancy;
using NzbDrone.Core.Profiles.Languages;
using Sonarr.Http.Extensions;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Series
{
public class SeriesEditorModule : NzbDroneApiModule
{
private readonly ISeriesService _seriesService;
private readonly ILanguageProfileService _languageProfileService;
public SeriesEditorModule(ISeriesService seriesService, ILanguageProfileService languageProfileService)
: base("/series/editor")
{
_seriesService = seriesService;
_languageProfileService = languageProfileService;
Put("/", series => SaveAll());
}
private object SaveAll()
{
var resources = Request.Body.FromJson<List<SeriesResource>>();
var seriesToUpdate = resources.Select(seriesResource =>
{
var series = _seriesService.GetSeries(seriesResource.Id);
var updatedSeries = seriesResource.ToModel(series);
// If the new language profile doens't exist, keep it the same.
// This could happen if a 3rd-party app uses this endpoint to update a
// series and doesn't pass the languageProfileI as well.
if (!_languageProfileService.Exists(updatedSeries.LanguageProfileId))
{
updatedSeries.LanguageProfileId = series.LanguageProfileId;
}
return updatedSeries;
}).ToList();
return ResponseWithCode(_seriesService.UpdateSeries(seriesToUpdate, true)
.ToResource(false)
, HttpStatusCode.Accepted);
}
}
}

View File

@ -1,44 +0,0 @@
using System.Collections.Generic;
using Nancy;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using System.Linq;
using Sonarr.Http;
namespace NzbDrone.Api.Series
{
public class SeriesLookupModule : SonarrRestModule<SeriesResource>
{
private readonly ISearchForNewSeries _searchProxy;
public SeriesLookupModule(ISearchForNewSeries searchProxy)
: base("/series/lookup")
{
_searchProxy = searchProxy;
Get("/", x => Search());
}
private object Search()
{
var tvDbResults = _searchProxy.SearchForNewSeries((string)Request.Query.term);
return MapToResource(tvDbResults);
}
private static IEnumerable<SeriesResource> MapToResource(IEnumerable<Core.Tv.Series> series)
{
foreach (var currentSeries in series)
{
var resource = currentSeries.ToResource();
var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;
}
yield return resource;
}
}
}
}

View File

@ -1,268 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Validation;
using NzbDrone.SignalR;
using Sonarr.Http;
using Sonarr.Http.Extensions;
namespace NzbDrone.Api.Series
{
public class SeriesModule : SonarrRestModuleWithSignalR<SeriesResource, Core.Tv.Series>,
IHandle<EpisodeImportedEvent>,
IHandle<EpisodeFileDeletedEvent>,
IHandle<SeriesUpdatedEvent>,
IHandle<SeriesEditedEvent>,
IHandle<SeriesDeletedEvent>,
IHandle<SeriesRenamedEvent>,
IHandle<MediaCoversUpdatedEvent>
{
private readonly ISeriesService _seriesService;
private readonly IAddSeriesService _addSeriesService;
private readonly ISeriesStatisticsService _seriesStatisticsService;
private readonly ISceneMappingService _sceneMappingService;
private readonly IMapCoversToLocal _coverMapper;
private readonly ILanguageProfileService _languageProfileService;
public SeriesModule(IBroadcastSignalRMessage signalRBroadcaster,
ISeriesService seriesService,
IAddSeriesService addSeriesService,
ISeriesStatisticsService seriesStatisticsService,
ISceneMappingService sceneMappingService,
IMapCoversToLocal coverMapper,
ILanguageProfileService languageProfileService,
RootFolderValidator rootFolderValidator,
SeriesPathValidator seriesPathValidator,
SeriesExistsValidator seriesExistsValidator,
SeriesAncestorValidator seriesAncestorValidator,
SystemFolderValidator systemFolderValidator,
ProfileExistsValidator profileExistsValidator,
LanguageProfileExistsValidator languageProfileExistsValidator
)
: base(signalRBroadcaster)
{
_seriesService = seriesService;
_addSeriesService = addSeriesService;
_seriesStatisticsService = seriesStatisticsService;
_sceneMappingService = sceneMappingService;
_coverMapper = coverMapper;
_languageProfileService = languageProfileService;
GetResourceAll = AllSeries;
GetResourceById = GetSeries;
CreateResource = AddSeries;
UpdateResource = UpdateSeries;
DeleteResource = DeleteSeries;
SharedValidator.RuleFor(s => s.ProfileId).ValidId();
SharedValidator.RuleFor(s => s.LanguageProfileId);
SharedValidator.RuleFor(s => s.Path)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(seriesPathValidator)
.SetValidator(seriesAncestorValidator)
.SetValidator(systemFolderValidator)
.When(s => !s.Path.IsNullOrWhiteSpace());
SharedValidator.RuleFor(s => s.ProfileId).SetValidator(profileExistsValidator);
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator);
PostValidator.RuleFor(s => s.LanguageProfileId).SetValidator(languageProfileExistsValidator).When(s => s.LanguageProfileId != 0);
PutValidator.RuleFor(s => s.Path).IsValidPath();
// Ensure any editing has a valid LanguageProfile
PutValidator.RuleFor(s => s.LanguageProfileId).SetValidator(languageProfileExistsValidator);
}
private SeriesResource GetSeries(int id)
{
var includeSeasonImages = Context != null && Request.GetBooleanQueryParameter("includeSeasonImages");
var series = _seriesService.GetSeries(id);
return MapToResource(series, includeSeasonImages);
}
private List<SeriesResource> AllSeries()
{
var includeSeasonImages = Request.GetBooleanQueryParameter("includeSeasonImages");
var seriesStats = _seriesStatisticsService.SeriesStatistics();
var seriesResources = _seriesService.GetAllSeries().Select(s => s.ToResource(includeSeasonImages)).ToList();
MapCoversToLocal(seriesResources.ToArray());
LinkSeriesStatistics(seriesResources, seriesStats);
PopulateAlternateTitles(seriesResources);
return seriesResources;
}
private int AddSeries(SeriesResource seriesResource)
{
var model = seriesResource.ToModel();
// Set a default LanguageProfileId to maintain backwards compatibility with apps using the v2 API
if (model.LanguageProfileId == 0 || !_languageProfileService.Exists(model.LanguageProfileId))
{
model.LanguageProfileId = _languageProfileService.All().First().Id;
}
return _addSeriesService.AddSeries(model).Id;
}
private void UpdateSeries(SeriesResource seriesResource)
{
var model = seriesResource.ToModel(_seriesService.GetSeries(seriesResource.Id));
_seriesService.UpdateSeries(model);
BroadcastResourceChange(ModelAction.Updated, seriesResource);
}
private void DeleteSeries(int id)
{
var deleteFiles = false;
var deleteFilesQuery = Request.Query.deleteFiles;
if (deleteFilesQuery.HasValue)
{
deleteFiles = Convert.ToBoolean(deleteFilesQuery.Value);
}
_seriesService.DeleteSeries(id, deleteFiles, false);
}
private SeriesResource MapToResource(Core.Tv.Series series, bool includeSeasonImages)
{
if (series == null) return null;
var resource = series.ToResource(includeSeasonImages);
MapCoversToLocal(resource);
FetchAndLinkSeriesStatistics(resource);
PopulateAlternateTitles(resource);
return resource;
}
private void MapCoversToLocal(params SeriesResource[] series)
{
foreach (var seriesResource in series)
{
_coverMapper.ConvertToLocalUrls(seriesResource.Id, seriesResource.Images);
}
}
private void FetchAndLinkSeriesStatistics(SeriesResource resource)
{
LinkSeriesStatistics(resource, _seriesStatisticsService.SeriesStatistics(resource.Id));
}
private void LinkSeriesStatistics(List<SeriesResource> resources, List<SeriesStatistics> seriesStatistics)
{
var dictSeriesStats = seriesStatistics.ToDictionary(v => v.SeriesId);
foreach (var series in resources)
{
var stats = dictSeriesStats.GetValueOrDefault(series.Id);
if (stats == null) continue;
LinkSeriesStatistics(series, stats);
}
}
private void LinkSeriesStatistics(SeriesResource resource, SeriesStatistics seriesStatistics)
{
resource.TotalEpisodeCount = seriesStatistics.TotalEpisodeCount;
resource.EpisodeCount = seriesStatistics.EpisodeCount;
resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount;
resource.NextAiring = seriesStatistics.NextAiring;
resource.PreviousAiring = seriesStatistics.PreviousAiring;
resource.SizeOnDisk = seriesStatistics.SizeOnDisk;
if (seriesStatistics.SeasonStatistics != null)
{
var dictSeasonStats = seriesStatistics.SeasonStatistics.ToDictionary(v => v.SeasonNumber);
foreach (var season in resource.Seasons)
{
season.Statistics = dictSeasonStats.GetValueOrDefault(season.SeasonNumber).ToResource();
}
}
}
private void PopulateAlternateTitles(List<SeriesResource> resources)
{
foreach (var resource in resources)
{
PopulateAlternateTitles(resource);
}
}
private void PopulateAlternateTitles(SeriesResource resource)
{
var mappings = _sceneMappingService.FindByTvdbId(resource.TvdbId);
if (mappings == null) return;
resource.AlternateTitles = mappings.ConvertAll(AlternateTitleResourceMapper.ToResource);
}
public void Handle(EpisodeImportedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.ImportedEpisode.SeriesId);
}
public void Handle(EpisodeFileDeletedEvent message)
{
if (message.Reason == DeleteMediaFileReason.Upgrade) return;
BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.SeriesId);
}
public void Handle(SeriesUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
}
public void Handle(SeriesEditedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
}
public void Handle(SeriesDeletedEvent message)
{
BroadcastResourceChange(ModelAction.Deleted, message.Series.ToResource());
}
public void Handle(SeriesRenamedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
}
public void Handle(MediaCoversUpdatedEvent message)
{
if (message.Updated)
{
BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
}
}
}
}

View File

@ -1,223 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Sonarr.Http.REST;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Series
{
public class SeriesResource : RestResource
{
public SeriesResource()
{
Monitored = true;
}
//Todo: Sorters should be done completely on the client
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
//Todo: We should get the entire QualityProfile instead of ID and Name separately
//View Only
public string Title { get; set; }
public List<AlternateTitleResource> AlternateTitles { get; set; }
public string SortTitle { get; set; }
public int SeasonCount
{
get
{
if (Seasons == null) return 0;
return Seasons.Where(s => s.SeasonNumber > 0).Count();
}
}
public int? TotalEpisodeCount { get; set; }
public int? EpisodeCount { get; set; }
public int? EpisodeFileCount { get; set; }
public long? SizeOnDisk { get; set; }
public SeriesStatusType Status { get; set; }
public string Overview { get; set; }
public DateTime? NextAiring { get; set; }
public DateTime? PreviousAiring { get; set; }
public string Network { get; set; }
public string AirTime { get; set; }
public List<MediaCover> Images { get; set; }
public string RemotePoster { get; set; }
public List<SeasonResource> Seasons { get; set; }
public int Year { get; set; }
//View & Edit
public string Path { get; set; }
public int ProfileId { get; set; }
public int LanguageProfileId { get; set; }
//Editing Only
public bool SeasonFolder { get; set; }
public bool Monitored { get; set; }
public bool UseSceneNumbering { get; set; }
public int Runtime { get; set; }
public int TvdbId { get; set; }
public int TvRageId { get; set; }
public int TvMazeId { get; set; }
public DateTime? FirstAired { get; set; }
public DateTime? LastInfoSync { get; set; }
public SeriesTypes SeriesType { get; set; }
public string CleanTitle { get; set; }
public string ImdbId { get; set; }
public string TitleSlug { get; set; }
public string RootFolderPath { get; set; }
public string Certification { get; set; }
public List<string> Genres { get; set; }
public HashSet<int> Tags { get; set; }
public DateTime Added { get; set; }
public AddSeriesOptions AddOptions { get; set; }
public Ratings Ratings { get; set; }
//Used to support legacy consumers
public int QualityProfileId
{
get
{
return ProfileId;
}
set
{
if (value > 0 && ProfileId == 0)
{
ProfileId = value;
}
}
}
}
public static class SeriesResourceMapper
{
public static SeriesResource ToResource(this Core.Tv.Series model, bool includeSeasonImages = false)
{
if (model == null) return null;
return new SeriesResource
{
Id = model.Id,
Title = model.Title,
//AlternateTitles
SortTitle = model.SortTitle,
//TotalEpisodeCount
//EpisodeCount
//EpisodeFileCount
//SizeOnDisk
Status = model.Status,
Overview = model.Overview,
//NextAiring
//PreviousAiring
Network = model.Network,
AirTime = model.AirTime,
Images = model.Images,
Seasons = model.Seasons.ToResource(includeSeasonImages),
Year = model.Year,
Path = model.Path,
ProfileId = model.QualityProfileId,
LanguageProfileId = model.LanguageProfileId,
SeasonFolder = model.SeasonFolder,
Monitored = model.Monitored,
UseSceneNumbering = model.UseSceneNumbering,
Runtime = model.Runtime,
TvdbId = model.TvdbId,
TvRageId = model.TvRageId,
TvMazeId = model.TvMazeId,
FirstAired = model.FirstAired,
LastInfoSync = model.LastInfoSync,
SeriesType = model.SeriesType,
CleanTitle = model.CleanTitle,
ImdbId = model.ImdbId,
TitleSlug = model.TitleSlug,
RootFolderPath = model.RootFolderPath,
Certification = model.Certification,
Genres = model.Genres,
Tags = model.Tags,
Added = model.Added,
AddOptions = model.AddOptions,
Ratings = model.Ratings
};
}
public static Core.Tv.Series ToModel(this SeriesResource resource)
{
if (resource == null) return null;
return new Core.Tv.Series
{
Id = resource.Id,
Title = resource.Title,
//AlternateTitles
SortTitle = resource.SortTitle,
//TotalEpisodeCount
//EpisodeCount
//EpisodeFileCount
//SizeOnDisk
Status = resource.Status,
Overview = resource.Overview,
//NextAiring
//PreviousAiring
Network = resource.Network,
AirTime = resource.AirTime,
Images = resource.Images,
Seasons = resource.Seasons.ToModel(),
Year = resource.Year,
Path = resource.Path,
QualityProfileId = resource.ProfileId,
LanguageProfileId = resource.LanguageProfileId,
SeasonFolder = resource.SeasonFolder,
Monitored = resource.Monitored,
UseSceneNumbering = resource.UseSceneNumbering,
Runtime = resource.Runtime,
TvdbId = resource.TvdbId,
TvRageId = resource.TvRageId,
TvMazeId = resource.TvMazeId,
FirstAired = resource.FirstAired,
LastInfoSync = resource.LastInfoSync,
SeriesType = resource.SeriesType,
CleanTitle = resource.CleanTitle,
ImdbId = resource.ImdbId,
TitleSlug = resource.TitleSlug,
RootFolderPath = resource.RootFolderPath,
Certification = resource.Certification,
Genres = resource.Genres,
Tags = resource.Tags,
Added = resource.Added,
AddOptions = resource.AddOptions,
Ratings = resource.Ratings
};
}
public static Core.Tv.Series ToModel(this SeriesResource resource, Core.Tv.Series series)
{
var updatedSeries = resource.ToModel();
series.ApplyChanges(updatedSeries);
return series;
}
public static List<SeriesResource> ToResource(this IEnumerable<Core.Tv.Series> series, bool includeSeasonImages)
{
return series.Select(s => ToResource(s, includeSeasonImages)).ToList();
}
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<Platforms>x86</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="8.4.0" />
<PackageReference Include="Ical.Net" Version="4.1.11" />
<PackageReference Include="Nancy" Version="2.0.0" />
<PackageReference Include="Nancy.Authentication.Basic" Version="2.0.0" />
<PackageReference Include="Nancy.Authentication.Forms" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\Sonarr.Core.csproj" />
<ProjectReference Include="..\Sonarr.Http\Sonarr.Http.csproj" />
</ItemGroup>
</Project>

View File

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

View File

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

View File

@ -1,89 +0,0 @@
using Nancy.Routing;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle;
namespace NzbDrone.Api.System
{
public class SystemModule : NzbDroneApiModule
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IPlatformInfo _platformInfo;
private readonly IOsInfo _osInfo;
private readonly IRouteCacheProvider _routeCacheProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IMainDatabase _database;
private readonly ILifecycleService _lifecycleService;
public SystemModule(IAppFolderInfo appFolderInfo,
IRuntimeInfo runtimeInfo,
IPlatformInfo platformInfo,
IOsInfo osInfo,
IRouteCacheProvider routeCacheProvider,
IConfigFileProvider configFileProvider,
IMainDatabase database,
ILifecycleService lifecycleService) : base("system")
{
_appFolderInfo = appFolderInfo;
_runtimeInfo = runtimeInfo;
_platformInfo = platformInfo;
_osInfo = osInfo;
_routeCacheProvider = routeCacheProvider;
_configFileProvider = configFileProvider;
_database = database;
_lifecycleService = lifecycleService;
Get("/status", x => GetStatus());
Get("/routes", x => GetRoutes());
Post("/shutdown", x => Shutdown());
Post("/restart", x => Restart());
}
private object GetStatus()
{
return new
{
Version = BuildInfo.Version.ToString(),
BuildTime = BuildInfo.BuildDateTime,
IsDebug = BuildInfo.IsDebug,
IsProduction = RuntimeInfo.IsProduction,
IsAdmin = _runtimeInfo.IsAdmin,
IsUserInteractive = RuntimeInfo.IsUserInteractive,
StartupPath = _appFolderInfo.StartUpFolder,
AppData = _appFolderInfo.GetAppDataPath(),
OsName = _osInfo.Name,
OsVersion = _osInfo.Version,
IsMonoRuntime = PlatformInfo.IsMono,
IsMono = PlatformInfo.IsMono,
IsLinux = OsInfo.IsLinux,
IsOsx = OsInfo.IsOsx,
IsWindows = OsInfo.IsWindows,
Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationMethod,
SqliteVersion = _database.Version,
UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = _platformInfo.Version,
RuntimeName = PlatformInfo.Platform
};
}
private object GetRoutes()
{
return _routeCacheProvider.GetCache().Values;
}
private object Shutdown()
{
_lifecycleService.Shutdown();
return "";
}
private object Restart()
{
_lifecycleService.Restart();
return "";
}
}
}

View File

@ -1,50 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.SignalR;
using Sonarr.Http;
namespace NzbDrone.Api.System.Tasks
{
public class TaskModule : SonarrRestModuleWithSignalR<TaskResource, ScheduledTask>, IHandle<CommandExecutedEvent>
{
private readonly ITaskManager _taskManager;
private static readonly Regex NameRegex = new Regex("(?<!^)[A-Z]", RegexOptions.Compiled);
public TaskModule(ITaskManager taskManager, IBroadcastSignalRMessage broadcastSignalRMessage)
: base(broadcastSignalRMessage, "system/task")
{
_taskManager = taskManager;
GetResourceAll = GetAll;
}
private List<TaskResource> GetAll()
{
return _taskManager.GetAll().Select(ConvertToResource).ToList();
}
private static TaskResource ConvertToResource(ScheduledTask scheduledTask)
{
var taskName = scheduledTask.TypeName.Split('.').Last().Replace("Command", "");
return new TaskResource
{
Id = scheduledTask.Id,
Name = NameRegex.Replace(taskName, match => " " + match.Value),
TaskName = taskName,
Interval = scheduledTask.Interval,
LastExecution = scheduledTask.LastExecution,
NextExecution = scheduledTask.LastExecution.AddMinutes(scheduledTask.Interval)
};
}
public void Handle(CommandExecutedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
}
}

View File

@ -1,14 +0,0 @@
using System;
using Sonarr.Http.REST;
namespace NzbDrone.Api.System.Tasks
{
public class TaskResource : RestResource
{
public string Name { get; set; }
public string TaskName { get; set; }
public int Interval { get; set; }
public DateTime LastExecution { get; set; }
public DateTime NextExecution { get; set; }
}
}

View File

@ -1,62 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tags;
using NzbDrone.SignalR;
using Sonarr.Http;
using Sonarr.Http.Mapping;
namespace NzbDrone.Api.Tags
{
public class TagModule : SonarrRestModuleWithSignalR<TagResource, Tag>, IHandle<TagsUpdatedEvent>
{
private readonly ITagService _tagService;
public TagModule(IBroadcastSignalRMessage signalRBroadcaster,
ITagService tagService)
: base(signalRBroadcaster)
{
_tagService = tagService;
GetResourceById = GetTag;
GetResourceAll = GetAllTags;
CreateResource = CreateTag;
UpdateResource = UpdateTag;
DeleteResource = DeleteTag;
}
private TagResource GetTag(int id)
{
return _tagService.GetTag(id).ToResource();
}
private List<TagResource> GetAllTags()
{
return _tagService.All().ToResource();
}
private int CreateTag(TagResource resource)
{
var model = resource.ToModel();
return _tagService.Add(model).Id;
}
private void UpdateTag(TagResource resource)
{
var model = resource.ToModel();
_tagService.Update(model);
}
private void DeleteTag(int id)
{
_tagService.Delete(id);
}
public void Handle(TagsUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
}
}

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