mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-22 17:04:39 -04:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a8d944397 | ||
|
|
0b70a5c315 | ||
|
|
91cded3b71 | ||
|
|
84112dc85b | ||
|
|
b1b947ae7f | ||
|
|
1b035f8657 | ||
|
|
9d50f4d651 | ||
|
|
c06a6dc988 | ||
|
|
f198ca2b77 | ||
|
|
9cfc766889 | ||
|
|
5ef1e40403 | ||
|
|
c9e6835d7b | ||
|
|
604cea00f6 | ||
|
|
b4782da1d1 | ||
|
|
d5504043c5 | ||
|
|
81ebbcad70 | ||
|
|
92e9dc6ee1 | ||
|
|
5e8b617625 | ||
|
|
2cbe17151d | ||
|
|
95bd615718 | ||
|
|
8e8c4ff497 | ||
|
|
5eddcc1660 | ||
|
|
d42165a93a | ||
|
|
99012d8a40 | ||
|
|
8bc42c76e4 | ||
|
|
b7f72c6259 | ||
|
|
64ef8db037 | ||
|
|
3b5887bf09 | ||
|
|
40a75949ba | ||
|
|
6747267d19 | ||
|
|
13db03f97c | ||
|
|
cd68eea790 | ||
|
|
cfe55d00ae | ||
|
|
1d88313424 | ||
|
|
9513068467 | ||
|
|
9206258370 | ||
|
|
0fa1509ca6 | ||
|
|
c5eb772f7a | ||
|
|
04fec6d4d8 | ||
|
|
d0b5e380d7 | ||
|
|
df69c58d2b | ||
|
|
27114c9399 | ||
|
|
cfca07996b | ||
|
|
e97b80f630 |
4
CLA.md
4
CLA.md
@@ -1,6 +1,6 @@
|
||||
# Sonarr Individual Contributor License Agreement #
|
||||
# Radarr Individual Contributor License Agreement #
|
||||
|
||||
Thank you for your interest in contributing to Sonarr ("We" or "Us").
|
||||
Thank you for your interest in contributing to Radarr ("We" or "Us").
|
||||
This contributor agreement ("Agreement") documents the rights granted by contributors to Us. To make this document effective, please complete the form below. This is a legally binding document, so please read it carefully before agreeing to it. The Agreement may cover more than one software project managed by Us.
|
||||
|
||||
## 1. Definitions ##
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# How to Contribute #
|
||||
|
||||
We're always looking for people to help make Sonarr even better, there are a number of ways to contribute.
|
||||
We're always looking for people to help make Radarr even better, there are a number of ways to contribute.
|
||||
|
||||
## Documentation ##
|
||||
Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
@@ -15,7 +15,7 @@ Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
|
||||
### Getting started ###
|
||||
|
||||
1. Fork Sonarr
|
||||
1. Fork Radarr
|
||||
2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)*
|
||||
3. Run `npm install`
|
||||
4. Run `npm start` - Used to compile the UI components and copy them.
|
||||
@@ -24,8 +24,8 @@ Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
5. Compile in Visual Studio
|
||||
|
||||
### Contributing Code ###
|
||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Sonarr/Sonarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Sonarr's develop branch, don't merge
|
||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Radarr/Radarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Radarr's develop branch, don't merge
|
||||
- Make meaningful commits, or squash them
|
||||
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
|
||||
- Reach out to us on the forums or on IRC if you have any questions
|
||||
|
||||
@@ -35,4 +35,11 @@ artifacts:
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.nuget\packages'
|
||||
- node_modules
|
||||
- node_modules
|
||||
|
||||
only_commits:
|
||||
files:
|
||||
- src/
|
||||
- osx/
|
||||
- gulp/
|
||||
- logo/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
| Service | Master | Develop |
|
||||
|----------|:---------------------------:|:----------------------------:|
|
||||
| AppVeyor | [](https://ci.appveyor.com/project/galli-leo/Radarr) | [](https://ci.appveyor.com/project/galli-leo/Radarr) |
|
||||
| AppVeyor | [](https://ci.appveyor.com/project/galli-leo/Radarr) | [](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
|
||||
| Travis | [](https://travis-ci.org/galli-leo/Radarr) | [](https://travis-ci.org/galli-leo/Radarr) |
|
||||
|
||||
This fork of Sonarr aims to turn it into something like Couchpotato.
|
||||
@@ -25,7 +25,11 @@ This fork of Sonarr aims to turn it into something like Couchpotato.
|
||||
|
||||
## Download
|
||||
The latest precompiled binary versions can be found here: https://github.com/galli-leo/Radarr/releases.
|
||||
A docker container can be found here: https://hub.docker.com/r/lsiodev/radarr/.
|
||||
|
||||
Docker containers from [linuxserver.io](https://linuxserver.io) can be found here.
|
||||
* [Radarr (x64)](https://hub.docker.com/r/linuxserver/radarr/)
|
||||
* [Radarr (armhf)](https://hub.docker.com/r/lsioarmhf/radarr/)
|
||||
* [Radarr (aarch64)](https://hub.docker.com/r/lsioarmhf/radarr-aarch64/)
|
||||
|
||||
For more up to date versions (but also sometimes broken), daily builds can be found here:
|
||||
* [OSX](https://leonardogalli.ch/radarr/builds/latest.php?os=osx)
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace NzbDrone.Api.Calendar
|
||||
|
||||
Get["/NzbDrone.ics"] = options => GetCalendarFeed();
|
||||
Get["/Sonarr.ics"] = options => GetCalendarFeed();
|
||||
Get["/Radarr.ics"] = options => GetCalendarFeed();
|
||||
}
|
||||
|
||||
private Response GetCalendarFeed()
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace NzbDrone.Api.Indexers
|
||||
{
|
||||
Guid = releaseInfo.Guid,
|
||||
Quality = parsedMovieInfo.Quality,
|
||||
//QualityWeight
|
||||
QualityWeight = parsedMovieInfo.Quality.Quality.Id, //Id kinda hacky for wheight, but what you gonna do? TODO: Fix this shit!
|
||||
Age = releaseInfo.Age,
|
||||
AgeHours = releaseInfo.AgeHours,
|
||||
AgeMinutes = releaseInfo.AgeMinutes,
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace NzbDrone.Common
|
||||
Console.WriteLine(" Commands:");
|
||||
Console.WriteLine(" /{0} Install the application as a Windows Service ({1}).", StartupContext.INSTALL_SERVICE, ServiceProvider.NZBDRONE_SERVICE_NAME);
|
||||
Console.WriteLine(" /{0} Uninstall already installed Windows Service ({1}).", StartupContext.UNINSTALL_SERVICE, ServiceProvider.NZBDRONE_SERVICE_NAME);
|
||||
Console.WriteLine(" /{0} Don't open Sonarr in a browser", StartupContext.NO_BROWSER);
|
||||
Console.WriteLine(" /{0} Don't open Radarr in a browser", StartupContext.NO_BROWSER);
|
||||
Console.WriteLine(" <No Arguments> Run application in console mode.");
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
static UserAgentBuilder()
|
||||
{
|
||||
UserAgent = string.Format("Sonarr/{0} ({1} {2})",
|
||||
UserAgent = string.Format("Radarr/{0} ({1} {2})",
|
||||
BuildInfo.Version,
|
||||
OsInfo.Os, OsInfo.Version.ToString(2));
|
||||
|
||||
UserAgentSimplified = string.Format("Sonarr/{0}",
|
||||
UserAgentSimplified = string.Format("Radarr/{0}",
|
||||
BuildInfo.Version.ToString(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,9 +103,9 @@ namespace NzbDrone.Common.Instrumentation
|
||||
|
||||
private static void RegisterAppFile(IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
RegisterAppFile(appFolderInfo, "appFileInfo", "sonarr.txt", 5, LogLevel.Info);
|
||||
RegisterAppFile(appFolderInfo, "appFileDebug", "sonarr.debug.txt", 50, LogLevel.Off);
|
||||
RegisterAppFile(appFolderInfo, "appFileTrace", "sonarr.trace.txt", 50, LogLevel.Off);
|
||||
RegisterAppFile(appFolderInfo, "appFileInfo", "radarr.txt", 5, LogLevel.Info);
|
||||
RegisterAppFile(appFolderInfo, "appFileDebug", "radarr.debug.txt", 50, LogLevel.Off);
|
||||
RegisterAppFile(appFolderInfo, "appFileTrace", "radarr.trace.txt", 50, LogLevel.Off);
|
||||
}
|
||||
|
||||
private static LoggingRule RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Fanzub;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.FanzubTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FanzubFixture : CoreTest<Fanzub>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition()
|
||||
{
|
||||
Name = "Fanzub",
|
||||
Settings = new FanzubSettings()
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_fanzub()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Fanzub/fanzub.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(3);
|
||||
|
||||
var releaseInfo = releases.First();
|
||||
|
||||
releaseInfo.Title.Should().Be("[Vivid] Hanayamata - 10 [A33D6606]");
|
||||
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
|
||||
releaseInfo.DownloadUrl.Should().Be("http://fanzub.com/nzb/296464/Vivid%20Hanayamata%20-%2010.nzb");
|
||||
releaseInfo.InfoUrl.Should().BeNullOrEmpty();
|
||||
releaseInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
releaseInfo.PublishDate.Should().Be(DateTime.Parse("2014/09/13 12:56:53"));
|
||||
releaseInfo.Size.Should().Be(556246858);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,6 @@
|
||||
<Compile Include="IndexerTests\NewznabTests\NewznabFixture.cs" />
|
||||
<Compile Include="IndexerTests\NewznabTests\NewznabRequestGeneratorFixture.cs" />
|
||||
<Compile Include="IndexerTests\NewznabTests\NewznabSettingFixture.cs" />
|
||||
<Compile Include="IndexerTests\FanzubTests\FanzubFixture.cs" />
|
||||
<Compile Include="IndexerTests\OmgwtfnzbsTests\OmgwtfnzbsFixture.cs" />
|
||||
<Compile Include="IndexerTests\SeasonSearchFixture.cs" />
|
||||
<Compile Include="IndexerTests\TestIndexer.cs" />
|
||||
|
||||
14
src/NzbDrone.Core/Datastore/Migration/114_remove_fanzub.cs
Normal file
14
src/NzbDrone.Core/Datastore/Migration/114_remove_fanzub.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(114)]
|
||||
public class remove_fanzub : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "Fanzub" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(115)]
|
||||
public class update_movie_sorttitle : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
// Create.Column("SortTitle").OnTable("Series").AsString().Nullable();
|
||||
Execute.WithConnection(SetSortTitles);
|
||||
}
|
||||
|
||||
private void SetSortTitles(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using (IDbCommand getSeriesCmd = conn.CreateCommand())
|
||||
{
|
||||
getSeriesCmd.Transaction = tran;
|
||||
getSeriesCmd.CommandText = @"SELECT Id, Title FROM Movies";
|
||||
using (IDataReader seriesReader = getSeriesCmd.ExecuteReader())
|
||||
{
|
||||
while (seriesReader.Read())
|
||||
{
|
||||
var id = seriesReader.GetInt32(0);
|
||||
var title = seriesReader.GetString(1);
|
||||
|
||||
var sortTitle = Parser.Parser.NormalizeTitle(title).ToLower();
|
||||
|
||||
using (IDbCommand updateCmd = conn.CreateCommand())
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
updateCmd.CommandText = "UPDATE Movies SET SortTitle = ? WHERE Id = ?";
|
||||
updateCmd.AddParameter(sortTitle);
|
||||
updateCmd.AddParameter(id);
|
||||
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
CompareQuality,
|
||||
CompareProtocol,
|
||||
CompareEpisodeCount,
|
||||
CompareEpisodeNumber,
|
||||
ComparePeersIfTorrent,
|
||||
CompareAgeIfUsenet,
|
||||
CompareSize
|
||||
@@ -56,6 +54,12 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
private int CompareQuality(DownloadDecision x, DownloadDecision y)
|
||||
{
|
||||
if (x.IsForMovie && y.IsForMovie)
|
||||
{
|
||||
return CompareAll(CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.Movie.Profile.Value.Items.FindIndex(v => v.Quality == remoteEpisode.ParsedMovieInfo.Quality.Quality)),
|
||||
CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.ParsedMovieInfo.Quality.Revision.Real),
|
||||
CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.ParsedMovieInfo.Quality.Revision.Version));
|
||||
}
|
||||
return CompareAll(CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.Profile.Value.Items.FindIndex(v => v.Quality == remoteEpisode.ParsedEpisodeInfo.Quality.Quality)),
|
||||
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Real),
|
||||
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version));
|
||||
@@ -63,6 +67,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
private int CompareProtocol(DownloadDecision x, DownloadDecision y)
|
||||
{
|
||||
|
||||
var result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode =>
|
||||
{
|
||||
var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Series.Tags);
|
||||
@@ -70,13 +75,22 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
return downloadProtocol == delayProfile.PreferredProtocol;
|
||||
});
|
||||
|
||||
if (x.IsForMovie)
|
||||
{
|
||||
result = CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode =>
|
||||
{
|
||||
var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Movie.Tags);
|
||||
var downloadProtocol = remoteEpisode.Release.DownloadProtocol;
|
||||
return downloadProtocol == delayProfile.PreferredProtocol;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int CompareEpisodeCount(DownloadDecision x, DownloadDecision y)
|
||||
{
|
||||
return CompareAll(CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.FullSeason),
|
||||
CompareByReverse(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Episodes.Count));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int CompareEpisodeNumber(DownloadDecision x, DownloadDecision y)
|
||||
@@ -88,20 +102,20 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
// Different protocols should get caught when checking the preferred protocol,
|
||||
// since we're dealing with the same series in our comparisions
|
||||
if (x.RemoteEpisode.Release.DownloadProtocol != DownloadProtocol.Torrent ||
|
||||
y.RemoteEpisode.Release.DownloadProtocol != DownloadProtocol.Torrent)
|
||||
if (x.RemoteMovie.Release.DownloadProtocol != DownloadProtocol.Torrent ||
|
||||
y.RemoteMovie.Release.DownloadProtocol != DownloadProtocol.Torrent)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CompareAll(
|
||||
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode =>
|
||||
CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode =>
|
||||
{
|
||||
var seeders = TorrentInfo.GetSeeders(remoteEpisode.Release);
|
||||
|
||||
return seeders.HasValue && seeders.Value > 0 ? Math.Round(Math.Log10(seeders.Value)) : 0;
|
||||
}),
|
||||
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode =>
|
||||
CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode =>
|
||||
{
|
||||
var peers = TorrentInfo.GetPeers(remoteEpisode.Release);
|
||||
|
||||
@@ -117,7 +131,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode =>
|
||||
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode =>
|
||||
{
|
||||
var ageHours = remoteEpisode.Release.AgeHours;
|
||||
var age = remoteEpisode.Release.Age;
|
||||
@@ -145,7 +159,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
// TODO: Is smaller better? Smaller for usenet could mean no par2 files.
|
||||
|
||||
return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Release.Size.Round(200.Megabytes()));
|
||||
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.Release.Size.Round(200.Megabytes()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,6 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
|
||||
_logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title + ", with spec: " + spec.GetType().Name);
|
||||
return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));//TODO UPDATE SPECS!
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -26,10 +26,10 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
|
||||
private static readonly TorrentBlackholeSettingsValidator Validator = new TorrentBlackholeSettingsValidator();
|
||||
|
||||
[FieldDefinition(0, Label = "Torrent Folder", Type = FieldType.Path, HelpText = "Folder in which Sonarr will store the .torrent file")]
|
||||
[FieldDefinition(0, Label = "Torrent Folder", Type = FieldType.Path, HelpText = "Folder in which Radarr will store the .torrent file")]
|
||||
public string TorrentFolder { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path, HelpText = "Folder from which Sonarr should import completed downloads")]
|
||||
[FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path, HelpText = "Folder from which Radarr should import completed downloads")]
|
||||
public string WatchFolder { get; set; }
|
||||
|
||||
[DefaultValue(false)]
|
||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
|
||||
[DefaultValue(false)]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
[FieldDefinition(3, Label = "Read Only", Type = FieldType.Checkbox, HelpText = "Instead of moving files this will instruct Sonarr to Copy or Hardlink (depending on settings/system configuration)")]
|
||||
[FieldDefinition(3, Label = "Read Only", Type = FieldType.Checkbox, HelpText = "Instead of moving files this will instruct Radarr to Copy or Hardlink (depending on settings/system configuration)")]
|
||||
public bool ReadOnly { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -19,10 +19,10 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
{
|
||||
private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator();
|
||||
|
||||
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "Folder in which Sonarr will store the .nzb file")]
|
||||
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "Folder in which Radarr will store the .nzb file")]
|
||||
public string NzbFolder { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path, HelpText = "Folder from which Sonarr should import completed downloads")]
|
||||
[FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path, HelpText = "Folder from which Radarr should import completed downloads")]
|
||||
public string WatchFolder { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -306,7 +306,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
{
|
||||
return new NzbDroneValidationFailure("TvCategory", "Configuration of label failed")
|
||||
{
|
||||
DetailedDescription = "Sonarr as unable to add the label to Deluge."
|
||||
DetailedDescription = "Radarr as unable to add the label to Deluge."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
[FieldDefinition(2, Label = "API Key", Type = FieldType.Textbox)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string TvCategory { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
||||
|
||||
@@ -337,7 +337,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
return new NzbDroneValidationFailure(string.Empty, "NzbGet setting KeepHistory should be greater than 0")
|
||||
{
|
||||
InfoLink = string.Format("http://{0}:{1}/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "NzbGet setting KeepHistory is set to 0. Which prevents Sonarr from seeing completed downloads."
|
||||
DetailedDescription = "NzbGet setting KeepHistory is set to 0. Which prevents Radarr from seeing completed downloads."
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string TvCategory { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
||||
|
||||
@@ -372,7 +372,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
return new NzbDroneValidationFailure("Version", "Sabnzbd develop version, assuming version 1.1.0 or higher.")
|
||||
{
|
||||
IsWarning = true,
|
||||
DetailedDescription = "Sonarr may not be able to support new features added to SABnzbd when running develop versions."
|
||||
DetailedDescription = "Radarr may not be able to support new features added to SABnzbd when running develop versions."
|
||||
};
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
return new NzbDroneValidationFailure("", "Disable 'Check before download' option in Sabnbzd")
|
||||
{
|
||||
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/switches/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "Using Check before download affects Sonarr ability to track new downloads. Also Sabnzbd recommends 'Abort jobs that cannot be completed' instead since it's more effective."
|
||||
DetailedDescription = "Using Check before download affects Radarr ability to track new downloads. Also Sabnzbd recommends 'Abort jobs that cannot be completed' instead since it's more effective."
|
||||
};
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
return new NzbDroneValidationFailure("TvCategory", "Enable Job folders")
|
||||
{
|
||||
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/categories/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "Sonarr prefers each download to have a separate folder. With * appended to the Folder/Path Sabnzbd will not create these job folders. Go to Sabnzbd to fix it."
|
||||
DetailedDescription = "Radarr prefers each download to have a separate folder. With * appended to the Folder/Path Sabnzbd will not create these job folders. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -475,7 +475,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting")
|
||||
{
|
||||
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -489,7 +489,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting")
|
||||
{
|
||||
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -503,7 +503,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting")
|
||||
{
|
||||
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string TvCategory { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
||||
|
||||
@@ -165,6 +165,31 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings);
|
||||
if (remoteMovie.Release.Age < 14 && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
|
||||
remoteMovie.Release.Age > 14 && Settings.OlderTvPriority == (int)TransmissionPriority.First)
|
||||
{
|
||||
_proxy.MoveTorrentToTopInQueue(hash, Settings);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
_proxy.AddTorrentFromData(fileContent, GetDownloadDirectory(), Settings);
|
||||
|
||||
if (remoteMovie.Release.Age < 14 && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
|
||||
remoteMovie.Release.Age > 14 && Settings.OlderTvPriority == (int)TransmissionPriority.First)
|
||||
{
|
||||
_proxy.MoveTorrentToTopInQueue(hash, Settings);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
failures.AddIfNotNull(TestConnection());
|
||||
@@ -207,7 +232,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
_logger.Error(ex, ex.Message);
|
||||
return new NzbDroneValidationFailure("Username", "Authentication failure")
|
||||
{
|
||||
DetailedDescription = string.Format("Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {0} by WhiteList limitations in the {0} configuration.", Name)
|
||||
DetailedDescription = string.Format("Please verify your username and password. Also verify if the host running Radarr isn't blocked from accessing {0} by WhiteList limitations in the {0} configuration.", Name)
|
||||
};
|
||||
}
|
||||
catch (WebException ex)
|
||||
|
||||
@@ -50,16 +50,16 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
|
||||
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
|
||||
public string TvCategory { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
|
||||
public string TvDirectory { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
||||
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing movies that we're released within the last 14 days")]
|
||||
public int RecentTvPriority { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
||||
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing movies that we're released over 14 days ago")]
|
||||
public int OlderTvPriority { get; set; }
|
||||
|
||||
[FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
[FieldDefinition(5, Label = "Password", Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional.")]
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional.")]
|
||||
public string TvCategory { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")]
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string TvCategory { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
||||
|
||||
@@ -109,7 +109,11 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
movie = _movieService.GetMovie(historyItem.MovieId);
|
||||
if (historyItem != null)
|
||||
{
|
||||
movie = _movieService.GetMovie(historyItem.MovieId);
|
||||
}
|
||||
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (grabbedItems.Empty())
|
||||
{
|
||||
trackedDownload.Warn("Download wasn't grabbed by sonarr, skipping");
|
||||
trackedDownload.Warn("Download wasn't grabbed by Radarr, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Fanzub
|
||||
{
|
||||
public class Fanzub : HttpIndexerBase<FanzubSettings>
|
||||
{
|
||||
public override string Name => "Fanzub";
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
|
||||
public Fanzub(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
|
||||
: base(httpClient, indexerStatusService, configService, parsingService, logger)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new FanzubRequestGenerator() { Settings = Settings };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new RssParser() { UseEnclosureUrl = true, UseEnclosureLength = true };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Fanzub
|
||||
{
|
||||
public class FanzubRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private static readonly Regex RemoveCharactersRegex = new Regex(@"[!?`]", RegexOptions.Compiled);
|
||||
|
||||
public FanzubSettings Settings { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
|
||||
public FanzubRequestGenerator()
|
||||
{
|
||||
PageSize = 100;
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetRecentRequests()
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(null));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var searchTitles = searchCriteria.QueryTitles.SelectMany(v => GetTitleSearchStrings(v, searchCriteria.AbsoluteEpisodeNumber)).ToList();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Join("|", searchTitles)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string query)
|
||||
{
|
||||
var url = new StringBuilder();
|
||||
url.AppendFormat("{0}?cat=anime&max={1}", Settings.BaseUrl, PageSize);
|
||||
|
||||
if (query.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
url.AppendFormat("&q={0}", query);
|
||||
}
|
||||
|
||||
yield return new IndexerRequest(url.ToString(), HttpAccept.Rss);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetTitleSearchStrings(string title, int absoluteEpisodeNumber)
|
||||
{
|
||||
var formats = new[] { "{0}%20{1:00}", "{0}%20-%20{1:00}" };
|
||||
|
||||
return formats.Select(s => "\"" + string.Format(s, CleanTitle(title), absoluteEpisodeNumber) + "\"");
|
||||
}
|
||||
|
||||
private string CleanTitle(string title)
|
||||
{
|
||||
return RemoveCharactersRegex.Replace(title, "");
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Fanzub
|
||||
{
|
||||
public class FanzubSettingsValidator : AbstractValidator<FanzubSettings>
|
||||
{
|
||||
public FanzubSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.BaseUrl).ValidRootUrl();
|
||||
}
|
||||
}
|
||||
|
||||
public class FanzubSettings : IProviderConfig
|
||||
{
|
||||
private static readonly FanzubSettingsValidator Validator = new FanzubSettingsValidator();
|
||||
|
||||
public FanzubSettings()
|
||||
{
|
||||
BaseUrl = "http://fanzub.com/rss/";
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Rss URL", HelpText = "Enter to URL to an Fanzub compatible RSS feed")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,8 +106,8 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
|
||||
if (capabilities.SupportedTvSearchParameters != null &&
|
||||
new[] { "q", "imdb" }.Any(v => capabilities.SupportedMovieSearchParamters.Contains(v)) &&
|
||||
new[] { "imdbtitle", "imdbyear" }.All(v => capabilities.SupportedMovieSearchParamters.Contains(v)))
|
||||
new[] { "q", "imdbid" }.Any(v => capabilities.SupportedMovieSearchParameters.Contains(v)) &&
|
||||
new[] { "imdbtitle", "imdbyear" }.All(v => capabilities.SupportedMovieSearchParameters.Contains(v)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
public int MaxPageSize { get; set; }
|
||||
public string[] SupportedSearchParameters { get; set; }
|
||||
public string[] SupportedTvSearchParameters { get; set; }
|
||||
public string[] SupportedMovieSearchParamters { get; set; }
|
||||
public string[] SupportedMovieSearchParameters { get; set; }
|
||||
public bool SupportsAggregateIdSearch { get; set; }
|
||||
public List<NewznabCategory> Categories { get; set; }
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
DefaultPageSize = 100;
|
||||
MaxPageSize = 100;
|
||||
SupportedSearchParameters = new[] { "q" };
|
||||
SupportedMovieSearchParamters = new[] { "q", "imdb", "imdbtitle", "imdbyear" };
|
||||
SupportedMovieSearchParameters = new[] { "q", "imdbid", "imdbtitle", "imdbyear" };
|
||||
SupportedTvSearchParameters = new[] { "q", "rid", "season", "ep" }; // This should remain 'rid' for older newznab installs.
|
||||
SupportsAggregateIdSearch = false;
|
||||
Categories = new List<NewznabCategory>();
|
||||
|
||||
@@ -102,11 +102,11 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
var xmlMovieSearch = xmlSearching.Element("movie-search");
|
||||
if (xmlMovieSearch == null || xmlMovieSearch.Attribute("available").Value != "yes")
|
||||
{
|
||||
capabilities.SupportedMovieSearchParamters = null;
|
||||
capabilities.SupportedMovieSearchParameters = null;
|
||||
}
|
||||
else if (xmlMovieSearch.Attribute("supportedParams") != null)
|
||||
{
|
||||
capabilities.SupportedMovieSearchParamters = xmlMovieSearch.Attribute("supportedParams").Value.Split(',');
|
||||
capabilities.SupportedMovieSearchParameters = xmlMovieSearch.Attribute("supportedParams").Value.Split(',');
|
||||
capabilities.SupportsAggregateIdSearch = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
|
||||
|
||||
return capabilities.SupportedMovieSearchParamters != null &&
|
||||
capabilities.SupportedMovieSearchParamters.Contains("imdb");
|
||||
return capabilities.SupportedMovieSearchParameters != null &&
|
||||
capabilities.SupportedMovieSearchParameters.Contains("imdbid");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
|
||||
|
||||
if (capabilities.SupportedMovieSearchParamters != null)
|
||||
if (capabilities.SupportedMovieSearchParameters != null)
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories.Concat(Settings.AnimeCategories), "movie", ""));
|
||||
}
|
||||
@@ -124,7 +124,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
if(SupportsMovieSearch)
|
||||
if (SupportsMovieSearch)
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie",
|
||||
string.Format("&imdbid={0}", searchCriteria.Movie.ImdbId.Substring(2)))); //strip off the "tt" - VERY HACKY
|
||||
|
||||
@@ -48,9 +48,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
|
||||
{
|
||||
releaseInfo = base.ProcessItem(item, releaseInfo);
|
||||
|
||||
releaseInfo.TvdbId = GetTvdbId(item);
|
||||
releaseInfo.TvRageId = GetTvRageId(item);
|
||||
releaseInfo.ImdbId = GetImdbId(item);
|
||||
|
||||
return releaseInfo;
|
||||
}
|
||||
@@ -114,27 +112,14 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
return url;
|
||||
}
|
||||
|
||||
protected virtual int GetTvdbId(XElement item)
|
||||
protected virtual int GetImdbId(XElement item)
|
||||
{
|
||||
var tvdbIdString = TryGetNewznabAttribute(item, "tvdbid");
|
||||
int tvdbId;
|
||||
var imdbIdString = TryGetNewznabAttribute(item, "imdb");
|
||||
int imdbId;
|
||||
|
||||
if (!tvdbIdString.IsNullOrWhiteSpace() && int.TryParse(tvdbIdString, out tvdbId))
|
||||
if (!imdbIdString.IsNullOrWhiteSpace() && int.TryParse(imdbIdString, out imdbId))
|
||||
{
|
||||
return tvdbId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected virtual int GetTvRageId(XElement item)
|
||||
{
|
||||
var tvRageIdString = TryGetNewznabAttribute(item, "rageid");
|
||||
int tvRageId;
|
||||
|
||||
if (!tvRageIdString.IsNullOrWhiteSpace() && int.TryParse(tvRageIdString, out tvRageId))
|
||||
{
|
||||
return tvRageId;
|
||||
return imdbId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
public NewznabSettings()
|
||||
{
|
||||
Categories = new[] { 2030, 2040, 2050 };
|
||||
Categories = new[] { 2030, 2035, 2040, 2045, 2050 };
|
||||
AnimeCategories = Enumerable.Empty<int>();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Commands
|
||||
{
|
||||
public class DownloadedMovieScanCommand : Command
|
||||
{
|
||||
public override bool SendUpdatesToClient => SendUpdates;
|
||||
|
||||
public bool SendUpdates { get; set; }
|
||||
|
||||
// Properties used by third-party apps, do not modify.
|
||||
public string Path { get; set; }
|
||||
public string DownloadClientId { get; set; }
|
||||
public ImportMode ImportMode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,265 +1,107 @@
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public interface IDownloadedMovieImportService
|
||||
{
|
||||
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
|
||||
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null);
|
||||
bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie);
|
||||
}
|
||||
|
||||
public class DownloadedMovieImportService : IDownloadedMovieImportService
|
||||
public class DownloadedMovieCommandService : IExecute<DownloadedMovieScanCommand>
|
||||
{
|
||||
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
|
||||
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly IImportApprovedMovie _importApprovedMovie;
|
||||
private readonly IDetectSample _detectSample;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadedMovieImportService(IDiskProvider diskProvider,
|
||||
IDiskScanService diskScanService,
|
||||
IMovieService movieService,
|
||||
IParsingService parsingService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
IImportApprovedMovie importApprovedMovie,
|
||||
IDetectSample detectSample,
|
||||
Logger logger)
|
||||
public DownloadedMovieCommandService(IDownloadedMovieImportService downloadedMovieImportService,
|
||||
ITrackedDownloadService trackedDownloadService,
|
||||
IDiskProvider diskProvider,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
{
|
||||
_downloadedMovieImportService = downloadedMovieImportService;
|
||||
_trackedDownloadService = trackedDownloadService;
|
||||
_diskProvider = diskProvider;
|
||||
_diskScanService = diskScanService;
|
||||
_movieService = movieService;
|
||||
_parsingService = parsingService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_importApprovedMovie = importApprovedMovie;
|
||||
_detectSample = detectSample;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo)
|
||||
private List<ImportResult> ProcessDroneFactoryFolder()
|
||||
{
|
||||
var results = new List<ImportResult>();
|
||||
var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
|
||||
|
||||
foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName))
|
||||
if (string.IsNullOrEmpty(downloadedEpisodesFolder))
|
||||
{
|
||||
var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null);
|
||||
results.AddRange(folderResults);
|
||||
}
|
||||
|
||||
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false))
|
||||
{
|
||||
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
|
||||
results.AddRange(fileResults);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null)
|
||||
{
|
||||
if (_diskProvider.FolderExists(path))
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(path);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
return ProcessFolder(directoryInfo, importMode, downloadClientItem);
|
||||
}
|
||||
|
||||
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
|
||||
}
|
||||
|
||||
if (_diskProvider.FileExists(path))
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
return ProcessFile(fileInfo, importMode, downloadClientItem);
|
||||
}
|
||||
|
||||
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
|
||||
}
|
||||
|
||||
_logger.Error("Import failed, path does not exist or is not accessible by Sonarr: {0}", path);
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie)
|
||||
{
|
||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
|
||||
|
||||
foreach (var videoFile in videoFiles)
|
||||
{
|
||||
var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile));
|
||||
|
||||
if (episodeParseResult == null)
|
||||
{
|
||||
_logger.Warn("Unable to parse file on import: [{0}]", videoFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
var size = _diskProvider.GetFileSize(videoFile);
|
||||
var quality = QualityParser.ParseQuality(videoFile);
|
||||
|
||||
if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode))
|
||||
{
|
||||
_logger.Warn("Non-sample file detected: [{0}]", videoFile);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes()))
|
||||
{
|
||||
_logger.Warn("RAR file detected, will require manual cleanup");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
var movie = _parsingService.GetMovie(cleanedUpName);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
_logger.Debug("Unknown Movie {0}", cleanedUpName);
|
||||
|
||||
return new List<ImportResult>
|
||||
{
|
||||
UnknownMovieResult("Unknown Movie")
|
||||
};
|
||||
}
|
||||
|
||||
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
if (_movieService.MoviePathExists(directoryInfo.FullName))
|
||||
{
|
||||
_logger.Warn("Unable to process folder that is mapped to an existing show");
|
||||
_logger.Trace("Drone Factory folder is not configured");
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name);
|
||||
|
||||
if (folderInfo != null)
|
||||
if (!_diskProvider.FolderExists(downloadedEpisodesFolder))
|
||||
{
|
||||
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
|
||||
_logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder);
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
return _downloadedMovieImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder));
|
||||
}
|
||||
|
||||
if (downloadClientItem == null)
|
||||
private List<ImportResult> ProcessPath(DownloadedMovieScanCommand message)
|
||||
{
|
||||
if (!_diskProvider.FolderExists(message.Path) && !_diskProvider.FileExists(message.Path))
|
||||
{
|
||||
foreach (var videoFile in videoFiles)
|
||||
_logger.Warn("Folder/File specified for import scan [{0}] doesn't exist.", message.Path);
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
if (message.DownloadClientId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(message.DownloadClientId);
|
||||
|
||||
if (trackedDownload != null)
|
||||
{
|
||||
if (_diskProvider.IsFileLocked(videoFile))
|
||||
{
|
||||
return new List<ImportResult>
|
||||
{
|
||||
FileIsLockedResult(videoFile)
|
||||
};
|
||||
}
|
||||
_logger.Debug("External directory scan request for known download {0}. [{1}]", message.DownloadClientId, message.Path);
|
||||
|
||||
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode, trackedDownload.RemoteMovie.Movie, trackedDownload.DownloadItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path);
|
||||
|
||||
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
|
||||
}
|
||||
}
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true);
|
||||
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
|
||||
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
|
||||
}
|
||||
|
||||
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
|
||||
importResults.Any(i => i.Result == ImportResultType.Imported) &&
|
||||
ShouldDeleteFolder(directoryInfo, movie))
|
||||
public void Execute(DownloadedMovieScanCommand message)
|
||||
{
|
||||
List<ImportResult> importResults;
|
||||
|
||||
if (message.Path.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Debug("Deleting folder after importing valid files");
|
||||
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
|
||||
importResults = ProcessPath(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
importResults = ProcessDroneFactoryFolder();
|
||||
}
|
||||
|
||||
return importResults;
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name));
|
||||
|
||||
if (movie == null)
|
||||
if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported))
|
||||
{
|
||||
_logger.Debug("Unknown Movie for file: {0}", fileInfo.Name);
|
||||
|
||||
return new List<ImportResult>
|
||||
{
|
||||
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
|
||||
};
|
||||
// Atm we don't report it as a command failure, coz that would cause the download to be failed.
|
||||
// Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later.
|
||||
//message.SetMessage("Failed to import");
|
||||
}
|
||||
|
||||
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
|
||||
{
|
||||
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
|
||||
|
||||
return new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
|
||||
};
|
||||
}
|
||||
|
||||
if (downloadClientItem == null)
|
||||
{
|
||||
if (_diskProvider.IsFileLocked(fileInfo.FullName))
|
||||
{
|
||||
return new List<ImportResult>
|
||||
{
|
||||
FileIsLockedResult(fileInfo.FullName)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, null, true);
|
||||
|
||||
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
|
||||
}
|
||||
|
||||
private string GetCleanedUpFolderName(string folder)
|
||||
{
|
||||
folder = folder.Replace("_UNPACK_", "")
|
||||
.Replace("_FAILED_", "");
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
private ImportResult FileIsLockedResult(string videoFile)
|
||||
{
|
||||
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
|
||||
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
|
||||
}
|
||||
|
||||
private ImportResult UnknownMovieResult(string message, string videoFile = null)
|
||||
{
|
||||
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
|
||||
|
||||
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
266
src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs
Normal file
266
src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public interface IDownloadedMovieImportService
|
||||
{
|
||||
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
|
||||
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null);
|
||||
bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie);
|
||||
}
|
||||
|
||||
public class DownloadedMovieImportService : IDownloadedMovieImportService
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly IImportApprovedMovie _importApprovedMovie;
|
||||
private readonly IDetectSample _detectSample;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadedMovieImportService(IDiskProvider diskProvider,
|
||||
IDiskScanService diskScanService,
|
||||
IMovieService movieService,
|
||||
IParsingService parsingService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
IImportApprovedMovie importApprovedMovie,
|
||||
IDetectSample detectSample,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_diskScanService = diskScanService;
|
||||
_movieService = movieService;
|
||||
_parsingService = parsingService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_importApprovedMovie = importApprovedMovie;
|
||||
_detectSample = detectSample;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo)
|
||||
{
|
||||
var results = new List<ImportResult>();
|
||||
|
||||
foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName))
|
||||
{
|
||||
var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null);
|
||||
results.AddRange(folderResults);
|
||||
}
|
||||
|
||||
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false))
|
||||
{
|
||||
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
|
||||
results.AddRange(fileResults);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null)
|
||||
{
|
||||
if (_diskProvider.FolderExists(path))
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(path);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
return ProcessFolder(directoryInfo, importMode, downloadClientItem);
|
||||
}
|
||||
|
||||
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
|
||||
}
|
||||
|
||||
if (_diskProvider.FileExists(path))
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
return ProcessFile(fileInfo, importMode, downloadClientItem);
|
||||
}
|
||||
|
||||
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
|
||||
}
|
||||
|
||||
_logger.Error("Import failed, path does not exist or is not accessible by Radarr: {0}", path);
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie)
|
||||
{
|
||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
|
||||
|
||||
foreach (var videoFile in videoFiles)
|
||||
{
|
||||
var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile));
|
||||
|
||||
if (episodeParseResult == null)
|
||||
{
|
||||
_logger.Warn("Unable to parse file on import: [{0}]", videoFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
var size = _diskProvider.GetFileSize(videoFile);
|
||||
var quality = QualityParser.ParseQuality(videoFile);
|
||||
|
||||
if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode))
|
||||
{
|
||||
_logger.Warn("Non-sample file detected: [{0}]", videoFile);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes()))
|
||||
{
|
||||
_logger.Warn("RAR file detected, will require manual cleanup");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
var movie = _parsingService.GetMovie(cleanedUpName);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
_logger.Debug("Unknown Movie {0}", cleanedUpName);
|
||||
|
||||
return new List<ImportResult>
|
||||
{
|
||||
UnknownMovieResult("Unknown Movie")
|
||||
};
|
||||
}
|
||||
|
||||
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
if (_movieService.MoviePathExists(directoryInfo.FullName))
|
||||
{
|
||||
_logger.Warn("Unable to process folder that is mapped to an existing show");
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name);
|
||||
|
||||
if (folderInfo != null)
|
||||
{
|
||||
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
|
||||
}
|
||||
|
||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
|
||||
if (downloadClientItem == null)
|
||||
{
|
||||
foreach (var videoFile in videoFiles)
|
||||
{
|
||||
if (_diskProvider.IsFileLocked(videoFile))
|
||||
{
|
||||
return new List<ImportResult>
|
||||
{
|
||||
FileIsLockedResult(videoFile)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true);
|
||||
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
|
||||
|
||||
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
|
||||
importResults.Any(i => i.Result == ImportResultType.Imported) &&
|
||||
ShouldDeleteFolder(directoryInfo, movie))
|
||||
{
|
||||
_logger.Debug("Deleting folder after importing valid files");
|
||||
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
|
||||
}
|
||||
|
||||
return importResults;
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name));
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
_logger.Debug("Unknown Movie for file: {0}", fileInfo.Name);
|
||||
|
||||
return new List<ImportResult>
|
||||
{
|
||||
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
|
||||
};
|
||||
}
|
||||
|
||||
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
|
||||
{
|
||||
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
|
||||
|
||||
return new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
|
||||
};
|
||||
}
|
||||
|
||||
if (downloadClientItem == null)
|
||||
{
|
||||
if (_diskProvider.IsFileLocked(fileInfo.FullName))
|
||||
{
|
||||
return new List<ImportResult>
|
||||
{
|
||||
FileIsLockedResult(fileInfo.FullName)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, null, true);
|
||||
|
||||
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
|
||||
}
|
||||
|
||||
private string GetCleanedUpFolderName(string folder)
|
||||
{
|
||||
folder = folder.Replace("_UNPACK_", "")
|
||||
.Replace("_FAILED_", "");
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
private ImportResult FileIsLockedResult(string videoFile)
|
||||
{
|
||||
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
|
||||
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
|
||||
}
|
||||
|
||||
private ImportResult UnknownMovieResult(string message, string videoFile = null)
|
||||
{
|
||||
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
|
||||
|
||||
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,5 +10,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
public List<int> EpisodeIds { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public int MovieId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,5 +17,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
public QualityModel Quality { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
public Movie Movie { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
||||
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||
private readonly IImportApprovedMovie _importApprovedMovie;
|
||||
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
||||
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -43,11 +46,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
IDiskScanService diskScanService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
ISeriesService seriesService,
|
||||
IMovieService movieService,
|
||||
IEpisodeService episodeService,
|
||||
IVideoFileInfoReader videoFileInfoReader,
|
||||
IImportApprovedEpisodes importApprovedEpisodes,
|
||||
IImportApprovedMovie importApprovedMovie,
|
||||
ITrackedDownloadService trackedDownloadService,
|
||||
IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
||||
IDownloadedMovieImportService downloadedMovieImportService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
@@ -56,11 +62,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
_diskScanService = diskScanService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_seriesService = seriesService;
|
||||
_movieService = movieService;
|
||||
_episodeService = episodeService;
|
||||
_videoFileInfoReader = videoFileInfoReader;
|
||||
_importApprovedEpisodes = importApprovedEpisodes;
|
||||
_importApprovedMovie = importApprovedMovie;
|
||||
_trackedDownloadService = trackedDownloadService;
|
||||
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
||||
_downloadedMovieImportService = downloadedMovieImportService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -126,62 +135,128 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
|
||||
var relativeFile = folder.GetRelativePath(file);
|
||||
|
||||
var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
|
||||
var movie = _parsingService.GetMovie(relativeFile.Split('\\', '/')[0]);
|
||||
|
||||
if (series == null)
|
||||
if (movie == null)
|
||||
{
|
||||
series = _parsingService.GetSeries(relativeFile);
|
||||
movie = _parsingService.GetMovie(relativeFile);
|
||||
}
|
||||
|
||||
if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
if (movie == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
series = trackedDownload.RemoteEpisode.Series;
|
||||
movie = trackedDownload.RemoteMovie.Movie;
|
||||
}
|
||||
|
||||
if (series == null)
|
||||
if (movie == null)
|
||||
{
|
||||
var localEpisode = new LocalEpisode();
|
||||
localEpisode.Path = file;
|
||||
localEpisode.Quality = QualityParser.ParseQuality(file);
|
||||
localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
var localMovie = new LocalMovie()
|
||||
{
|
||||
Path = file,
|
||||
Quality = QualityParser.ParseQuality(file),
|
||||
Size = _diskProvider.GetFileSize(file)
|
||||
};
|
||||
|
||||
return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
|
||||
return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), folder, downloadId);
|
||||
}
|
||||
|
||||
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
|
||||
series, null, SceneSource(series, folder));
|
||||
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file },
|
||||
movie, null, SceneSource(movie, folder));
|
||||
|
||||
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
|
||||
}
|
||||
|
||||
//private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
|
||||
//{
|
||||
// if (folder.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// folder = new FileInfo(file).Directory.FullName;
|
||||
// }
|
||||
|
||||
// var relativeFile = folder.GetRelativePath(file);
|
||||
|
||||
// var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
|
||||
|
||||
// if (series == null)
|
||||
// {
|
||||
// series = _parsingService.GetSeries(relativeFile);
|
||||
// }
|
||||
|
||||
// if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
// {
|
||||
// var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
// series = trackedDownload.RemoteEpisode.Series;
|
||||
// }
|
||||
|
||||
// if (series == null)
|
||||
// {
|
||||
// var localEpisode = new LocalEpisode();
|
||||
// localEpisode.Path = file;
|
||||
// localEpisode.Quality = QualityParser.ParseQuality(file);
|
||||
// localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
|
||||
// return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
|
||||
// }
|
||||
|
||||
// var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
|
||||
// series, null, SceneSource(series, folder));
|
||||
|
||||
// return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
|
||||
//}
|
||||
|
||||
private bool SceneSource(Series series, string folder)
|
||||
{
|
||||
return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder));
|
||||
}
|
||||
|
||||
private bool SceneSource(Movie movie, string folder)
|
||||
{
|
||||
return !(movie.Path.PathEquals(folder) || movie.Path.IsParentPath(folder));
|
||||
}
|
||||
|
||||
//private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
|
||||
//{
|
||||
// var item = new ManualImportItem();
|
||||
|
||||
// item.Path = decision.LocalEpisode.Path;
|
||||
// item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
|
||||
// item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
|
||||
// item.DownloadId = downloadId;
|
||||
|
||||
// if (decision.LocalEpisode.Series != null)
|
||||
// {
|
||||
// item.Series = decision.LocalEpisode.Series;
|
||||
// }
|
||||
|
||||
// if (decision.LocalEpisode.Episodes.Any())
|
||||
// {
|
||||
// item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
|
||||
// item.Episodes = decision.LocalEpisode.Episodes;
|
||||
// }
|
||||
|
||||
// item.Quality = decision.LocalEpisode.Quality;
|
||||
// item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
|
||||
// item.Rejections = decision.Rejections;
|
||||
|
||||
// return item;
|
||||
//}
|
||||
|
||||
private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
|
||||
{
|
||||
var item = new ManualImportItem();
|
||||
|
||||
item.Path = decision.LocalEpisode.Path;
|
||||
item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
|
||||
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
|
||||
item.Path = decision.LocalMovie.Path;
|
||||
item.RelativePath = folder.GetRelativePath(decision.LocalMovie.Path);
|
||||
item.Name = Path.GetFileNameWithoutExtension(decision.LocalMovie.Path);
|
||||
item.DownloadId = downloadId;
|
||||
|
||||
if (decision.LocalEpisode.Series != null)
|
||||
if (decision.LocalMovie.Movie != null)
|
||||
{
|
||||
item.Series = decision.LocalEpisode.Series;
|
||||
item.Movie = decision.LocalMovie.Movie;
|
||||
}
|
||||
|
||||
if (decision.LocalEpisode.Episodes.Any())
|
||||
{
|
||||
item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
|
||||
item.Episodes = decision.LocalEpisode.Episodes;
|
||||
}
|
||||
|
||||
item.Quality = decision.LocalEpisode.Quality;
|
||||
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
|
||||
item.Quality = decision.LocalMovie.Quality;
|
||||
item.Size = _diskProvider.GetFileSize(decision.LocalMovie.Path);
|
||||
item.Rejections = decision.Rejections;
|
||||
|
||||
return item;
|
||||
@@ -199,45 +274,43 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
|
||||
|
||||
var file = message.Files[i];
|
||||
var series = _seriesService.GetSeries(file.SeriesId);
|
||||
var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
|
||||
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
|
||||
var movie = _movieService.GetMovie(file.MovieId);
|
||||
var parsedMovieInfo = Parser.Parser.ParseMoviePath(file.Path) ?? new ParsedMovieInfo();
|
||||
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
|
||||
var existingFile = series.Path.IsParentPath(file.Path);
|
||||
var existingFile = movie.Path.IsParentPath(file.Path);
|
||||
|
||||
var localEpisode = new LocalEpisode
|
||||
var localMovie = new LocalMovie
|
||||
{
|
||||
ExistingFile = false,
|
||||
Episodes = episodes,
|
||||
MediaInfo = mediaInfo,
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
ParsedMovieInfo = parsedMovieInfo,
|
||||
Path = file.Path,
|
||||
Quality = file.Quality,
|
||||
Series = series,
|
||||
Movie = movie,
|
||||
Size = 0
|
||||
};
|
||||
|
||||
//TODO: Cleanup non-tracked downloads
|
||||
|
||||
var importDecision = new ImportDecision(localEpisode);
|
||||
var importDecision = new ImportDecision(localMovie);
|
||||
|
||||
if (file.DownloadId.IsNullOrWhiteSpace())
|
||||
{
|
||||
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
||||
imported.AddRange(_importApprovedMovie.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
|
||||
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
|
||||
var importResult = _importApprovedMovie.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
|
||||
|
||||
imported.Add(importResult);
|
||||
|
||||
importedTrackedDownload.Add(new ManuallyImportedFile
|
||||
{
|
||||
TrackedDownload = trackedDownload,
|
||||
ImportResult = importResult
|
||||
});
|
||||
{
|
||||
TrackedDownload = trackedDownload,
|
||||
ImportResult = importResult
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,20 +322,98 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
|
||||
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
|
||||
{
|
||||
if (_downloadedEpisodesImportService.ShouldDeleteFolder(
|
||||
if (_downloadedMovieImportService.ShouldDeleteFolder(
|
||||
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
|
||||
trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
trackedDownload.RemoteMovie.Movie) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
{
|
||||
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
|
||||
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, 1)) //TODO: trackedDownload.RemoteMovie.Movie.Count is always 1?
|
||||
{
|
||||
trackedDownload.State = TrackedDownloadStage.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//public void Execute(ManualImportCommand message)
|
||||
//{
|
||||
// _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode);
|
||||
|
||||
// var imported = new List<ImportResult>();
|
||||
// var importedTrackedDownload = new List<ManuallyImportedFile>();
|
||||
|
||||
// for (int i = 0; i < message.Files.Count; i++)
|
||||
// {
|
||||
// _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
|
||||
|
||||
// var file = message.Files[i];
|
||||
// var series = _seriesService.GetSeries(file.SeriesId);
|
||||
// var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
|
||||
// var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
|
||||
// var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
|
||||
// var existingFile = series.Path.IsParentPath(file.Path);
|
||||
|
||||
// var localEpisode = new LocalEpisode
|
||||
// {
|
||||
// ExistingFile = false,
|
||||
// Episodes = episodes,
|
||||
// MediaInfo = mediaInfo,
|
||||
// ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
// Path = file.Path,
|
||||
// Quality = file.Quality,
|
||||
// Series = series,
|
||||
// Size = 0
|
||||
// };
|
||||
|
||||
// //TODO: Cleanup non-tracked downloads
|
||||
|
||||
// var importDecision = new ImportDecision(localEpisode);
|
||||
|
||||
// if (file.DownloadId.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
||||
// }
|
||||
|
||||
// else
|
||||
// {
|
||||
// var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
|
||||
// var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
|
||||
|
||||
// imported.Add(importResult);
|
||||
|
||||
// importedTrackedDownload.Add(new ManuallyImportedFile
|
||||
// {
|
||||
// TrackedDownload = trackedDownload,
|
||||
// ImportResult = importResult
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// _logger.ProgressTrace("Manually imported {0} files", imported.Count);
|
||||
|
||||
// foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
|
||||
// {
|
||||
// var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
|
||||
|
||||
// if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
|
||||
// {
|
||||
// if (_downloadedEpisodesImportService.ShouldDeleteFolder(
|
||||
// new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
|
||||
// trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
// {
|
||||
// _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
|
||||
// {
|
||||
// trackedDownload.State = TrackedDownloadStage.Imported;
|
||||
// _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||
public Production_Companies[] production_companies { get; set; }
|
||||
public Production_Countries[] production_countries { get; set; }
|
||||
public string release_date { get; set; }
|
||||
public int revenue { get; set; }
|
||||
public long revenue { get; set; }
|
||||
public int runtime { get; set; }
|
||||
public Spoken_Languages[] spoken_languages { get; set; }
|
||||
public string status { get; set; }
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
{
|
||||
var lowerTitle = title.ToLower();
|
||||
|
||||
var parserResult = Parser.Parser.ParseMovieTitle(title);
|
||||
var parserResult = Parser.Parser.ParseMovieTitle(title, true);
|
||||
|
||||
var yearTerm = "";
|
||||
|
||||
@@ -327,7 +327,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
imdbMovie.TmdbId = result.id;
|
||||
try
|
||||
{
|
||||
imdbMovie.SortTitle = result.title;
|
||||
imdbMovie.SortTitle = Parser.Parser.NormalizeTitle(result.title);
|
||||
imdbMovie.Title = result.title;
|
||||
string titleSlug = result.title;
|
||||
imdbMovie.TitleSlug = titleSlug.ToLower().Replace(" ", "-");
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
const string subject = "Sonarr [TV] - Grabbed";
|
||||
const string subject = "Radarr [TV] - Grabbed";
|
||||
var body = string.Format("{0} sent to queue.", grabMessage.Message);
|
||||
|
||||
_emailService.SendEmail(Settings, subject, body);
|
||||
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
const string subject = "Sonarr [TV] - Downloaded";
|
||||
const string subject = "Radarr [TV] - Downloaded";
|
||||
var body = string.Format("{0} Downloaded and sorted.", message.Message);
|
||||
|
||||
_emailService.SendEmail(Settings, subject, body);
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
|
||||
try
|
||||
{
|
||||
SendEmail(settings, "Sonarr - Test Notification", body);
|
||||
SendEmail(settings, "Radarr - Test Notification", body);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -18,14 +18,14 @@ namespace NzbDrone.Core.Notifications.Join
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
const string title = "Sonarr - Episode Grabbed";
|
||||
const string title = "Radarr - Episode Grabbed";
|
||||
|
||||
_proxy.SendNotification(title, grabMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
const string title = "Sonarr - Episode Downloaded";
|
||||
const string title = "Radarr - Episode Downloaded";
|
||||
|
||||
_proxy.SendNotification(title, message.Message, Settings);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Notifications.MediaBrowser
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
const string title = "Sonarr - Grabbed";
|
||||
const string title = "Radarr - Grabbed";
|
||||
|
||||
if (Settings.Notify)
|
||||
{
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Notifications.MediaBrowser
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
const string title = "Sonarr - Downloaded";
|
||||
const string title = "Radarr - Downloaded";
|
||||
|
||||
if (Settings.Notify)
|
||||
{
|
||||
|
||||
@@ -18,13 +18,13 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
const string header = "Sonarr [TV] - Grabbed";
|
||||
const string header = "Radarr [TV] - Grabbed";
|
||||
_plexClientService.Notify(Settings, header, grabMessage.Message);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
const string header = "Sonarr [TV] - Downloaded";
|
||||
const string header = "Radarr [TV] - Downloaded";
|
||||
_plexClientService.Notify(Settings, header, message.Message);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
const string header = "Sonarr - Grabbed";
|
||||
const string header = "Radarr - Grabbed";
|
||||
|
||||
Notify(Settings, header, grabMessage.Message);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
const string header = "Sonarr - Downloaded";
|
||||
const string header = "Radarr - Downloaded";
|
||||
|
||||
Notify(Settings, header, message.Message);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||
{
|
||||
return Json.Deserialize<PlexMediaContainerLegacy>(response.Content)
|
||||
.Sections
|
||||
.Where(d => d.Type == "show")
|
||||
.Where(d => d.Type == "movie")
|
||||
.Select(s => new PlexSection
|
||||
{
|
||||
Id = s.Id,
|
||||
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||
return Json.Deserialize<PlexResponse<PlexSectionsContainer>>(response.Content)
|
||||
.MediaContainer
|
||||
.Sections
|
||||
.Where(d => d.Type == "show")
|
||||
.Where(d => d.Type == "movie")
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||
{
|
||||
if (version >= new Version(1, 3, 0) && version < new Version(1, 3, 1))
|
||||
{
|
||||
throw new PlexVersionException("Found version {0}, upgrade to PMS 1.3.1 to fix library updating and then restart Sonarr", version);
|
||||
throw new PlexVersionException("Found version {0}, upgrade to PMS 1.3.1 to fix library updating and then restart Radarr", version);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
const string title = "Sonarr - Episode Grabbed";
|
||||
const string title = "Radarr - Episode Grabbed";
|
||||
|
||||
_proxy.SendNotification(title, grabMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
const string title = "Sonarr - Episode Downloaded";
|
||||
const string title = "Radarr - Episode Downloaded";
|
||||
|
||||
_proxy.SendNotification(title, message.Message, Settings);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||
{
|
||||
try
|
||||
{
|
||||
const string title = "Sonarr - Test Notification";
|
||||
const string title = "Radarr - Test Notification";
|
||||
const string body = "This is a test message from Radarr";
|
||||
|
||||
SendNotification(title, body, settings);
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Notifications.Pushalot
|
||||
[FieldDefinition(1, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(PushalotPriority))]
|
||||
public int Priority { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Image", Type = FieldType.Checkbox, HelpText = "Include Sonarr logo with notifications")]
|
||||
[FieldDefinition(2, Label = "Image", Type = FieldType.Checkbox, HelpText = "Include Radarr logo with notifications")]
|
||||
public bool Image { get; set; }
|
||||
|
||||
public bool IsValid => !string.IsNullOrWhiteSpace(AuthToken);
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace NzbDrone.Core.Notifications.Slack
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = $"Test message from Sonarr posted at {DateTime.Now}";
|
||||
var message = $"Test message from Radarr posted at {DateTime.Now}";
|
||||
var payload = new SlackPayload
|
||||
{
|
||||
IconEmoji = Settings.Icon,
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace NzbDrone.Core.Notifications.Twitter
|
||||
{
|
||||
try
|
||||
{
|
||||
var body = "Sonarr: Test Message @ " + DateTime.Now;
|
||||
var body = "Radarr: Test Message @ " + DateTime.Now;
|
||||
|
||||
SendNotification(body, settings);
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace NzbDrone.Core.Notifications.Xbmc
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
const string header = "Sonarr - Grabbed";
|
||||
const string header = "Radarr - Grabbed";
|
||||
|
||||
Notify(Settings, header, grabMessage.Message);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
const string header = "Sonarr - Downloaded";
|
||||
const string header = "Radarr - Downloaded";
|
||||
|
||||
Notify(Settings, header, message.Message);
|
||||
UpdateAndClean(message.Series, message.OldFiles.Any());
|
||||
|
||||
@@ -183,8 +183,10 @@
|
||||
<Compile Include="Datastore\Migration\002_remove_tvrage_imdb_unique_constraint.cs" />
|
||||
<Compile Include="Datastore\Migration\003_remove_clean_title_from_scene_mapping.cs" />
|
||||
<Compile Include="Datastore\Migration\004_updated_history.cs" />
|
||||
<Compile Include="Datastore\Migration\115_update_movie_sorttitle.cs" />
|
||||
<Compile Include="Datastore\Migration\111_remove_bitmetv.cs" />
|
||||
<Compile Include="Datastore\Migration\112_remove_torrentleech.cs" />
|
||||
<Compile Include="Datastore\Migration\114_remove_fanzub.cs" />
|
||||
<Compile Include="Datastore\Migration\113_remove_broadcasthenet.cs" />
|
||||
<Compile Include="Datastore\Migration\108_update_schedule_interval.cs" />
|
||||
<Compile Include="Datastore\Migration\107_fix_movie_files.cs" />
|
||||
@@ -582,9 +584,6 @@
|
||||
<Compile Include="Indexers\Exceptions\RequestLimitReachedException.cs" />
|
||||
<Compile Include="Indexers\Exceptions\UnsupportedFeedException.cs" />
|
||||
<Compile Include="Indexers\EzrssTorrentRssParser.cs" />
|
||||
<Compile Include="Indexers\Fanzub\Fanzub.cs" />
|
||||
<Compile Include="Indexers\Fanzub\FanzubRequestGenerator.cs" />
|
||||
<Compile Include="Indexers\Fanzub\FanzubSettings.cs" />
|
||||
<Compile Include="Indexers\FetchAndParseRssService.cs" />
|
||||
<Compile Include="Indexers\PassThePopcorn\PassThePopcorn.cs" />
|
||||
<Compile Include="Indexers\PassThePopcorn\PassThePopcornApi.cs" />
|
||||
@@ -707,10 +706,12 @@
|
||||
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
|
||||
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\DownloadedMovieScanCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameMovieCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameMovieFilesCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
|
||||
<Compile Include="MediaFiles\DownloadedMovieCommandService.cs" />
|
||||
<Compile Include="MediaFiles\DownloadedMovieImportService.cs" />
|
||||
<Compile Include="MediaFiles\MovieFileMovingService.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieDownloadedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieFileAddedEvent.cs" />
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace NzbDrone.Core.Parser.Model
|
||||
public DownloadProtocol DownloadProtocol { get; set; }
|
||||
public int TvdbId { get; set; }
|
||||
public int TvRageId { get; set; }
|
||||
public int ImdbId { get; set; }
|
||||
public DateTime PublishDate { get; set; }
|
||||
|
||||
public string Origin { get; set; }
|
||||
@@ -82,6 +83,7 @@ namespace NzbDrone.Core.Parser.Model
|
||||
stringBuilder.AppendLine("DownloadProtocol: " + DownloadProtocol ?? "Empty");
|
||||
stringBuilder.AppendLine("TvdbId: " + TvdbId ?? "Empty");
|
||||
stringBuilder.AppendLine("TvRageId: " + TvRageId ?? "Empty");
|
||||
stringBuilder.AppendLine("ImdbId: " + ImdbId ?? "Empty");
|
||||
stringBuilder.AppendLine("PublishDate: " + PublishDate ?? "Empty");
|
||||
return stringBuilder.ToString();
|
||||
default:
|
||||
|
||||
@@ -18,16 +18,10 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex[] ReportMovieTitleRegex = new[]
|
||||
{
|
||||
//Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.Special.Edition.2011
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<edition>(\w+\.?edition))\.(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)",
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<edition>(\.?((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final|Extended|Rogue|Special|Despecialized).(Cut|Edition|Version)|Extended|Uncensored|Remastered|Unrated|Uncut|IMAX)))\.(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
//Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.2011.Special.Edition //TODO: Seems to slow down parsing heavily!
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)(?<edition>((\w+\.?){1,3}edition))",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
//Cut Movies, e.g: Mission.Impossible.3.Directors.Cut.2011
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<edition>(\w+\.?cut))\.(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
//Cut Movies, e.g: Mission.Impossible.3.2011.Directors.Cut
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)(?<edition>((\w+\.?){1,3}cut))",
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)(?<edition>((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final|Extended|Rogue|Special|Despecialized).(Cut|Edition|Version)|Extended|Uncensored|Remastered|Unrated|Uncut|IMAX))",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Normal movie format, e.g: Mission.Impossible.3.2011
|
||||
@@ -36,6 +30,15 @@ namespace NzbDrone.Core.Parser
|
||||
//PassThePopcorn Torrent names: Star.Wars[PassThePopcorn]
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
//That did not work? Maybe some tool uses [] for years. Who would do that?
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
};
|
||||
|
||||
private static readonly Regex[] ReportMovieTitleFolderRegex = new[]
|
||||
{
|
||||
//When year comes first.
|
||||
new Regex(@"^(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?<title>.+?)?$")
|
||||
};
|
||||
|
||||
private static readonly Regex[] ReportTitleRegex = new[]
|
||||
@@ -327,7 +330,7 @@ namespace NzbDrone.Core.Parser
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
|
||||
var result = ParseMovieTitle(fileInfo.Name);
|
||||
var result = ParseMovieTitle(fileInfo.Name, true);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
@@ -345,7 +348,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
}
|
||||
|
||||
public static ParsedMovieInfo ParseMovieTitle(string title)
|
||||
public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false)
|
||||
{
|
||||
|
||||
ParsedMovieInfo realResult = null;
|
||||
@@ -376,7 +379,14 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle, string.Empty);
|
||||
|
||||
foreach (var regex in ReportMovieTitleRegex)
|
||||
var allRegexes = ReportMovieTitleRegex.ToList();
|
||||
|
||||
if (isDir)
|
||||
{
|
||||
allRegexes.AddRange(ReportMovieTitleFolderRegex);
|
||||
}
|
||||
|
||||
foreach (var regex in allRegexes)
|
||||
{
|
||||
var match = regex.Matches(simpleTitle);
|
||||
|
||||
|
||||
@@ -149,7 +149,11 @@ namespace NzbDrone.Core.RootFolders
|
||||
foreach (string unmappedFolder in unmappedFolders)
|
||||
{
|
||||
var di = new DirectoryInfo(unmappedFolder.Normalize());
|
||||
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName });
|
||||
if (!di.Attributes.HasFlag(FileAttributes.System) && !di.Attributes.HasFlag(FileAttributes.Hidden))
|
||||
{
|
||||
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var setToRemove = SpecialFolders;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Radarr.Host
|
||||
SecurityProtocolPolicy.Register();
|
||||
X509CertificateValidationPolicy.Register();
|
||||
|
||||
Logger.Info("Starting Sonarr - {0} - Version {1}", Assembly.GetCallingAssembly().Location, Assembly.GetExecutingAssembly().GetName().Version);
|
||||
Logger.Info("Starting Radarr - {0} - Version {1}", Assembly.GetCallingAssembly().Location, Assembly.GetExecutingAssembly().GetName().Version);
|
||||
|
||||
if (!PlatformValidation.IsValidate(userAlert))
|
||||
{
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
{{/if_eq}}
|
||||
|
||||
{{#if_eq reason compare="MissingFromDisk"}}
|
||||
Sonarr was unable to find the file on disk so it was removed
|
||||
Radarr was unable to find the file on disk so it was removed
|
||||
{{/if_eq}}
|
||||
|
||||
{{#if_eq reason compare="Upgrade"}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Sonarr Calendar feed</h3>
|
||||
<h3>Radarr Calendar feed</h3>
|
||||
</div>
|
||||
<div class="modal-body edit-series-modal">
|
||||
<div class="form-horizontal">
|
||||
|
||||
34
src/UI/Cells/DownloadedQualityCell.js
Normal file
34
src/UI/Cells/DownloadedQualityCell.js
Normal file
@@ -0,0 +1,34 @@
|
||||
var Backgrid = require('backgrid');
|
||||
var ProfileCollection = require('../Profile/ProfileCollection');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backgrid.Cell.extend({
|
||||
className : 'profile-cell',
|
||||
|
||||
_originalInit : Backgrid.Cell.prototype.initialize,
|
||||
|
||||
initialize : function () {
|
||||
this._originalInit.apply(this, arguments);
|
||||
|
||||
this.listenTo(ProfileCollection, 'sync', this.render);
|
||||
},
|
||||
|
||||
render : function() {
|
||||
|
||||
this.$el.empty();
|
||||
if (this.model.get("movieFile")) {
|
||||
var profileId = this.model.get("movieFile").quality.quality.id;
|
||||
|
||||
var profile = _.findWhere(ProfileCollection.models, { id : profileId });
|
||||
|
||||
if (profile) {
|
||||
this.$el.html(profile.get('name'));
|
||||
} else {
|
||||
this.$el.html(this.model.get("movieFile").quality.quality.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
@@ -7,10 +7,16 @@ module.exports = TemplatedCell.extend({
|
||||
var monthNames = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
var cinemasDate = new Date(this.model.get("inCinemas"));
|
||||
var year = cinemasDate.getFullYear();
|
||||
var month = monthNames[cinemasDate.getMonth()];
|
||||
this.$el.html(month + " " + year); //Hack, but somehow handlebar helper does not work.
|
||||
return this;
|
||||
|
||||
this.$el.html("");
|
||||
|
||||
if (this.model.get("inCinemas")) {
|
||||
var cinemasDate = new Date(this.model.get("inCinemas"));
|
||||
var year = cinemasDate.getFullYear();
|
||||
var month = monthNames[cinemasDate.getMonth()];
|
||||
this.$el.html(month + " " + year); //Hack, but somehow handlebar helper does not work.
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,4 +3,8 @@ var TemplatedCell = require('./TemplatedCell');
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'movie-title-cell',
|
||||
template : 'Cells/MovieDownloadStatusTemplate',
|
||||
sortKey : function(model) {
|
||||
debugger;
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,5 +4,7 @@ var QualityCellEditor = require('./Edit/QualityCellEditor');
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'quality-cell',
|
||||
template : 'Cells/QualityCellTemplate',
|
||||
editor : QualityCellEditor
|
||||
});
|
||||
editor : QualityCellEditor,
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -181,10 +181,13 @@ Handlebars.registerHelper('inCinemas', function() {
|
||||
var year = d.getFullYear();
|
||||
return "Available: " + day + ". " + month + " " + year;
|
||||
}
|
||||
var cinemasDate = new Date(this.inCinemas);
|
||||
var year = cinemasDate.getFullYear();
|
||||
var month = monthNames[cinemasDate.getMonth()];
|
||||
return "In Cinemas: " + month + " " + year;
|
||||
if (this.inCinemas) {
|
||||
var cinemasDate = new Date(this.inCinemas);
|
||||
var year = cinemasDate.getFullYear();
|
||||
var month = monthNames[cinemasDate.getMonth()];
|
||||
return "In Cinemas: " + month + " " + year;
|
||||
}
|
||||
return "To be announced";
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('tvRageUrl', function() {
|
||||
|
||||
43
src/UI/ManualImport/Cells/MovieCell.js
Normal file
43
src/UI/ManualImport/Cells/MovieCell.js
Normal file
@@ -0,0 +1,43 @@
|
||||
var vent = require('../../vent');
|
||||
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||
var SelectMovieLayout = require('../Movie/SelectMovieLayout');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'series-title-cell editable',
|
||||
|
||||
events : {
|
||||
'click' : '_onClick'
|
||||
},
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
|
||||
var movie = this.model.get('movie');
|
||||
|
||||
if (movie)
|
||||
{
|
||||
this.$el.html(movie.title + " (" + movie.year + ")" );
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
},
|
||||
|
||||
_onClick : function () {
|
||||
var view = new SelectMovieLayout();
|
||||
|
||||
this.listenTo(view, 'manualimport:selected:movie', this._setMovie);
|
||||
|
||||
vent.trigger(vent.Commands.OpenModal2Command, view);
|
||||
},
|
||||
|
||||
_setMovie : function (e) {
|
||||
if (this.model.has('movie') && e.model.id === this.model.get('movie').id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.set({
|
||||
movie : e.model.toJSON()
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -16,6 +16,7 @@ var QualityCell = require('./Cells/QualityCell');
|
||||
var FileSizeCell = require('../Cells/FileSizeCell');
|
||||
var ApprovalStatusCell = require('../Cells/ApprovalStatusCell');
|
||||
var ManualImportCollection = require('./ManualImportCollection');
|
||||
var MovieCell = require('./Cells/MovieCell');
|
||||
var Messenger = require('../Shared/Messenger');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
@@ -49,23 +50,29 @@ module.exports = Marionette.Layout.extend({
|
||||
sortable : true
|
||||
},
|
||||
{
|
||||
name : 'series',
|
||||
label : 'Series',
|
||||
cell : SeriesCell,
|
||||
name : 'movie',
|
||||
label : 'Movie',
|
||||
cell : MovieCell,
|
||||
sortable : true
|
||||
},
|
||||
{
|
||||
name : 'seasonNumber',
|
||||
label : 'Season',
|
||||
cell : SeasonCell,
|
||||
sortable : true
|
||||
},
|
||||
{
|
||||
name : 'episodes',
|
||||
label : 'Episode(s)',
|
||||
cell : EpisodesCell,
|
||||
sortable : false
|
||||
},
|
||||
// {
|
||||
// name : 'series',
|
||||
// label : 'Series',
|
||||
// cell : SeriesCell,
|
||||
// sortable : true
|
||||
// },
|
||||
// {
|
||||
// name : 'seasonNumber',
|
||||
// label : 'Season',
|
||||
// cell : SeasonCell,
|
||||
// sortable : true
|
||||
// },
|
||||
// {
|
||||
// name : 'episodes',
|
||||
// label : 'Episode(s)',
|
||||
// cell : EpisodesCell,
|
||||
// sortable : false
|
||||
// },
|
||||
{
|
||||
name : 'quality',
|
||||
label : 'Quality',
|
||||
@@ -161,8 +168,8 @@ module.exports = Marionette.Layout.extend({
|
||||
},
|
||||
|
||||
_automaticImport : function (e) {
|
||||
CommandController.Execute('downloadedEpisodesScan', {
|
||||
name : 'downloadedEpisodesScan',
|
||||
CommandController.Execute('downloadedMovieScan', {
|
||||
name : 'downloadedMovieScan',
|
||||
path : e.folder
|
||||
});
|
||||
|
||||
@@ -176,29 +183,36 @@ module.exports = Marionette.Layout.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.any(selected, function (model) {
|
||||
return !model.has('series');
|
||||
})) {
|
||||
|
||||
this._showErrorMessage('Series must be chosen for each selected file');
|
||||
if(_.any(selected, function(model) {
|
||||
return !model.has('movie');
|
||||
})) {
|
||||
this._showErrorMessage('Movie must be chosen for each selected file');
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.any(selected, function (model) {
|
||||
return !model.has('seasonNumber');
|
||||
})) {
|
||||
// if (_.any(selected, function (model) {
|
||||
// return !model.has('series');
|
||||
// })) {
|
||||
|
||||
this._showErrorMessage('Season must be chosen for each selected file');
|
||||
return;
|
||||
}
|
||||
// this._showErrorMessage('Series must be chosen for each selected file');
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (_.any(selected, function (model) {
|
||||
return !model.has('episodes') || model.get('episodes').length === 0;
|
||||
})) {
|
||||
// if (_.any(selected, function (model) {
|
||||
// return !model.has('seasonNumber');
|
||||
// })) {
|
||||
|
||||
this._showErrorMessage('One or more episodes must be chosen for each selected file');
|
||||
return;
|
||||
}
|
||||
// this._showErrorMessage('Season must be chosen for each selected file');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (_.any(selected, function (model) {
|
||||
// return !model.has('episodes') || model.get('episodes').length === 0;
|
||||
// })) {
|
||||
|
||||
// this._showErrorMessage('One or more episodes must be chosen for each selected file');
|
||||
// return;
|
||||
// }
|
||||
|
||||
var importMode = this.ui.importMode.val();
|
||||
|
||||
@@ -207,8 +221,9 @@ module.exports = Marionette.Layout.extend({
|
||||
files : _.map(selected, function (file) {
|
||||
return {
|
||||
path : file.get('path'),
|
||||
seriesId : file.get('series').id,
|
||||
episodeIds : _.map(file.get('episodes'), 'id'),
|
||||
movieId : file.get('movie').id,
|
||||
// seriesId : file.get('series').id,
|
||||
// episodeIds : _.map(file.get('episodes'), 'id'),
|
||||
quality : file.get('quality'),
|
||||
downloadId : file.get('downloadId')
|
||||
};
|
||||
|
||||
101
src/UI/ManualImport/Movie/SelectMovieLayout.js
Normal file
101
src/UI/ManualImport/Movie/SelectMovieLayout.js
Normal file
@@ -0,0 +1,101 @@
|
||||
var _ = require('underscore');
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var MoviesCollection = require('../../Movies/MoviesCollection');
|
||||
var SelectRow = require('./SelectMovieRow');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'ManualImport/Movie/SelectMovieLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
movie : '.x-movie'
|
||||
},
|
||||
|
||||
ui : {
|
||||
filter : '.x-filter'
|
||||
},
|
||||
|
||||
columns : [
|
||||
{
|
||||
name : 'title',
|
||||
label : 'Title',
|
||||
cell : 'String',
|
||||
sortValue : 'title'
|
||||
}
|
||||
],
|
||||
|
||||
initialize : function() {
|
||||
this.movieCollection = MoviesCollection.clone();
|
||||
this._setModelCollection();
|
||||
|
||||
this.listenTo(this.movieCollection, 'row:selected', this._onSelected);
|
||||
this.listenTo(this, 'modal:afterShow', this._setFocus);
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
this.movieView = new Backgrid.Grid({
|
||||
columns : this.columns,
|
||||
collection : this.movieCollection,
|
||||
className : 'table table-hover season-grid',
|
||||
row : SelectRow
|
||||
});
|
||||
|
||||
this.movie.show(this.movieView);
|
||||
this._setupFilter();
|
||||
},
|
||||
|
||||
_setupFilter : function () {
|
||||
var self = this;
|
||||
|
||||
//TODO: This should be a mixin (same as Add Series searching)
|
||||
this.ui.filter.keyup(function(e) {
|
||||
if (_.contains([
|
||||
9,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
91,
|
||||
92,
|
||||
93
|
||||
], e.keyCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._filter(self.ui.filter.val());
|
||||
});
|
||||
},
|
||||
|
||||
_filter : function (term) {
|
||||
this.movieCollection.setFilter(['title', term, 'contains']);
|
||||
this._setModelCollection();
|
||||
},
|
||||
|
||||
_onSelected : function (e) {
|
||||
this.trigger('manualimport:selected:movie', { model: e.model });
|
||||
|
||||
vent.trigger(vent.Commands.CloseModal2Command);
|
||||
},
|
||||
|
||||
_setFocus : function () {
|
||||
this.ui.filter.focus();
|
||||
},
|
||||
|
||||
_setModelCollection: function () {
|
||||
var self = this;
|
||||
|
||||
_.each(this.movieCollection.models, function (model) {
|
||||
model.collection = self.movieCollection;
|
||||
});
|
||||
}
|
||||
});
|
||||
30
src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs
Normal file
30
src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="modal-content">
|
||||
<div class="manual-import-modal">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
|
||||
<h3>
|
||||
Manual Import - Select Movie
|
||||
</h3>
|
||||
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control x-filter" placeholder="Filter movies" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 x-movie"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
13
src/UI/ManualImport/Movie/SelectMovieRow.js
Normal file
13
src/UI/ManualImport/Movie/SelectMovieRow.js
Normal file
@@ -0,0 +1,13 @@
|
||||
var Backgrid = require('backgrid');
|
||||
|
||||
module.exports = Backgrid.Row.extend({
|
||||
className : 'select-row select-series-row',
|
||||
|
||||
events : {
|
||||
'click' : '_onClick'
|
||||
},
|
||||
|
||||
_onClick : function() {
|
||||
this.model.collection.trigger('row:selected', { model: this.model });
|
||||
}
|
||||
});
|
||||
@@ -4,16 +4,25 @@ var Marionette = require('marionette');
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'ManualImport/Summary/ManualImportSummaryViewTemplate',
|
||||
|
||||
// initialize : function (options) {
|
||||
// var episodes = _.map(options.episodes, function (episode) {
|
||||
// return episode.toJSON();
|
||||
// });
|
||||
|
||||
// this.templateHelpers = {
|
||||
// file : options.file,
|
||||
// series : options.series,
|
||||
// season : options.season,
|
||||
// episodes : episodes,
|
||||
// quality : options.quality
|
||||
// };
|
||||
// }
|
||||
|
||||
initialize : function (options) {
|
||||
var episodes = _.map(options.episodes, function (episode) {
|
||||
return episode.toJSON();
|
||||
});
|
||||
|
||||
this.templateHelpers = {
|
||||
file : options.file,
|
||||
series : options.series,
|
||||
season : options.season,
|
||||
episodes : episodes,
|
||||
movie : options.movie,
|
||||
quality : options.quality
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,16 +3,8 @@
|
||||
<dt>Path:</dt>
|
||||
<dd>{{file}}</dd>
|
||||
|
||||
<dt>Series:</dt>
|
||||
<dd>{{series.title}}</dd>
|
||||
|
||||
<dt>Season:</dt>
|
||||
<dd>{{season.seasonNumber}}</dd>
|
||||
|
||||
{{#each episodes}}
|
||||
<dt>Episode:</dt>
|
||||
<dd>{{episodeNumber}} - {{title}}</dd>
|
||||
{{/each}}
|
||||
<dt>Movie:</dt>
|
||||
<dd>{{movie.title}} ({{movie.year}})</dd>
|
||||
|
||||
<dt>Quality:</dt>
|
||||
<dd>{{quality.name}}</dd>
|
||||
|
||||
@@ -13,6 +13,7 @@ var MovieLinksCell = require('../../Cells/MovieLinksCell');
|
||||
var MovieActionCell = require('../../Cells/MovieActionCell');
|
||||
var MovieStatusCell = require('../../Cells/MovieStatusCell');
|
||||
var MovieDownloadStatusCell = require('../../Cells/MovieDownloadStatusCell');
|
||||
var DownloadedQualityCell = require('../../Cells/DownloadedQualityCell');
|
||||
var FooterView = require('./FooterView');
|
||||
var FooterModel = require('./FooterModel');
|
||||
var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout');
|
||||
@@ -40,6 +41,11 @@ module.exports = Marionette.Layout.extend({
|
||||
cell : MovieTitleCell,
|
||||
cellValue : 'this',
|
||||
},
|
||||
{
|
||||
name : "downloadedQuality",
|
||||
label : "Downloaded",
|
||||
cell : DownloadedQualityCell,
|
||||
},
|
||||
{
|
||||
name : 'profileId',
|
||||
label : 'Profile',
|
||||
@@ -54,12 +60,19 @@ module.exports = Marionette.Layout.extend({
|
||||
name : 'this',
|
||||
label : 'Links',
|
||||
cell : MovieLinksCell,
|
||||
className : "movie-links-cell"
|
||||
className : "movie-links-cell",
|
||||
sortable : false,
|
||||
},
|
||||
{
|
||||
name : "this",
|
||||
label : "Status",
|
||||
cell : MovieDownloadStatusCell,
|
||||
sortValue : function(m, k) {
|
||||
if (m.get("downloaded")) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
|
||||
@@ -9,5 +9,31 @@ module.exports = Backbone.Model.extend({
|
||||
episodeCount : 0,
|
||||
isExisting : false,
|
||||
status : 0
|
||||
},
|
||||
|
||||
getStatus : function() {
|
||||
var monitored = this.get("monitored");
|
||||
var status = this.get("status");
|
||||
var inCinemas = this.get("inCinemas");
|
||||
var date = new Date(inCinemas);
|
||||
var timeSince = new Date().getTime() - date.getTime();
|
||||
var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
|
||||
|
||||
if (status === "announced") {
|
||||
return "announced"
|
||||
}
|
||||
|
||||
if (numOfMonths < 3 && numOfMonths > 0) {
|
||||
|
||||
return "inCinemas";
|
||||
}
|
||||
|
||||
if (status === 'released') {
|
||||
return "released";
|
||||
}
|
||||
|
||||
if (numOfMonths > 3) {
|
||||
return "released";//TODO: Update for PreDB.me
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,10 +15,10 @@ var Collection = PageableCollection.extend({
|
||||
tableName : 'movie',
|
||||
|
||||
state : {
|
||||
sortKey : 'title',
|
||||
sortKey : 'sortTitle',
|
||||
order : 1,
|
||||
pageSize : 100000,
|
||||
secondarySortKey : 'title',
|
||||
secondarySortKey : 'sortTitle',
|
||||
secondarySortOrder : -1
|
||||
},
|
||||
|
||||
@@ -73,9 +73,28 @@ var Collection = PageableCollection.extend({
|
||||
|
||||
sortMappings : {
|
||||
title : {
|
||||
sortKey : 'title'
|
||||
sortKey : 'sortTitle'
|
||||
},
|
||||
statusWeight : {
|
||||
sortValue : function(model, attr) {
|
||||
if (model.getStatus() == "released") {
|
||||
return 1;
|
||||
}
|
||||
if (model.getStatus() == "inCinemas") {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
downloadedQuality : {
|
||||
sortValue : function(model, attr) {
|
||||
if (model.get("movieFile")) {
|
||||
return 1000-model.get("movieFile").quality.quality.id;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
nextAiring : {
|
||||
sortValue : function(model, attr, order) {
|
||||
var nextAiring = model.get(attr);
|
||||
@@ -91,7 +110,15 @@ var Collection = PageableCollection.extend({
|
||||
return Number.MAX_VALUE;
|
||||
}
|
||||
},
|
||||
|
||||
status: {
|
||||
sortValue : function(model, attr) {
|
||||
debugger;
|
||||
if (model.get("downloaded")) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
percentOfEpisodes : {
|
||||
sortValue : function(model, attr) {
|
||||
var percentOfEpisodes = model.get(attr);
|
||||
@@ -100,7 +127,18 @@ var Collection = PageableCollection.extend({
|
||||
return percentOfEpisodes + episodeCount / 1000000;
|
||||
}
|
||||
},
|
||||
inCinemas : {
|
||||
|
||||
sortValue : function(model, attr) {
|
||||
var monthNames = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
if (model.get("inCinemas")) {
|
||||
return model.get("inCinemas");
|
||||
}
|
||||
return "2100-01-01";
|
||||
}
|
||||
},
|
||||
path : {
|
||||
sortValue : function(model) {
|
||||
var path = model.get('path');
|
||||
|
||||
@@ -37,7 +37,7 @@ module.exports = Marionette.Layout.extend({
|
||||
name : 'edition',
|
||||
label : 'Edition',
|
||||
cell : EditionCell,
|
||||
title : "Edition"
|
||||
title : "Edition",
|
||||
},
|
||||
{
|
||||
name : 'indexer',
|
||||
@@ -57,7 +57,7 @@ module.exports = Marionette.Layout.extend({
|
||||
{
|
||||
name : 'quality',
|
||||
label : 'Quality',
|
||||
cell : QualityCell
|
||||
cell : QualityCell,
|
||||
},
|
||||
{
|
||||
name : 'rejections',
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
<div id="episode-release-grid" class="table-responsive"></div>
|
||||
<button class="btn x-search-back">Back</button>
|
||||
@@ -7,12 +7,12 @@
|
||||
<span class="icon-sonarr-navbar-collapsed fa-lg"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{{UrlBase}}/">
|
||||
<!--<img src="{{UrlBase}}/Content/Images/logo.png?v=2" alt="Sonarr">-->
|
||||
<!--<img src="{{UrlBase}}/Content/Images/logo.png?v=2" alt="Radarr">-->
|
||||
<img src="{{UrlBase}}/Content/Images/logos/128.png" class="visible-lg"/>
|
||||
<img src="{{UrlBase}}/Content/Images/logos/64.png" class="visible-md visible-sm"/>
|
||||
<span class="visible-xs">
|
||||
<img src="{{UrlBase}}/Content/Images/logos/32.png"/>
|
||||
<span class="logo-text">sonarr</span>
|
||||
<span class="logo-text">Radarr</span>
|
||||
</span>
|
||||
|
||||
</a>
|
||||
|
||||
@@ -16,7 +16,7 @@ var Collection = PagableCollection.extend({
|
||||
|
||||
sortMappings : {
|
||||
'quality' : {
|
||||
sortKey : 'qualityWeight'
|
||||
sortKey : "qualityWeight"
|
||||
},
|
||||
'rejections' : {
|
||||
sortValue : function(model) {
|
||||
@@ -30,6 +30,9 @@ var Collection = PagableCollection.extend({
|
||||
return releaseWeight;
|
||||
}
|
||||
},
|
||||
"edition" : {
|
||||
sortKey : "edition"
|
||||
},
|
||||
'download' : {
|
||||
sortKey : 'releaseWeight'
|
||||
},
|
||||
|
||||
@@ -78,7 +78,7 @@ module.exports = Marionette.Layout.extend({
|
||||
|
||||
CommandController.Execute('renameMovieFiles', {
|
||||
name : 'renameMovieFiles',
|
||||
movieId : this.model.id,
|
||||
movieId : this.model.id,
|
||||
files : files
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Should Sonarr download episodes for this series?"/>
|
||||
<i class="icon-sonarr-form-info" title="Should Radarr download episodes for this series?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-2 col-sm-pull-1">
|
||||
<input type="number" name="downloadedEpisodesScanInterval" class="form-control" />
|
||||
<input type="number" name="downloadedMovieScanInterval" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="modal-body remotepath-mapping-modal">
|
||||
<div class="form-horizontal">
|
||||
<div>
|
||||
<p>Use this feature if you have a remotely running Download Client. Sonarr will use the information provided to translate the paths provided by the Download Client API to something Sonarr can access and import.</p>
|
||||
<p>Use this feature if you have a remotely running Download Client. Radarr will use the information provided to translate the paths provided by the Download Client API to something Radarr can access and import.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Host</label>
|
||||
@@ -40,7 +40,7 @@
|
||||
<label class="col-sm-3 control-label">Local Path</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-5 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Path that Sonarr should use to access the same directory remotely." />
|
||||
<i class="icon-sonarr-form-info" title="Path that Radarr should use to access the same directory remotely." />
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5 col-sm-pull-1">
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Open a web browser and navigate to Sonarr homepage on app start. Has no effect if installed as a windows service"/>
|
||||
<i class="icon-sonarr-form-info" title="Open a web browser and navigate to Radarr homepage on app start. Has no effect if installed as a windows service"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +114,7 @@
|
||||
|
||||
<div class="col-sm-1 col-sm-push-4 help-inline">
|
||||
<i class="icon-sonarr-form-warning" title="Requires restart to take effect"/>
|
||||
<i class="icon-sonarr-form-info" title="Require Username and Password to access Sonarr"/>
|
||||
<i class="icon-sonarr-form-info" title="Require Username and Password to access Radarr"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 col-sm-pull-1">
|
||||
@@ -308,7 +308,7 @@
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Send anonymous information about your browser and which parts of the web interface you use to Sonarr servers. We use this information to prioritize features and browser support. We will NEVER include any personal information or any information that could identify you."/>
|
||||
<i class="icon-sonarr-form-info" title="Send anonymous information about your browser and which parts of the web interface you use to Radarr servers. We use this information to prioritize features and browser support. We will NEVER include any personal information or any information that could identify you."/>
|
||||
<i class="icon-sonarr-form-warning" title="Requires restart to take effect"/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info">
|
||||
Sonarr supports any indexer that uses the Newznab standard, as well as other indexers listed below.<br/>
|
||||
Radarr supports any indexer that uses the Newznab standard, as well as other indexers listed below.<br/>
|
||||
For more information on the individual indexers, click on the info buttons.
|
||||
</div>
|
||||
<div class="add-indexer add-thingies">
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Extract video information such as resolution, runtime and codec information from files. This requires Sonarr to read parts of the file which may cause high disk or network activity during scans."/>
|
||||
<i class="icon-sonarr-form-info" title="Extract video information such as resolution, runtime and codec information from files. This requires Radarr to read parts of the file which may cause high disk or network activity during scans."/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Include Series Title</label>
|
||||
{{!--<label class="col-sm-3 control-label">Include Series Title</label>
|
||||
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
@@ -36,7 +36,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>--}}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Include Quality</label>
|
||||
@@ -88,7 +88,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{{!--<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Numbering Style</label>
|
||||
|
||||
<div class="col-sm-9">
|
||||
@@ -99,4 +99,4 @@
|
||||
<option value="s{season:00}e{episode:00}">s01e05</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>--}}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<label class="col-sm-3 control-label">File chmod mask</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-4 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Octal, applied to media files when imported/renamed by Sonarr"/>
|
||||
<i class="icon-sonarr-form-info" title="Octal, applied to media files when imported/renamed by Radarr"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 col-sm-pull-1">
|
||||
@@ -40,7 +40,7 @@
|
||||
<label class="col-sm-3 control-label">Folder chmod mask</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-4 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Octal, applied to series/season folders created by Sonarr"/>
|
||||
<i class="icon-sonarr-form-info" title="Octal, applied to series/season folders created by Radarr"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 col-sm-pull-1">
|
||||
|
||||
@@ -40,6 +40,6 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Once this quality is reached Sonarr will no longer download episodes"/>
|
||||
<i class="icon-sonarr-form-info" title="Once this quality is reached Radarr will no longer download episodes"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ var LogDetailsView = require('../../System/Logs/Table/Details/LogDetailsView');
|
||||
var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout');
|
||||
var ManualImportLayout = require('../../ManualImport/ManualImportLayout');
|
||||
var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout');
|
||||
var MoviesDetailsLayout = require('../../Movies/Details/MoviesDetailsLayout');
|
||||
|
||||
module.exports = Marionette.AppRouter.extend({
|
||||
initialize : function() {
|
||||
|
||||
@@ -133,7 +133,7 @@ module.exports = Marionette.Layout.extend({
|
||||
{
|
||||
title : 'Rescan Drone Factory Folder',
|
||||
icon : 'icon-sonarr-refresh',
|
||||
command : 'downloadedepisodesscan',
|
||||
command : 'downloadedMovieScan',
|
||||
properties : { sendUpdates : true }
|
||||
},
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<link rel="mask-icon" href="/Content/Images/safari/logo.svg" color="#35c5f4">
|
||||
<link rel="icon" type="image/ico" href="/Content/Images/favicon.ico"/>
|
||||
|
||||
<link rel="alternate" type="text/calendar" title="iCalendar feed for Sonarr" href="/feed/calendar/NzbDrone.ics"/>
|
||||
<link rel="alternate" type="text/calendar" title="iCalendar feed for Radarr" href="/feed/calendar/NzbDrone.ics"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="container-fluid main-region" id="main-region">
|
||||
<div class="col-md-2 col-md-offset-5">
|
||||
<form name="login" id="login" class="login" method="POST">
|
||||
<h2><img src="/Content/Images/logos/32.png" alt=""/> Sonarr</h2>
|
||||
<h2><img src="/Content/Images/logos/32.png" alt=""/> Radarr</h2>
|
||||
<div class="form-group">
|
||||
<label for="username" class="sr-only">Email address</label>
|
||||
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
|
||||
|
||||
Reference in New Issue
Block a user