mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-17 16:14:46 -04:00
Compare commits
52 Commits
v0.2.0.535
...
v0.2.0.596
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
640edf0cce | ||
|
|
280445e756 | ||
|
|
bae8d5e9a4 | ||
|
|
07254adf91 | ||
|
|
d33ec334f3 | ||
|
|
a80e9f11f2 | ||
|
|
4a0ef984fb | ||
|
|
685012280b | ||
|
|
6963078669 | ||
|
|
7182081fca | ||
|
|
87ee360818 | ||
|
|
ad222570be | ||
|
|
680681c8bd | ||
|
|
e2ae7536ad | ||
|
|
98c117a460 | ||
|
|
c54f8806b3 | ||
|
|
454d5c37f9 | ||
|
|
e9f084fd81 | ||
|
|
d1bbcdc039 | ||
|
|
c17deb7d92 | ||
|
|
7066b078ab | ||
|
|
b4bb8875d3 | ||
|
|
cb596488f2 | ||
|
|
3403ddf993 | ||
|
|
17118cf24d | ||
|
|
27ab70333c | ||
|
|
f4031f1e5f | ||
|
|
a9154559b8 | ||
|
|
0f2f2e4b32 | ||
|
|
6deefbb997 | ||
|
|
43a71da0a7 | ||
|
|
499e46e10a | ||
|
|
3f013271c9 | ||
|
|
529591bc18 | ||
|
|
79307d3c25 | ||
|
|
8f79563cf0 | ||
|
|
0dc67419be | ||
|
|
066c746e5f | ||
|
|
31fcac5bd9 | ||
|
|
1b29b89bf1 | ||
|
|
c593f4250d | ||
|
|
59c07cc5f3 | ||
|
|
2b1023e768 | ||
|
|
f10af08f95 | ||
|
|
18fcda5fd6 | ||
|
|
1ccfde334f | ||
|
|
421a191650 | ||
|
|
35e046bb87 | ||
|
|
8ece7e8b4d | ||
|
|
fadc5f0099 | ||
|
|
680430737d | ||
|
|
5d3750a295 |
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,5 +1,9 @@
|
||||
**Description:**
|
||||
|
||||
Check first that your problem is not listed in our wiki section:
|
||||
* https://github.com/Radarr/Radarr/wiki/Common-Problems
|
||||
* https://github.com/Radarr/Radarr/wiki/FAQ
|
||||
|
||||
Provide a description of the feature request or bug here, the more details the better.
|
||||
Please also try to include the following if you are reporting a bug
|
||||
|
||||
|
||||
17
README.md
17
README.md
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="/Logo/text256.png" alt="Radarr">
|
||||
<img src="/Logo/text256.png" alt="Radarr">
|
||||
</p>
|
||||
|
||||
Radarr is an __independent__ fork of [Sonarr](https://github.com/Sonarr/Sonarr) reworked for automatically downloading movies via Usenet and BitTorrent.
|
||||
@@ -72,14 +72,21 @@ Radarr is currently undergoing rapid development and pull requests are actively
|
||||
* New PassThePopcorn Indexer
|
||||
* QBittorrent, Deluge, rTorrent, Transmission and uTorrent download client (Other clients are coming)
|
||||
* New TorrentPotato Indexer (Works well with [Jackett](https://github.com/Jackett/Jackett))
|
||||
* Scanning PreDB to know when a new release is available
|
||||
* Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114))
|
||||
* Full integration with Kodi, Plex (notification, library update)
|
||||
* And a beautiful UI
|
||||
|
||||
### Planned Features
|
||||
|
||||
* Scanning PreDB to know when a new release is available
|
||||
* Fixing the other Indexers and download clients
|
||||
* Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114))
|
||||
* Full integration with Kodi, Plex (notification, library update, metadata)
|
||||
* Downloading Metadata such as trailers or subtitles (\*)
|
||||
* Adding metadata such as posters and information for Kodi and others to use (\*)
|
||||
* Dynamically renaming folders with quality info, etc. (\*)
|
||||
* Supporting custom folder structures, such as all movie files in one folder (\*)
|
||||
* Supporting multiple editions per movies (waiting on The Movie Database to finish their implementation)
|
||||
* Supporting collections of movies, such as James Bond
|
||||
|
||||
**Note:** All features marked with (\*) are set to be in the first release of Radarr.
|
||||
|
||||
#### [Feature Requests](http://feathub.com/Radarr/Radarr)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ test:
|
||||
|
||||
artifacts:
|
||||
- path: '_artifacts\*.zip'
|
||||
- path: '_artifacts\*.exe'
|
||||
- path: '_artifacts\*.tar.gz'
|
||||
|
||||
cache:
|
||||
@@ -42,3 +43,6 @@ only_commits:
|
||||
- osx/
|
||||
- gulp/
|
||||
- logo/
|
||||
- setup/
|
||||
- appveyor.yml
|
||||
- build-appveyor.cake
|
||||
|
||||
@@ -264,6 +264,13 @@ Task("ArtifactsWindows").Does(() => {
|
||||
CopyDirectory(outputFolder, artifactsFolderWindows + "/Radarr");
|
||||
});
|
||||
|
||||
Task("ArtifactsWindowsInstaller").Does(() => {
|
||||
InnoSetup("./setup/nzbdrone.iss", new InnoSetupSettings {
|
||||
OutputDirectory = artifactsFolder,
|
||||
ToolPath = "./setup/inno/ISCC.exe"
|
||||
});
|
||||
});
|
||||
|
||||
Task("ArtifactsLinux").Does(() => {
|
||||
CopyDirectory(outputFolderMono, artifactsFolderLinux + "/Radarr");
|
||||
});
|
||||
@@ -293,6 +300,7 @@ Task("CompressArtifacts").Does(() => {
|
||||
Task("Artifacts")
|
||||
.IsDependentOn("CleanArtifacts")
|
||||
.IsDependentOn("ArtifactsWindows")
|
||||
.IsDependentOn("ArtifactsWindowsInstaller")
|
||||
.IsDependentOn("ArtifactsLinux")
|
||||
.IsDependentOn("ArtifactsOsx")
|
||||
.IsDependentOn("ArtifactsOsxApp")
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define AppName "Sonarr"
|
||||
#define AppPublisher "Team Sonarr"
|
||||
#define AppURL "https://sonarr.tv/"
|
||||
#define ForumsURL "https://forums.sonarr.tv/"
|
||||
#define AppExeName "NzbDrone.exe"
|
||||
#define AppName "Radarr"
|
||||
#define AppPublisher "Team Radarr"
|
||||
#define AppURL "https://radarr.video/"
|
||||
#define ForumsURL "https://github.com/Radarr/Radarr/issues"
|
||||
#define AppExeName "Radarr.exe"
|
||||
#define BuildNumber "2.0"
|
||||
#define BuildNumber GetEnv('BUILD_NUMBER')
|
||||
#define BranchName GetEnv('branch')
|
||||
#define BuildVersion GetEnv('APPVEYOR_BUILD_VERSION')
|
||||
#define BranchName GetEnv('APPVEYOR_REPO_BRANCH')
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{56C1065D-3523-4025-B76D-6F73F67F7F71}
|
||||
AppId={{56C1065D-3523-4025-B76D-6F73F67F7F82}
|
||||
AppName={#AppName}
|
||||
AppVersion=2.0
|
||||
AppVersion=0.2
|
||||
AppPublisher={#AppPublisher}
|
||||
AppPublisherURL={#AppURL}
|
||||
AppSupportURL={#ForumsURL}
|
||||
AppUpdatesURL={#AppURL}
|
||||
DefaultDirName={commonappdata}\NzbDrone\bin
|
||||
DefaultDirName={commonappdata}\Radarr\bin
|
||||
DisableDirPage=yes
|
||||
DefaultGroupName={#AppName}
|
||||
DisableProgramGroupPage=yes
|
||||
OutputBaseFilename=NzbDrone.{#BranchName}.{#BuildNumber}
|
||||
OutputBaseFilename=Radarr.{#BranchName}.{#BuildVersion}.installer
|
||||
SolidCompression=yes
|
||||
AppCopyright=Creative Commons 3.0 License
|
||||
AllowUNCPath=False
|
||||
UninstallDisplayIcon={app}\NzbDrone.exe
|
||||
UninstallDisplayIcon={app}\Radarr.exe
|
||||
DisableReadyPage=True
|
||||
CompressionThreads=2
|
||||
Compression=lzma2/normal
|
||||
@@ -44,7 +44,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
Name: "windowsService"; Description: "Install as a Windows Service"
|
||||
|
||||
[Files]
|
||||
Source: "..\_output\NzbDrone.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\_output\Radarr.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\_output\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
@@ -53,8 +53,8 @@ Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
|
||||
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: waituntilterminated;
|
||||
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/i"; Flags: waituntilterminated; Tasks: windowsService
|
||||
Filename: "{app}\radarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated;
|
||||
Filename: "{app}\radarr.console.exe"; Parameters: "/i"; Flags: waituntilterminated; Tasks: windowsService
|
||||
|
||||
[UninstallRun]
|
||||
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
||||
Filename: "{app}\radarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace NzbDrone.Api.Config
|
||||
public int Retention { get; set; }
|
||||
public int RssSyncInterval { get; set; }
|
||||
public int AvailabilityDelay { get; set; }
|
||||
public bool AllowHardcodedSubs { get; set; }
|
||||
public string WhitelistedHardcodedSubs { get; set; }
|
||||
}
|
||||
|
||||
public static class IndexerConfigResourceMapper
|
||||
@@ -21,6 +23,9 @@ namespace NzbDrone.Api.Config
|
||||
Retention = model.Retention,
|
||||
RssSyncInterval = model.RssSyncInterval,
|
||||
AvailabilityDelay = model.AvailabilityDelay,
|
||||
AllowHardcodedSubs = model.AllowHardcodedSubs,
|
||||
WhitelistedHardcodedSubs = model.WhitelistedHardcodedSubs,
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace NzbDrone.Api.Queue
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
_downloadService.DownloadReport(pendingRelease.RemoteEpisode);
|
||||
_downloadService.DownloadReport(pendingRelease.RemoteMovie);
|
||||
|
||||
return resource.AsResponse();
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace NzbDrone.Api.Movie
|
||||
|
||||
pagingSpec.FilterExpression = _moviesService.ConstructFilterExpression(pagingResource.FilterKey, pagingResource.FilterValue, pagingResource.FilterType);
|
||||
|
||||
return ApplyToPage(_moviesService.Paged, pagingSpec, MovieResourceMapper.ToResource);
|
||||
return ApplyToPage(_moviesService.Paged, pagingSpec, MapToResource);
|
||||
}
|
||||
|
||||
protected MovieResource MapToResource(Core.Tv.Movie movies)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{
|
||||
public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, application/xml, text/xml");
|
||||
public static readonly HttpAccept Json = new HttpAccept("application/json");
|
||||
public static readonly HttpAccept JsonCharset = new HttpAccept("application/json;charset=utf-8");
|
||||
public static readonly HttpAccept Html = new HttpAccept("text/html");
|
||||
|
||||
public string Value { get; private set; }
|
||||
|
||||
@@ -275,7 +275,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
{ "default_destination", _defaultDestination },
|
||||
};
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationInfoProxy>()
|
||||
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
|
||||
.Returns(_downloadStationConfigItems);
|
||||
}
|
||||
@@ -311,7 +311,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
torrents = new List<DownloadStationTask>();
|
||||
}
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
|
||||
.Returns(torrents);
|
||||
}
|
||||
@@ -330,11 +330,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
|
||||
.Callback(PrepareClientToReturnQueuedItem);
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
|
||||
.Callback(PrepareClientToReturnQueuedItem);
|
||||
}
|
||||
@@ -352,7 +352,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
{
|
||||
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Setup(d => d.GetTasks(_settings))
|
||||
.Returns(tasks);
|
||||
|
||||
@@ -372,7 +372,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
||||
}
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
{ "default_destination", _defaultDestination },
|
||||
};
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationInfoProxy>()
|
||||
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
|
||||
.Returns(_downloadStationConfigItems);
|
||||
}
|
||||
@@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
nzbs = new List<DownloadStationTask>();
|
||||
}
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
|
||||
.Returns(nzbs);
|
||||
}
|
||||
@@ -233,7 +233,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
|
||||
*/
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
|
||||
.Callback(PrepareClientToReturnQueuedItem);
|
||||
}
|
||||
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
{
|
||||
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Setup(d => d.GetTasks(_settings))
|
||||
.Returns(tasks);
|
||||
}
|
||||
@@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
|
||||
|
||||
Mocker.GetMock<IDownloadStationProxy>()
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
||||
}
|
||||
|
||||
|
||||
@@ -25,26 +25,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
|
||||
};
|
||||
|
||||
_downloading = new RTorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
IsFinished = false,
|
||||
IsOpen = true,
|
||||
IsActive = true,
|
||||
Name = _title,
|
||||
TotalSize = 1000,
|
||||
RemainingSize = 500,
|
||||
Path = "somepath"
|
||||
};
|
||||
{
|
||||
Hash = "HASH",
|
||||
IsFinished = false,
|
||||
IsOpen = true,
|
||||
IsActive = true,
|
||||
Name = _title,
|
||||
TotalSize = 1000,
|
||||
RemainingSize = 500,
|
||||
Path = "somepath"
|
||||
};
|
||||
|
||||
_completed = new RTorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
IsFinished = true,
|
||||
Name = _title,
|
||||
TotalSize = 1000,
|
||||
RemainingSize = 0,
|
||||
Path = "somepath"
|
||||
};
|
||||
{
|
||||
Hash = "HASH",
|
||||
IsFinished = true,
|
||||
Name = _title,
|
||||
TotalSize = 1000,
|
||||
RemainingSize = 0,
|
||||
Path = "somepath"
|
||||
};
|
||||
|
||||
Mocker.GetMock<ITorrentFileInfoReader>()
|
||||
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>()))
|
||||
@@ -54,11 +54,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
|
||||
protected void GivenSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<IRTorrentProxy>()
|
||||
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<RTorrentSettings>()))
|
||||
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<RTorrentPriority>(), It.IsAny<string>(), It.IsAny<RTorrentSettings>()))
|
||||
.Callback(PrepareClientToReturnCompletedItem);
|
||||
|
||||
Mocker.GetMock<IRTorrentProxy>()
|
||||
.Setup(s => s.AddTorrentFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<RTorrentSettings>()))
|
||||
.Setup(s => s.AddTorrentFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<RTorrentPriority>(), It.IsAny<string>(), It.IsAny<RTorrentSettings>()))
|
||||
.Callback(PrepareClientToReturnCompletedItem);
|
||||
|
||||
|
||||
@@ -116,11 +116,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
var remoteEpisode = CreateRemoteMovie();
|
||||
|
||||
var id = Subject.Download(remoteMovie);
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
private SabnzbdHistory _failed;
|
||||
private SabnzbdHistory _completed;
|
||||
private SabnzbdConfig _config;
|
||||
private SabnzbdFullStatus _fullStatus;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
{
|
||||
Status = SabnzbdDownloadStatus.Failed,
|
||||
Size = 1000,
|
||||
Category = "tv",
|
||||
Category = "tv",
|
||||
Id = "sabnzbd_nzb12345",
|
||||
Title = "Droned.1998.1080p.WEB-DL-DRONE"
|
||||
}
|
||||
@@ -80,7 +81,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
{
|
||||
Status = SabnzbdDownloadStatus.Completed,
|
||||
Size = 1000,
|
||||
Category = "tv",
|
||||
Category = "tv",
|
||||
Id = "sabnzbd_nzb12345",
|
||||
Title = "Droned.1998.1080p.WEB-DL-DRONE",
|
||||
Storage = "/remote/mount/vv/Droned.1998.1080p.WEB-DL-DRONE"
|
||||
@@ -100,9 +101,29 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
}
|
||||
};
|
||||
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Setup(v => v.GetVersion(It.IsAny<SabnzbdSettings>()))
|
||||
.Returns("1.2.3");
|
||||
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Setup(s => s.GetConfig(It.IsAny<SabnzbdSettings>()))
|
||||
.Returns(_config);
|
||||
|
||||
_fullStatus = new SabnzbdFullStatus
|
||||
{
|
||||
CompleteDir = @"Y:\nzbget\root\complete".AsOsAgnostic()
|
||||
};
|
||||
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Setup(s => s.GetFullStatus(It.IsAny<SabnzbdSettings>()))
|
||||
.Returns(_fullStatus);
|
||||
}
|
||||
|
||||
protected void GivenVersion(string version)
|
||||
{
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Setup(s => s.GetVersion(It.IsAny<SabnzbdSettings>()))
|
||||
.Returns(version);
|
||||
}
|
||||
|
||||
protected void GivenFailedDownload()
|
||||
@@ -166,7 +187,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
|
||||
GivenQueue(_queued);
|
||||
GivenHistory(null);
|
||||
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyQueued(result);
|
||||
@@ -387,23 +408,46 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
result.OutputPath.Should().Be(@"C:\sorted\somewhere\asdfasdf\asdfasdf.mkv".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads\vv")]
|
||||
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed\vv")]
|
||||
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads/vv")]
|
||||
[TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed/vv")]
|
||||
public void should_return_status_with_outputdir(string rootFolder, string completeDir, string categoryDir, string expectedDir)
|
||||
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")]
|
||||
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")]
|
||||
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")]
|
||||
[TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed", @"/nzbget/root/completed/vv")]
|
||||
public void should_return_status_with_outputdir_for_version_lt_2(string rootFolder, string completeDir, string categoryDir, string fullCompleteDir, string fullCategoryDir)
|
||||
{
|
||||
_fullStatus.CompleteDir = null;
|
||||
_queued.DefaultRootFolder = rootFolder;
|
||||
_config.Misc.complete_dir = completeDir;
|
||||
_config.Categories.First().Dir = categoryDir;
|
||||
|
||||
|
||||
GivenVersion("1.2.1");
|
||||
GivenQueue(null);
|
||||
|
||||
var result = Subject.GetStatus();
|
||||
|
||||
result.IsLocalhost.Should().BeTrue();
|
||||
result.OutputRootFolders.Should().NotBeNull();
|
||||
result.OutputRootFolders.First().Should().Be(expectedDir);
|
||||
result.OutputRootFolders.First().Should().Be(fullCategoryDir);
|
||||
}
|
||||
|
||||
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")]
|
||||
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")]
|
||||
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")]
|
||||
[TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed", @"/nzbget/root/completed/vv")]
|
||||
public void should_return_status_with_outputdir_for_version_gte_2(string rootFolder, string completeDir, string categoryDir, string fullCompleteDir, string fullCategoryDir)
|
||||
{
|
||||
_fullStatus.CompleteDir = fullCompleteDir;
|
||||
_queued.DefaultRootFolder = null;
|
||||
_config.Misc.complete_dir = completeDir;
|
||||
_config.Categories.First().Dir = categoryDir;
|
||||
|
||||
GivenVersion("2.0.0beta1");
|
||||
GivenQueue(null);
|
||||
|
||||
var result = Subject.GetStatus();
|
||||
|
||||
result.IsLocalhost.Should().BeTrue();
|
||||
result.OutputRootFolders.Should().NotBeNull();
|
||||
result.OutputRootFolders.First().Should().Be(fullCategoryDir);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -451,5 +495,73 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
result.IsValid.Should().BeTrue();
|
||||
result.HasWarnings.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_test_success_if_tv_sorting_disabled()
|
||||
{
|
||||
_config.Misc.enable_tv_sorting = false;
|
||||
_config.Misc.tv_categories = null;
|
||||
|
||||
var result = new NzbDroneValidationResult(Subject.Test());
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_test_failed_if_tv_sorting_null()
|
||||
{
|
||||
_config.Misc.enable_tv_sorting = true;
|
||||
_config.Misc.tv_categories = null;
|
||||
|
||||
var result = new NzbDroneValidationResult(Subject.Test());
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_test_failed_if_tv_sorting_empty()
|
||||
{
|
||||
_config.Misc.enable_tv_sorting = true;
|
||||
_config.Misc.tv_categories = new string[0];
|
||||
|
||||
var result = new NzbDroneValidationResult(Subject.Test());
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_test_success_if_tv_sorting_contains_different_category()
|
||||
{
|
||||
_config.Misc.enable_tv_sorting = true;
|
||||
_config.Misc.tv_categories = new[] { "tv-custom" };
|
||||
|
||||
var result = new NzbDroneValidationResult(Subject.Test());
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_test_failed_if_tv_sorting_contains_category()
|
||||
{
|
||||
_config.Misc.enable_tv_sorting = true;
|
||||
_config.Misc.tv_categories = new[] { "tv" };
|
||||
|
||||
var result = new NzbDroneValidationResult(Subject.Test());
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_test_failed_if_tv_sorting_default_category()
|
||||
{
|
||||
Subject.Definition.Settings.As<SabnzbdSettings>().TvCategory = null;
|
||||
|
||||
_config.Misc.enable_tv_sorting = true;
|
||||
_config.Misc.tv_categories = new[] { "Default" };
|
||||
|
||||
var result = new NzbDroneValidationResult(Subject.Test());
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4502
src/NzbDrone.Core.Test/Files/ArabicRomanNumeralDictionary.JSON
Normal file
4502
src/NzbDrone.Core.Test/Files/ArabicRomanNumeralDictionary.JSON
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,57 +1,63 @@
|
||||
{
|
||||
"status": 0,
|
||||
"data": [
|
||||
{
|
||||
"id": 257142,
|
||||
"hash": "EABC50AEF9F53CEDED84ADF14144D3368E586F3A",
|
||||
"leechers": 1,
|
||||
"seeders": 46,
|
||||
"name": "Supernatural S10E17 1080p WEB-DL DD5.1 H.264-ECI",
|
||||
"times_completed": 49,
|
||||
"size": 1718009717,
|
||||
"utadded": 1428179446,
|
||||
"added": "2015-04-04T20:30:46+0000",
|
||||
"comments": 0,
|
||||
"numfiles": 1,
|
||||
"filename": "Supernatural.S10E17.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
|
||||
"freeleech": "no",
|
||||
"type_category": 2,
|
||||
"type_codec": 1,
|
||||
"type_medium": 6,
|
||||
"type_origin": 0,
|
||||
"username": "abc",
|
||||
"owner": 1107944,
|
||||
"tvdb": {
|
||||
"id": 78901,
|
||||
"season": 10,
|
||||
"episode": 17
|
||||
}
|
||||
{
|
||||
"id": 257142,
|
||||
"hash": "EABC50AEF9F53CEDED84ADF14144D3368E586F3A",
|
||||
"leechers": 1,
|
||||
"seeders": 46,
|
||||
"name": "Supernatural S10E17 1080p WEB-DL DD5.1 H.264-ECI",
|
||||
"times_completed": 49,
|
||||
"size": 1718009717,
|
||||
"utadded": 1428179446,
|
||||
"added": "2015-04-04T20:30:46+0000",
|
||||
"comments": 0,
|
||||
"numfiles": 1,
|
||||
"filename": "Supernatural.S10E17.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
|
||||
"freeleech": "no",
|
||||
"type_category": 2,
|
||||
"type_codec": 1,
|
||||
"type_medium": 6,
|
||||
"type_origin": 0,
|
||||
"username": "abc",
|
||||
"owner": 1107944,
|
||||
"tvdb": {
|
||||
"id": 78901,
|
||||
"season": 10,
|
||||
"episode": 17
|
||||
},
|
||||
{
|
||||
"id": 257140,
|
||||
"hash": "BE3BA5396B9A30544353B55FDD89EDE46C8FB72A",
|
||||
"leechers": 0,
|
||||
"seeders": 18,
|
||||
"name": "Scandal S04E18 1080p WEB-DL DD5.1 H.264-ECI",
|
||||
"times_completed": 19,
|
||||
"size": 1789106197,
|
||||
"utadded": 1428179128,
|
||||
"added": "2015-04-04T20:25:28+0000",
|
||||
"comments": 0,
|
||||
"numfiles": 1,
|
||||
"filename": "Scandal.2012.S04E18.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
|
||||
"freeleech": "no",
|
||||
"type_category": 2,
|
||||
"type_codec": 1,
|
||||
"type_medium": 6,
|
||||
"type_origin": 0,
|
||||
"username": "abc",
|
||||
"owner": 1107944,
|
||||
"tvdb": {
|
||||
"id": 248841,
|
||||
"season": 4,
|
||||
"episode": 18
|
||||
}
|
||||
"imdb": {
|
||||
"id": 78901
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 257140,
|
||||
"hash": "BE3BA5396B9A30544353B55FDD89EDE46C8FB72A",
|
||||
"leechers": 0,
|
||||
"seeders": 18,
|
||||
"name": "Scandal S04E18 1080p WEB-DL DD5.1 H.264-ECI",
|
||||
"times_completed": 19,
|
||||
"size": 1789106197,
|
||||
"utadded": 1428179128,
|
||||
"added": "2015-04-04T20:25:28+0000",
|
||||
"comments": 0,
|
||||
"numfiles": 1,
|
||||
"filename": "Scandal.2012.S04E18.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
|
||||
"freeleech": "no",
|
||||
"type_category": 2,
|
||||
"type_codec": 1,
|
||||
"type_medium": 6,
|
||||
"type_origin": 0,
|
||||
"username": "abc",
|
||||
"owner": 1107944,
|
||||
"tvdb": {
|
||||
"id": 248841,
|
||||
"season": 4,
|
||||
"episode": 18
|
||||
},
|
||||
"imdb": {
|
||||
"id": 78901
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,57 +1,63 @@
|
||||
{
|
||||
"status": 0,
|
||||
"data": [
|
||||
{
|
||||
"id": "257142",
|
||||
"hash": "EABC50AEF9F53CEDED84ADF14144D3368E586F3A",
|
||||
"leechers": 1,
|
||||
"seeders": 46,
|
||||
"name": "Supernatural S10E17 1080p WEB-DL DD5.1 H.264-ECI",
|
||||
"times_completed": 49,
|
||||
"size": 1718009717,
|
||||
"utadded": 1428179446,
|
||||
"added": "2015-04-04T20:30:46+0000",
|
||||
"comments": 0,
|
||||
"numfiles": 1,
|
||||
"filename": "Supernatural.S10E17.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
|
||||
"freeleech": "no",
|
||||
"type_category": 2,
|
||||
"type_codec": 1,
|
||||
"type_medium": 6,
|
||||
"type_origin": 0,
|
||||
"username": "abc",
|
||||
"owner": 1107944,
|
||||
"tvdb": {
|
||||
"id": 78901,
|
||||
"season": 10,
|
||||
"episode": 17
|
||||
}
|
||||
{
|
||||
"id": "257142",
|
||||
"hash": "EABC50AEF9F53CEDED84ADF14144D3368E586F3A",
|
||||
"leechers": 1,
|
||||
"seeders": 46,
|
||||
"name": "Supernatural S10E17 1080p WEB-DL DD5.1 H.264-ECI",
|
||||
"times_completed": 49,
|
||||
"size": 1718009717,
|
||||
"utadded": 1428179446,
|
||||
"added": "2015-04-04T20:30:46+0000",
|
||||
"comments": 0,
|
||||
"numfiles": 1,
|
||||
"filename": "Supernatural.S10E17.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
|
||||
"freeleech": "no",
|
||||
"type_category": 2,
|
||||
"type_codec": 1,
|
||||
"type_medium": 6,
|
||||
"type_origin": 0,
|
||||
"username": "abc",
|
||||
"owner": 1107944,
|
||||
"tvdb": {
|
||||
"id": 78901,
|
||||
"season": 10,
|
||||
"episode": 17
|
||||
},
|
||||
{
|
||||
"id": "257140",
|
||||
"hash": "BE3BA5396B9A30544353B55FDD89EDE46C8FB72A",
|
||||
"leechers": 0,
|
||||
"seeders": 18,
|
||||
"name": "Scandal S04E18 1080p WEB-DL DD5.1 H.264-ECI",
|
||||
"times_completed": 19,
|
||||
"size": 1789106197,
|
||||
"utadded": 1428179128,
|
||||
"added": "2015-04-04T20:25:28+0000",
|
||||
"comments": 0,
|
||||
"numfiles": 1,
|
||||
"filename": "Scandal.2012.S04E18.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
|
||||
"freeleech": "no",
|
||||
"type_category": 2,
|
||||
"type_codec": 1,
|
||||
"type_medium": 6,
|
||||
"type_origin": 0,
|
||||
"username": "abc",
|
||||
"owner": 1107944,
|
||||
"tvdb": {
|
||||
"id": 248841,
|
||||
"season": 4,
|
||||
"episode": 18
|
||||
}
|
||||
"imdb": {
|
||||
"id": 78901
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "257140",
|
||||
"hash": "BE3BA5396B9A30544353B55FDD89EDE46C8FB72A",
|
||||
"leechers": 0,
|
||||
"seeders": 18,
|
||||
"name": "Scandal S04E18 1080p WEB-DL DD5.1 H.264-ECI",
|
||||
"times_completed": 19,
|
||||
"size": 1789106197,
|
||||
"utadded": 1428179128,
|
||||
"added": "2015-04-04T20:25:28+0000",
|
||||
"comments": 0,
|
||||
"numfiles": 1,
|
||||
"filename": "Scandal.2012.S04E18.1080p.WEB-DL.DD5.1.H.264-ECI.torrent",
|
||||
"freeleech": "no",
|
||||
"type_category": 2,
|
||||
"type_codec": 1,
|
||||
"type_medium": 6,
|
||||
"type_origin": 0,
|
||||
"username": "abc",
|
||||
"owner": 1107944,
|
||||
"tvdb": {
|
||||
"id": 248841,
|
||||
"season": 4,
|
||||
"episode": 18
|
||||
},
|
||||
"imdb": {
|
||||
"id": 78901
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,12 +11,12 @@ using NzbDrone.Core.Qualities;
|
||||
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
{
|
||||
[TestFixture]
|
||||
public class CleanupOrphanedEpisodeFilesFixture : DbTest<CleanupOrphanedEpisodeFiles, EpisodeFile>
|
||||
public class CleanupOrphanedEpisodeFilesFixture : DbTest<CleanupOrphanedEpisodeFiles, MovieFile>
|
||||
{
|
||||
[Test]
|
||||
public void should_delete_orphaned_episode_files()
|
||||
{
|
||||
var episodeFile = Builder<EpisodeFile>.CreateNew()
|
||||
var episodeFile = Builder<MovieFile>.CreateNew()
|
||||
.With(h => h.Quality = new QualityModel())
|
||||
.BuildNew();
|
||||
|
||||
@@ -28,22 +28,22 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
[Test]
|
||||
public void should_not_delete_unorphaned_episode_files()
|
||||
{
|
||||
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(2)
|
||||
var episodeFiles = Builder<MovieFile>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(h => h.Quality = new QualityModel())
|
||||
.BuildListOfNew();
|
||||
|
||||
Db.InsertMany(episodeFiles);
|
||||
|
||||
var episode = Builder<Episode>.CreateNew()
|
||||
.With(e => e.EpisodeFileId = episodeFiles.First().Id)
|
||||
var episode = Builder<Movie>.CreateNew()
|
||||
.With(e => e.MovieFileId = episodeFiles.First().Id)
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(episode);
|
||||
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().HaveCount(1);
|
||||
Db.All<Episode>().Should().Contain(e => e.EpisodeFileId == AllStoredModels.First().Id);
|
||||
Db.All<Movie>().Should().Contain(e => e.MovieFileId == AllStoredModels.First().Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using FluentAssertions;
|
||||
using System;
|
||||
using System.Xml;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
{
|
||||
@@ -64,5 +67,35 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
caps.DefaultPageSize.Should().Be(100);
|
||||
caps.MaxPageSize.Should().Be(100);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_failed_to_get()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Get(It.IsAny<HttpRequest>()))
|
||||
.Throws<Exception>();
|
||||
|
||||
Assert.Throws<Exception>(() => Subject.GetCapabilities(_settings));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_xml_invalid()
|
||||
{
|
||||
GivenCapsResponse(_caps.Replace("<limits", "<>"));
|
||||
|
||||
Assert.Throws<XmlException>(() => Subject.GetCapabilities(_settings));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_on_xml_data_unexpected()
|
||||
{
|
||||
GivenCapsResponse(_caps.Replace("5030", "asdf"));
|
||||
|
||||
var result = Subject.GetCapabilities(_settings);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +297,7 @@
|
||||
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
|
||||
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
|
||||
<Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" />
|
||||
<Compile Include="ParserTests\RomanNumeralTests\RomanNumeralConversionFixture.cs" />
|
||||
<Compile Include="Qualities\RevisionComparableFixture.cs" />
|
||||
<Compile Include="QueueTests\QueueServiceFixture.cs" />
|
||||
<Compile Include="RemotePathMappingsTests\RemotePathMappingServiceFixture.cs" />
|
||||
@@ -385,6 +386,9 @@
|
||||
<Compile Include="UpdateTests\UpdateServiceFixture.cs" />
|
||||
<Compile Include="XbmcVersionTests.cs" />
|
||||
<Compile Include="BulkImport\AddMultiMoviesFixture.cs" />
|
||||
<Content Include="Files\ArabicRomanNumeralDictionary.JSON">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Parser.RomanNumerals;
|
||||
|
||||
namespace NzbDrone.Core.Test.ParserTests.RomanNumeralTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class RomanNumeralConversionFixture
|
||||
{
|
||||
private const string TEST_VALUES = @"Files/ArabicRomanNumeralDictionary.JSON";
|
||||
|
||||
|
||||
private Dictionary<int, string> _arabicToRomanNumeralsMapping;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void PopulateDictionaryWithProvenValues()
|
||||
{
|
||||
var pathToTestValues = Path.Combine(TestContext.CurrentContext.TestDirectory, Path.Combine(TEST_VALUES.Split('/')));
|
||||
_arabicToRomanNumeralsMapping =
|
||||
JsonConvert.DeserializeObject<Dictionary<int, string>>(File.ReadAllText(pathToTestValues));
|
||||
}
|
||||
|
||||
|
||||
[Test(Description = "Converts the supported range [1-3999] of Arabic to Roman numerals.")]
|
||||
[Order(0)]
|
||||
public void should_convert_arabic_numeral_to_roman_numeral([Range(1,3999)] int arabicNumeral)
|
||||
{
|
||||
RomanNumeral romanNumeral = new RomanNumeral(arabicNumeral);
|
||||
|
||||
string expectedValue = _arabicToRomanNumeralsMapping[arabicNumeral];
|
||||
|
||||
Assert.AreEqual(romanNumeral.ToRomanNumeral(), expectedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Order(1)]
|
||||
public void should_convert_roman_numeral_to_arabic_numeral([Range(1, 3999)] int arabicNumeral)
|
||||
{
|
||||
RomanNumeral romanNumeral = new RomanNumeral(_arabicToRomanNumeralsMapping[arabicNumeral]);
|
||||
|
||||
int expectecdValue = arabicNumeral;
|
||||
|
||||
Assert.AreEqual(romanNumeral.ToInt(), expectecdValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -190,6 +190,20 @@ namespace NzbDrone.Core.Configuration
|
||||
set { SetValue("EnableCompletedDownloadHandling", value); }
|
||||
}
|
||||
|
||||
public bool AllowHardcodedSubs
|
||||
{
|
||||
get { return GetValueBoolean("AllowHardcodedSubs", false); }
|
||||
|
||||
set { SetValue("AllowHardcodedSubs", value); }
|
||||
}
|
||||
|
||||
public string WhitelistedHardcodedSubs
|
||||
{
|
||||
get { return GetValue("WhitelistedHardcodedSubs", ""); }
|
||||
|
||||
set { SetValue("WhitelistedHardcodedSubs", value); }
|
||||
}
|
||||
|
||||
public bool RemoveCompletedDownloads
|
||||
{
|
||||
get { return GetValueBoolean("RemoveCompletedDownloads", false); }
|
||||
|
||||
@@ -48,6 +48,9 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
int AvailabilityDelay { get; set; }
|
||||
|
||||
bool AllowHardcodedSubs { get; set; }
|
||||
string WhitelistedHardcodedSubs { get; set; }
|
||||
|
||||
int NetImportSyncInterval { get; set; }
|
||||
string ListSyncLevel { get; set; }
|
||||
string ImportExclusions { get; set; }
|
||||
|
||||
@@ -159,6 +159,7 @@ namespace NzbDrone.Core.Datastore
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedMovieInfo), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<int>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter());
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -21,12 +22,14 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications, IParsingService parsingService, Logger logger)
|
||||
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications, IParsingService parsingService, IConfigService configService, Logger logger)
|
||||
{
|
||||
_specifications = specifications;
|
||||
_parsingService = parsingService;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -68,30 +71,50 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title);
|
||||
|
||||
if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
|
||||
{
|
||||
RemoteMovie remoteMovie = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
|
||||
remoteMovie.Release = report;
|
||||
if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
|
||||
{
|
||||
RemoteMovie remoteMovie = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
|
||||
remoteMovie.Release = report;
|
||||
|
||||
if (remoteMovie.Movie == null)
|
||||
{
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection("Unknown movie. Cannot parse release name."));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
remoteMovie.DownloadAllowed = true;
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection("Hardcoded subs found: " + parsedMovieInfo.Quality.HardcodedSubs));
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteMovie.DownloadAllowed = true;
|
||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (remoteMovie.Movie == null)
|
||||
{
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection("Unknown movie. Movie found does not match wanted movie."));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
remoteMovie.DownloadAllowed = true;
|
||||
if (_configService.AllowHardcodedSubs)
|
||||
{
|
||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||
}
|
||||
else
|
||||
{
|
||||
var whitelisted = _configService.WhitelistedHardcodedSubs.Split(',');
|
||||
_logger.Debug("Testing: {0}", whitelisted);
|
||||
if (whitelisted != null && whitelisted.Any(t => (parsedMovieInfo.Quality.HardcodedSubs.ToLower().Contains(t.ToLower()) && t.IsNotNullOrWhiteSpace())))
|
||||
{
|
||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection("Hardcoded subs found: " + parsedMovieInfo.Quality.HardcodedSubs));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteMovie.DownloadAllowed = true;
|
||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Trace("{0} could not be parsed :(.", report.Title);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
{
|
||||
public class DiskStationApiInfo
|
||||
{
|
||||
{
|
||||
private string _path;
|
||||
|
||||
public int MaxVersion { get; set; }
|
||||
|
||||
public int MinVersion { get; set; }
|
||||
|
||||
public DiskStationApi Type { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool NeedsAuthentication { get; set; }
|
||||
|
||||
public string Path
|
||||
{
|
||||
get { return _path; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||
|
||||
@@ -13,20 +14,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
|
||||
public class DSMInfoProxy : DiskStationProxyBase, IDSMInfoProxy
|
||||
{
|
||||
public DSMInfoProxy(IHttpClient httpClient, Logger logger) :
|
||||
base(httpClient, logger)
|
||||
public DSMInfoProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) :
|
||||
base(DiskStationApi.DSMInfo, "SYNO.DSM.Info", httpClient, cacheManager, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public string GetSerialNumber(DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>() {
|
||||
{ "api", "SYNO.DSM.Info" },
|
||||
{ "version", "2" },
|
||||
{ "method", "getinfo" }
|
||||
};
|
||||
var info = GetApiInfo(settings);
|
||||
|
||||
var requestBuilder = BuildRequest(settings, "getinfo", info.MinVersion);
|
||||
|
||||
var response = ProcessRequest<DSMInfoResponse>(requestBuilder, "get serial number", settings);
|
||||
|
||||
var response = ProcessRequest<DSMInfoResponse>(DiskStationApi.DSMInfo, arguments, settings, "get serial number");
|
||||
return response.Data.SerialNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
{
|
||||
public abstract class DiskStationProxyBase
|
||||
public interface IDiskStationProxy
|
||||
{
|
||||
private static readonly Dictionary<DiskStationApi, string> Resources;
|
||||
DiskStationApiInfo GetApiInfo(DownloadStationSettings settings);
|
||||
}
|
||||
|
||||
public abstract class DiskStationProxyBase : IDiskStationProxy
|
||||
{
|
||||
protected readonly Logger _logger;
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
protected readonly Logger _logger;
|
||||
private bool _authenticated;
|
||||
private readonly ICached<DiskStationApiInfo> _infoCache;
|
||||
private readonly ICached<string> _sessionCache;
|
||||
private readonly DiskStationApi _apiType;
|
||||
private readonly string _apiName;
|
||||
|
||||
private static readonly DiskStationApiInfo _apiInfo;
|
||||
|
||||
static DiskStationProxyBase()
|
||||
{
|
||||
Resources = new Dictionary<DiskStationApi, string>
|
||||
_apiInfo = new DiskStationApiInfo()
|
||||
{
|
||||
{ DiskStationApi.Info, "query.cgi" }
|
||||
Type = DiskStationApi.Info,
|
||||
Name = "SYNO.API.Info",
|
||||
Path = "query.cgi",
|
||||
MaxVersion = 1,
|
||||
MinVersion = 1,
|
||||
NeedsAuthentication = false
|
||||
};
|
||||
}
|
||||
|
||||
public DiskStationProxyBase(IHttpClient httpClient, Logger logger)
|
||||
public DiskStationProxyBase(DiskStationApi apiType,
|
||||
string apiName,
|
||||
IHttpClient httpClient,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_infoCache = cacheManager.GetCache<DiskStationApiInfo>(typeof(DiskStationProxyBase), "apiInfo");
|
||||
_sessionCache = cacheManager.GetCache<string>(typeof(DiskStationProxyBase), "sessions");
|
||||
_apiType = apiType;
|
||||
_apiName = apiName;
|
||||
}
|
||||
|
||||
|
||||
protected DiskStationResponse<object> ProcessRequest(DiskStationApi api,
|
||||
Dictionary<string, object> arguments,
|
||||
DownloadStationSettings settings,
|
||||
string operation,
|
||||
HttpMethod method = HttpMethod.GET)
|
||||
private string GenerateSessionCacheKey(DownloadStationSettings settings)
|
||||
{
|
||||
return ProcessRequest<object>(api, arguments, settings, operation, method);
|
||||
return $"{settings.Username}@{settings.Host}:{settings.Port}";
|
||||
}
|
||||
|
||||
protected DiskStationResponse<T> ProcessRequest<T>(DiskStationApi api,
|
||||
Dictionary<string, object> arguments,
|
||||
DownloadStationSettings settings,
|
||||
string operation,
|
||||
HttpMethod method = HttpMethod.GET,
|
||||
int retries = 0) where T : new()
|
||||
protected DiskStationResponse<T> ProcessRequest<T>(HttpRequestBuilder requestBuilder,
|
||||
string operation,
|
||||
DownloadStationSettings settings) where T : new()
|
||||
{
|
||||
if (retries == 5)
|
||||
{
|
||||
throw new DownloadClientException("Try to process request to {0} with {1} more than 5 times", api, arguments.ToJson().ToString());
|
||||
}
|
||||
return ProcessRequest<T>(requestBuilder, operation, _apiType, settings);
|
||||
}
|
||||
|
||||
if (!_authenticated && api != DiskStationApi.Info && api != DiskStationApi.DSMInfo)
|
||||
{
|
||||
AuthenticateClient(settings);
|
||||
}
|
||||
|
||||
var request = BuildRequest(settings, api, arguments, method);
|
||||
private DiskStationResponse<T> ProcessRequest<T>(HttpRequestBuilder requestBuilder,
|
||||
string operation,
|
||||
DiskStationApi api,
|
||||
DownloadStationSettings settings) where T : new()
|
||||
{
|
||||
var request = requestBuilder.Build();
|
||||
var response = _httpClient.Execute(request);
|
||||
|
||||
_logger.Debug("Trying to {0}", operation);
|
||||
@@ -77,16 +91,14 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
|
||||
if (responseContent.Error.SessionError)
|
||||
{
|
||||
_authenticated = false;
|
||||
_sessionCache.Remove(GenerateSessionCacheKey(settings));
|
||||
|
||||
if (responseContent.Error.Code == 105)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException(msg);
|
||||
}
|
||||
|
||||
return ProcessRequest<T>(api, arguments, settings, operation, method, ++retries);
|
||||
}
|
||||
|
||||
|
||||
throw new DownloadClientException(msg);
|
||||
}
|
||||
}
|
||||
@@ -96,124 +108,126 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
}
|
||||
}
|
||||
|
||||
private void AuthenticateClient(DownloadStationSettings settings)
|
||||
private string AuthenticateClient(DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "api", "SYNO.API.Auth" },
|
||||
{ "version", "1" },
|
||||
{ "method", "login" },
|
||||
{ "account", settings.Username },
|
||||
{ "passwd", settings.Password },
|
||||
{ "format", "cookie" },
|
||||
{ "session", "DownloadStation" },
|
||||
};
|
||||
var authInfo = GetApiInfo(DiskStationApi.Auth, settings);
|
||||
|
||||
var authLoginRequest = BuildRequest(settings, DiskStationApi.Auth, arguments, HttpMethod.GET);
|
||||
authLoginRequest.StoreResponseCookie = true;
|
||||
var requestBuilder = BuildRequest(settings, authInfo, "login", 2);
|
||||
requestBuilder.AddQueryParam("account", settings.Username);
|
||||
requestBuilder.AddQueryParam("passwd", settings.Password);
|
||||
requestBuilder.AddQueryParam("format", "sid");
|
||||
requestBuilder.AddQueryParam("session", "DownloadStation");
|
||||
|
||||
var response = _httpClient.Execute(authLoginRequest);
|
||||
var authResponse = ProcessRequest<DiskStationAuthResponse>(requestBuilder, "login", DiskStationApi.Auth, settings);
|
||||
|
||||
var downloadStationResponse = Json.Deserialize<DiskStationResponse<DiskStationAuthResponse>>(response.Content);
|
||||
|
||||
var authResponse = Json.Deserialize<DiskStationResponse<DiskStationAuthResponse>>(response.Content);
|
||||
|
||||
_authenticated = authResponse.Success;
|
||||
|
||||
if (!_authenticated)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException(downloadStationResponse.Error.GetMessage(DiskStationApi.Auth));
|
||||
}
|
||||
return authResponse.Data.SId;
|
||||
}
|
||||
|
||||
private HttpRequest BuildRequest(DownloadStationSettings settings, DiskStationApi api, Dictionary<string, object> arguments, HttpMethod method)
|
||||
protected HttpRequestBuilder BuildRequest(DownloadStationSettings settings, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET)
|
||||
{
|
||||
if (!Resources.ContainsKey(api))
|
||||
{
|
||||
GetApiVersion(settings, api);
|
||||
}
|
||||
var info = GetApiInfo(_apiType, settings);
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{Resources[api]}");
|
||||
requestBuilder.Method = method;
|
||||
return BuildRequest(settings, info, methodName, apiVersion, httpVerb);
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(DownloadStationSettings settings, DiskStationApiInfo apiInfo, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{apiInfo.Path}");
|
||||
requestBuilder.Method = httpVerb;
|
||||
requestBuilder.LogResponseContent = true;
|
||||
requestBuilder.SuppressHttpError = true;
|
||||
requestBuilder.AllowAutoRedirect = false;
|
||||
requestBuilder.Headers.ContentType = "application/json";
|
||||
|
||||
if (requestBuilder.Method == HttpMethod.POST)
|
||||
if (apiVersion < apiInfo.MinVersion || apiVersion > apiInfo.MaxVersion)
|
||||
{
|
||||
if (api == DiskStationApi.DownloadStationTask && arguments.ContainsKey("file"))
|
||||
{
|
||||
requestBuilder.Headers.ContentType = "multipart/form-data";
|
||||
throw new ArgumentOutOfRangeException(nameof(apiVersion));
|
||||
}
|
||||
|
||||
foreach (var arg in arguments)
|
||||
{
|
||||
if (arg.Key == "file")
|
||||
{
|
||||
Dictionary<string, object> file = (Dictionary<string, object>)arg.Value;
|
||||
requestBuilder.AddFormUpload(arg.Key, file["name"].ToString(), (byte[])file["data"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestBuilder.AddFormParameter(arg.Key, arg.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (httpVerb == HttpMethod.POST)
|
||||
{
|
||||
if (apiInfo.NeedsAuthentication)
|
||||
{
|
||||
requestBuilder.Headers.ContentType = "application/json";
|
||||
requestBuilder.AddFormParameter("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6)));
|
||||
}
|
||||
|
||||
requestBuilder.AddFormParameter("api", apiInfo.Name);
|
||||
requestBuilder.AddFormParameter("version", apiVersion);
|
||||
requestBuilder.AddFormParameter("method", methodName);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
if (apiInfo.NeedsAuthentication)
|
||||
{
|
||||
requestBuilder.AddQueryParam(arg.Key, arg.Value);
|
||||
requestBuilder.AddQueryParam("_sid", _sessionCache.Get(GenerateSessionCacheKey(settings), () => AuthenticateClient(settings), TimeSpan.FromHours(6)));
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("api", apiInfo.Name);
|
||||
requestBuilder.AddQueryParam("version", apiVersion);
|
||||
requestBuilder.AddQueryParam("method", methodName);
|
||||
}
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private string GenerateInfoCacheKey(DownloadStationSettings settings, DiskStationApi api)
|
||||
{
|
||||
return $"{settings.Host}:{settings.Port}->{api}";
|
||||
}
|
||||
|
||||
private void UpdateApiInfo(DownloadStationSettings settings)
|
||||
{
|
||||
var apis = new Dictionary<string, DiskStationApi>()
|
||||
{
|
||||
{ "SYNO.API.Auth", DiskStationApi.Auth },
|
||||
{ _apiName, _apiType }
|
||||
};
|
||||
|
||||
var requestBuilder = BuildRequest(settings, _apiInfo, "query", _apiInfo.MinVersion);
|
||||
requestBuilder.AddQueryParam("query", string.Join(",", apis.Keys));
|
||||
|
||||
var infoResponse = ProcessRequest<DiskStationApiInfoResponse>(requestBuilder, "get api info", _apiInfo.Type, settings);
|
||||
|
||||
foreach (var data in infoResponse.Data)
|
||||
{
|
||||
if (apis.ContainsKey(data.Key))
|
||||
{
|
||||
data.Value.Name = data.Key;
|
||||
data.Value.Type = apis[data.Key];
|
||||
data.Value.NeedsAuthentication = apis[data.Key] != DiskStationApi.Auth;
|
||||
|
||||
_infoCache.Set(GenerateInfoCacheKey(settings, apis[data.Key]), data.Value, TimeSpan.FromHours(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DiskStationApiInfo GetApiInfo(DiskStationApi api, DownloadStationSettings settings)
|
||||
{
|
||||
if (api == DiskStationApi.Info)
|
||||
{
|
||||
return _apiInfo;
|
||||
}
|
||||
|
||||
var key = GenerateInfoCacheKey(settings, api);
|
||||
var info = _infoCache.Find(key);
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
UpdateApiInfo(settings);
|
||||
info = _infoCache.Find(key);
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
throw new DownloadClientException("Info of {0} not found on {1}:{2}", api, settings.Host, settings.Port);
|
||||
}
|
||||
}
|
||||
|
||||
return requestBuilder.Build();
|
||||
return info;
|
||||
}
|
||||
|
||||
protected IEnumerable<int> GetApiVersion(DownloadStationSettings settings, DiskStationApi api)
|
||||
public DiskStationApiInfo GetApiInfo(DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "api", "SYNO.API.Info" },
|
||||
{ "version", "1" },
|
||||
{ "method", "query" },
|
||||
{ "query", "SYNO.API.Auth, SYNO.DownloadStation.Info, SYNO.DownloadStation.Task, SYNO.FileStation.List, SYNO.DSM.Info" },
|
||||
};
|
||||
|
||||
var infoResponse = ProcessRequest<DiskStationApiInfoResponse>(DiskStationApi.Info, arguments, settings, "Get api version");
|
||||
|
||||
//TODO: Refactor this into more elegant code
|
||||
var infoResponeDSAuth = infoResponse.Data["SYNO.API.Auth"];
|
||||
var infoResponeDSInfo = infoResponse.Data["SYNO.DownloadStation.Info"];
|
||||
var infoResponeDSTask = infoResponse.Data["SYNO.DownloadStation.Task"];
|
||||
var infoResponseFSList = infoResponse.Data["SYNO.FileStation.List"];
|
||||
var infoResponseDSMInfo = infoResponse.Data["SYNO.DSM.Info"];
|
||||
|
||||
Resources[DiskStationApi.Auth] = infoResponeDSAuth.Path;
|
||||
Resources[DiskStationApi.DownloadStationInfo] = infoResponeDSInfo.Path;
|
||||
Resources[DiskStationApi.DownloadStationTask] = infoResponeDSTask.Path;
|
||||
Resources[DiskStationApi.FileStationList] = infoResponseFSList.Path;
|
||||
Resources[DiskStationApi.DSMInfo] = infoResponseDSMInfo.Path;
|
||||
|
||||
switch (api)
|
||||
{
|
||||
case DiskStationApi.Auth:
|
||||
return Enumerable.Range(infoResponeDSAuth.MinVersion, infoResponeDSAuth.MaxVersion - infoResponeDSAuth.MinVersion + 1);
|
||||
case DiskStationApi.DownloadStationInfo:
|
||||
return Enumerable.Range(infoResponeDSInfo.MinVersion, infoResponeDSInfo.MaxVersion - infoResponeDSInfo.MinVersion + 1);
|
||||
case DiskStationApi.DownloadStationTask:
|
||||
return Enumerable.Range(infoResponeDSTask.MinVersion, infoResponeDSTask.MaxVersion - infoResponeDSTask.MinVersion + 1);
|
||||
case DiskStationApi.FileStationList:
|
||||
return Enumerable.Range(infoResponseFSList.MinVersion, infoResponseFSList.MaxVersion - infoResponseFSList.MinVersion + 1);
|
||||
case DiskStationApi.DSMInfo:
|
||||
return Enumerable.Range(infoResponseDSMInfo.MinVersion, infoResponseDSMInfo.MaxVersion - infoResponseDSMInfo.MinVersion + 1);
|
||||
default:
|
||||
throw new DownloadClientException("Api not implemented");
|
||||
}
|
||||
return GetApiInfo(_apiType, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Cache;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
{
|
||||
public interface IDownloadStationInfoProxy : IDiskStationProxy
|
||||
{
|
||||
Dictionary<string, object> GetConfig(DownloadStationSettings settings);
|
||||
}
|
||||
|
||||
public class DownloadStationInfoProxy : DiskStationProxyBase, IDownloadStationInfoProxy
|
||||
{
|
||||
public DownloadStationInfoProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger) :
|
||||
base(DiskStationApi.DownloadStationInfo, "SYNO.DownloadStation.Info", httpClient, cacheManager, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetConfig(DownloadStationSettings settings)
|
||||
{
|
||||
var requestBuilder = BuildRequest(settings, "getConfig", 1);
|
||||
|
||||
var response = ProcessRequest<Dictionary<string, object>>(requestBuilder, "get config", settings);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
{
|
||||
public interface IDownloadStationProxy
|
||||
{
|
||||
IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings);
|
||||
Dictionary<string, object> GetConfig(DownloadStationSettings settings);
|
||||
void RemoveTask(string downloadId, DownloadStationSettings settings);
|
||||
void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings);
|
||||
void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings);
|
||||
IEnumerable<int> GetApiVersion(DownloadStationSettings settings);
|
||||
}
|
||||
|
||||
public class DownloadStationProxy : DiskStationProxyBase, IDownloadStationProxy
|
||||
{
|
||||
public DownloadStationProxy(IHttpClient httpClient, Logger logger)
|
||||
: base(httpClient, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "api", "SYNO.DownloadStation.Task" },
|
||||
{ "version", "2" },
|
||||
{ "method", "create" }
|
||||
};
|
||||
|
||||
if (downloadDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
arguments.Add("destination", downloadDirectory);
|
||||
}
|
||||
|
||||
arguments.Add("file", new Dictionary<string, object>() { { "name", filename }, { "data", data } });
|
||||
|
||||
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from data {filename}", HttpMethod.POST);
|
||||
}
|
||||
|
||||
public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "api", "SYNO.DownloadStation.Task" },
|
||||
{ "version", "3" },
|
||||
{ "method", "create" },
|
||||
{ "uri", url }
|
||||
};
|
||||
|
||||
if (downloadDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
arguments.Add("destination", downloadDirectory);
|
||||
}
|
||||
|
||||
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from url {url}");
|
||||
}
|
||||
|
||||
public IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "api", "SYNO.DownloadStation.Task" },
|
||||
{ "version", "1" },
|
||||
{ "method", "list" },
|
||||
{ "additional", "detail,transfer" }
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = ProcessRequest<DownloadStationTaskInfoResponse>(DiskStationApi.DownloadStationTask, arguments, settings, "get tasks");
|
||||
|
||||
return response.Data.Tasks;
|
||||
}
|
||||
catch (DownloadClientException e)
|
||||
{
|
||||
_logger.Error(e);
|
||||
return new List<DownloadStationTask>();
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetConfig(DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "api", "SYNO.DownloadStation.Info" },
|
||||
{ "version", "1" },
|
||||
{ "method", "getconfig" }
|
||||
};
|
||||
|
||||
var response = ProcessRequest<Dictionary<string, object>>(DiskStationApi.DownloadStationInfo, arguments, settings, "get config");
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public void RemoveTask(string downloadId, DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "api", "SYNO.DownloadStation.Task" },
|
||||
{ "version", "1" },
|
||||
{ "method", "delete" },
|
||||
{ "id", downloadId },
|
||||
{ "force_complete", false }
|
||||
};
|
||||
|
||||
var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"remove item {downloadId}");
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetApiVersion(DownloadStationSettings settings)
|
||||
{
|
||||
return base.GetApiVersion(settings, DiskStationApi.DownloadStationInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
{
|
||||
public interface IDownloadStationTaskProxy : IDiskStationProxy
|
||||
{
|
||||
IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings);
|
||||
void RemoveTask(string downloadId, DownloadStationSettings settings);
|
||||
void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings);
|
||||
void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings);
|
||||
}
|
||||
|
||||
public class DownloadStationTaskProxy : DiskStationProxyBase, IDownloadStationTaskProxy
|
||||
{
|
||||
public DownloadStationTaskProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger)
|
||||
: base(DiskStationApi.DownloadStationTask, "SYNO.DownloadStation.Task", httpClient, cacheManager, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings)
|
||||
{
|
||||
var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.POST);
|
||||
|
||||
if (downloadDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.AddFormParameter("destination", downloadDirectory);
|
||||
}
|
||||
|
||||
requestBuilder.AddFormUpload("file", filename, data);
|
||||
|
||||
var response = ProcessRequest<object>(requestBuilder, $"add task from data {filename}", settings);
|
||||
}
|
||||
|
||||
public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings)
|
||||
{
|
||||
var requestBuilder = BuildRequest(settings, "create", 3);
|
||||
requestBuilder.AddQueryParam("uri", url);
|
||||
|
||||
if (downloadDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.AddQueryParam("destination", downloadDirectory);
|
||||
}
|
||||
|
||||
var response = ProcessRequest<object>(requestBuilder, $"add task from url {url}", settings);
|
||||
}
|
||||
|
||||
public IEnumerable<DownloadStationTask> GetTasks(DownloadStationSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestBuilder = BuildRequest(settings, "list", 1);
|
||||
requestBuilder.AddQueryParam("additional", "detail,transfer");
|
||||
|
||||
var response = ProcessRequest<DownloadStationTaskInfoResponse>(requestBuilder, "get tasks", settings);
|
||||
|
||||
return response.Data.Tasks;
|
||||
}
|
||||
catch (DownloadClientException e)
|
||||
{
|
||||
_logger.Error(e);
|
||||
return new List<DownloadStationTask>();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTask(string downloadId, DownloadStationSettings settings)
|
||||
{
|
||||
var requestBuilder = BuildRequest(settings, "delete", 1);
|
||||
requestBuilder.AddQueryParam("id", downloadId);
|
||||
requestBuilder.AddQueryParam("force_complete", false);
|
||||
|
||||
var response = ProcessRequest<object>(requestBuilder, $"remove item {downloadId}", settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Download.Clients.DownloadStation.Responses;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
{
|
||||
public interface IFileStationProxy
|
||||
public interface IFileStationProxy : IDiskStationProxy
|
||||
{
|
||||
SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings);
|
||||
IEnumerable<int> GetApiVersion(DownloadStationSettings settings);
|
||||
|
||||
FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings);
|
||||
}
|
||||
|
||||
public class FileStationProxy : DiskStationProxyBase, IFileStationProxy
|
||||
{
|
||||
public FileStationProxy(IHttpClient httpClient, Logger logger)
|
||||
: base(httpClient, logger)
|
||||
public FileStationProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger)
|
||||
: base(DiskStationApi.FileStationList, "SYNO.FileStation.List", httpClient, cacheManager, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetApiVersion(DownloadStationSettings settings)
|
||||
{
|
||||
return base.GetApiVersion(settings, DiskStationApi.FileStationList);
|
||||
}
|
||||
|
||||
|
||||
public SharedFolderMapping GetSharedFolderMapping(string sharedFolder, DownloadStationSettings settings)
|
||||
{
|
||||
var info = GetInfoFileOrDirectory(sharedFolder, settings);
|
||||
@@ -38,16 +34,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
|
||||
public FileStationListFileInfoResponse GetInfoFileOrDirectory(string path, DownloadStationSettings settings)
|
||||
{
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "api", "SYNO.FileStation.List" },
|
||||
{ "version", "2" },
|
||||
{ "method", "getinfo" },
|
||||
{ "path", new [] { path }.ToJson() },
|
||||
{ "additional", $"[\"real_path\"]" }
|
||||
};
|
||||
var requestBuilder = BuildRequest(settings, "getinfo", 2);
|
||||
requestBuilder.AddQueryParam("path", new[] { path }.ToJson());
|
||||
requestBuilder.AddQueryParam("additional", "[\"real_path\"]");
|
||||
|
||||
var response = ProcessRequest<FileStationListResponse>(DiskStationApi.FileStationList, arguments, settings, $"get info of {path}");
|
||||
var response = ProcessRequest<FileStationListResponse>(requestBuilder, $"get info of {path}", settings);
|
||||
|
||||
return response.Data.Files.First();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -20,7 +19,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
{
|
||||
public class TorrentDownloadStation : TorrentClientBase<DownloadStationSettings>
|
||||
{
|
||||
protected readonly IDownloadStationProxy _proxy;
|
||||
protected readonly IDownloadStationInfoProxy _dsInfoProxy;
|
||||
protected readonly IDownloadStationTaskProxy _dsTaskProxy;
|
||||
protected readonly ISharedFolderResolver _sharedFolderResolver;
|
||||
protected readonly ISerialNumberProvider _serialNumberProvider;
|
||||
protected readonly IFileStationProxy _fileStationProxy;
|
||||
@@ -28,7 +28,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
public TorrentDownloadStation(ISharedFolderResolver sharedFolderResolver,
|
||||
ISerialNumberProvider serialNumberProvider,
|
||||
IFileStationProxy fileStationProxy,
|
||||
IDownloadStationProxy proxy,
|
||||
IDownloadStationInfoProxy dsInfoProxy,
|
||||
IDownloadStationTaskProxy dsTaskProxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
@@ -37,7 +38,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_dsInfoProxy = dsInfoProxy;
|
||||
_dsTaskProxy = dsTaskProxy;
|
||||
_fileStationProxy = fileStationProxy;
|
||||
_sharedFolderResolver = sharedFolderResolver;
|
||||
_serialNumberProvider = serialNumberProvider;
|
||||
@@ -47,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
|
||||
protected IEnumerable<DownloadStationTask> GetTasks()
|
||||
{
|
||||
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.BT.ToString().ToLower());
|
||||
return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.BT.ToString().ToLower());
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
@@ -129,7 +131,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
DeleteItemData(downloadId);
|
||||
}
|
||||
|
||||
_proxy.RemoveTask(ParseDownloadId(downloadId), Settings);
|
||||
_dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
|
||||
_logger.Debug("{0} removed correctly", downloadId);
|
||||
}
|
||||
|
||||
@@ -158,7 +160,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
{
|
||||
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
|
||||
|
||||
_proxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings);
|
||||
_dsTaskProxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings);
|
||||
|
||||
var item = GetTasks().SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink);
|
||||
|
||||
@@ -177,7 +179,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
{
|
||||
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
|
||||
|
||||
_proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings);
|
||||
_dsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings);
|
||||
|
||||
var items = GetTasks().Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename));
|
||||
|
||||
@@ -368,13 +370,13 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
|
||||
protected ValidationFailure ValidateVersion()
|
||||
{
|
||||
var versionRange = _proxy.GetApiVersion(Settings);
|
||||
var info = _dsTaskProxy.GetApiInfo(Settings);
|
||||
|
||||
_logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max());
|
||||
_logger.Debug("Download Station api version information: Min {0} - Max {1}", info.MinVersion, info.MaxVersion);
|
||||
|
||||
if (!versionRange.Contains(2))
|
||||
if (info.MinVersion > 2 || info.MaxVersion < 2)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {versionRange.Min()} to {versionRange.Max()}");
|
||||
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}");
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -405,7 +407,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
|
||||
protected string GetDefaultDir()
|
||||
{
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
var config = _dsInfoProxy.GetConfig(Settings);
|
||||
|
||||
var path = config["default_destination"] as string;
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
{
|
||||
public class UsenetDownloadStation : UsenetClientBase<DownloadStationSettings>
|
||||
{
|
||||
protected readonly IDownloadStationProxy _proxy;
|
||||
protected readonly IDownloadStationInfoProxy _dsInfoProxy;
|
||||
protected readonly IDownloadStationTaskProxy _dsTaskProxy;
|
||||
protected readonly ISharedFolderResolver _sharedFolderResolver;
|
||||
protected readonly ISerialNumberProvider _serialNumberProvider;
|
||||
protected readonly IFileStationProxy _fileStationProxy;
|
||||
@@ -25,7 +26,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
public UsenetDownloadStation(ISharedFolderResolver sharedFolderResolver,
|
||||
ISerialNumberProvider serialNumberProvider,
|
||||
IFileStationProxy fileStationProxy,
|
||||
IDownloadStationProxy proxy,
|
||||
IDownloadStationInfoProxy dsInfoProxy,
|
||||
IDownloadStationTaskProxy dsTaskProxy,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
@@ -34,7 +36,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
)
|
||||
: base(httpClient, configService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_dsInfoProxy = dsInfoProxy;
|
||||
_dsTaskProxy = dsTaskProxy;
|
||||
_fileStationProxy = fileStationProxy;
|
||||
_sharedFolderResolver = sharedFolderResolver;
|
||||
_serialNumberProvider = serialNumberProvider;
|
||||
@@ -44,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
|
||||
protected IEnumerable<DownloadStationTask> GetTasks()
|
||||
{
|
||||
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.NZB.ToString().ToLower());
|
||||
return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.NZB.ToString().ToLower());
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
@@ -153,7 +156,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
DeleteItemData(downloadId);
|
||||
}
|
||||
|
||||
_proxy.RemoveTask(ParseDownloadId(downloadId), Settings);
|
||||
_dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
|
||||
_logger.Debug("{0} removed correctly", downloadId);
|
||||
}
|
||||
|
||||
@@ -166,7 +169,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
{
|
||||
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
|
||||
|
||||
_proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings);
|
||||
_dsTaskProxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings);
|
||||
|
||||
var items = GetTasks().Where(t => t.Additional.Detail["uri"] == filename);
|
||||
|
||||
@@ -281,13 +284,13 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
|
||||
protected ValidationFailure ValidateVersion()
|
||||
{
|
||||
var versionRange = _proxy.GetApiVersion(Settings);
|
||||
var info = _dsTaskProxy.GetApiInfo(Settings);
|
||||
|
||||
_logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max());
|
||||
_logger.Debug("Download Station api version information: Min {0} - Max {1}", info.MinVersion, info.MaxVersion);
|
||||
|
||||
if (!versionRange.Contains(2))
|
||||
if (info.MinVersion > 2 || info.MaxVersion < 2)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {versionRange.Min()} to {versionRange.Max()}");
|
||||
return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}");
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -399,7 +402,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
|
||||
protected string GetDefaultDir()
|
||||
{
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
var config = _dsInfoProxy.GetConfig(Settings);
|
||||
|
||||
var path = config["default_destination"] as string;
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses
|
||||
{
|
||||
public class SabnzbdFullStatusResponse
|
||||
{
|
||||
public SabnzbdFullStatus Status { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -225,10 +225,18 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
if (!completeDir.IsRooted)
|
||||
{
|
||||
var queue = _proxy.GetQueue(0, 1, Settings);
|
||||
var defaultRootFolder = new OsPath(queue.DefaultRootFolder);
|
||||
if (HasVersion(2, 0))
|
||||
{
|
||||
var status = _proxy.GetFullStatus(Settings);
|
||||
completeDir = new OsPath(status.CompleteDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
var queue = _proxy.GetQueue(0, 1, Settings);
|
||||
var defaultRootFolder = new OsPath(queue.DefaultRootFolder);
|
||||
|
||||
completeDir = defaultRootFolder + completeDir;
|
||||
completeDir = defaultRootFolder + completeDir;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var category in config.Categories)
|
||||
@@ -448,50 +456,47 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Misc.enable_tv_sorting)
|
||||
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.TvCategory))
|
||||
{
|
||||
if (!config.Misc.tv_categories.Any<string>() ||
|
||||
config.Misc.tv_categories.Contains(Settings.TvCategory) ||
|
||||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.tv_categories.Contains("Default")))
|
||||
return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting")
|
||||
{
|
||||
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 Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
|
||||
if (config.Misc.enable_movie_sorting)
|
||||
if (config.Misc.enable_movie_sorting && ContainsCategory(config.Misc.movie_categories, Settings.TvCategory))
|
||||
{
|
||||
if (!config.Misc.movie_categories.Any<string>() ||
|
||||
config.Misc.movie_categories.Contains(Settings.TvCategory) ||
|
||||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.movie_categories.Contains("Default")))
|
||||
return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting")
|
||||
{
|
||||
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 Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
|
||||
if (config.Misc.enable_date_sorting)
|
||||
if (config.Misc.enable_date_sorting && ContainsCategory(config.Misc.date_categories, Settings.TvCategory))
|
||||
{
|
||||
if (!config.Misc.date_categories.Any<string>() ||
|
||||
config.Misc.date_categories.Contains(Settings.TvCategory) ||
|
||||
(Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.date_categories.Contains("Default")))
|
||||
return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting")
|
||||
{
|
||||
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 Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool ContainsCategory(IEnumerable<string> categories, string category)
|
||||
{
|
||||
if (categories == null || categories.Empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (category.IsNullOrWhiteSpace())
|
||||
{
|
||||
category = "Default";
|
||||
}
|
||||
|
||||
return categories.Contains(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
{
|
||||
public class SabnzbdFullStatus
|
||||
{
|
||||
// Added in Sabnzbd 2.0.0, my_home was previously in &mode=queue.
|
||||
// This is the already resolved completedir path.
|
||||
[JsonProperty(PropertyName = "completedir")]
|
||||
public string CompleteDir { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
void RemoveFrom(string source, string id,bool deleteData, SabnzbdSettings settings);
|
||||
string GetVersion(SabnzbdSettings settings);
|
||||
SabnzbdConfig GetConfig(SabnzbdSettings settings);
|
||||
SabnzbdFullStatus GetFullStatus(SabnzbdSettings settings);
|
||||
SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings);
|
||||
SabnzbdHistory GetHistory(int start, int limit, string category, SabnzbdSettings settings);
|
||||
string RetryDownload(string id, SabnzbdSettings settings);
|
||||
@@ -37,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
request.AddQueryParam("cat", category);
|
||||
request.AddQueryParam("priority", priority);
|
||||
|
||||
|
||||
request.AddFormUpload("name", filename, nzbData, "application/x-nzb");
|
||||
|
||||
SabnzbdAddResponse response;
|
||||
@@ -84,6 +85,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
return response.Config;
|
||||
}
|
||||
|
||||
public SabnzbdFullStatus GetFullStatus(SabnzbdSettings settings)
|
||||
{
|
||||
var request = BuildRequest("fullstatus", settings);
|
||||
request.AddQueryParam("skip_dashboard", "1");
|
||||
|
||||
var response = Json.Deserialize<SabnzbdFullStatusResponse>(ProcessRequest(request, settings));
|
||||
|
||||
return response.Status;
|
||||
}
|
||||
|
||||
public SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings)
|
||||
{
|
||||
var request = BuildRequest("queue", settings);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
{
|
||||
public class SabnzbdQueue
|
||||
{
|
||||
// Removed in Sabnzbd 2.0.0, see mode=fullstatus instead.
|
||||
[JsonProperty(PropertyName = "my_home")]
|
||||
public string DefaultRootFolder { get; set; }
|
||||
|
||||
|
||||
@@ -49,52 +49,36 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentFromUrl(magnetLink, Settings);
|
||||
_proxy.AddTorrentFromUrl(magnetLink, Settings.MovieCategory, RTorrentPriority.Normal, Settings.MovieDirectory, Settings);
|
||||
|
||||
// Download the magnet to the appropriate directory.
|
||||
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
|
||||
SetDownloadDirectory(hash);
|
||||
_proxy.StartTorrent(hash, Settings);
|
||||
|
||||
// Wait for the magnet to be resolved.
|
||||
var tries = 10;
|
||||
var retryDelay = 500;
|
||||
if (WaitForTorrent(hash, tries, retryDelay))
|
||||
{
|
||||
_logger.Info("Resolved magnet for {0}", remoteMovie.Movie.CleanTitle);
|
||||
SetDownloadDirectory(hash);
|
||||
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
|
||||
_proxy.StartTorrent(hash, Settings);
|
||||
return hash;
|
||||
}
|
||||
else
|
||||
|
||||
// Wait a bit for the magnet to be resolved.
|
||||
if (!WaitForTorrent(hash, tries, retryDelay))
|
||||
{
|
||||
_logger.Warn("rTorrent could not resolve magnet within {0} seconds, download may remain stuck: {1}.", tries * retryDelay / 1000, magnetLink);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings.MovieCategory, RTorrentPriority.Normal, Settings.MovieDirectory, Settings);
|
||||
|
||||
var tries = 5;
|
||||
var retryDelay = 200;
|
||||
if (WaitForTorrent(hash, tries, retryDelay))
|
||||
var tries = 10;
|
||||
var retryDelay = 500;
|
||||
if (!WaitForTorrent(hash, tries, retryDelay))
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash, Settings.MovieCategory, Settings);
|
||||
SetDownloadDirectory(hash);
|
||||
_proxy.StartTorrent(hash, Settings);
|
||||
return hash;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("rTorrent could not add file");
|
||||
_logger.Debug("rTorrent didn't add the torrent within {0} seconds: {1}.", tries * retryDelay / 1000, filename);
|
||||
|
||||
RemoveItem(hash, true);
|
||||
throw new ReleaseDownloadException(remoteMovie.Release, "Downloading torrent failed");
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override string Name => "rTorrent";
|
||||
@@ -233,14 +217,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
return result.Errors.First();
|
||||
}
|
||||
|
||||
private void SetDownloadDirectory(string hash)
|
||||
{
|
||||
if (Settings.MovieDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentDownloadDirectory(hash, Settings.MovieDirectory, Settings);
|
||||
}
|
||||
}
|
||||
|
||||
private bool WaitForTorrent(string hash, int tries, int retryDelay)
|
||||
{
|
||||
for (var i = 0; i < tries; i++)
|
||||
|
||||
@@ -13,15 +13,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
string GetVersion(RTorrentSettings settings);
|
||||
List<RTorrentTorrent> GetTorrents(RTorrentSettings settings);
|
||||
|
||||
void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings);
|
||||
void AddTorrentFromFile(string fileName, byte[] fileContent, RTorrentSettings settings);
|
||||
void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings);
|
||||
void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings);
|
||||
void RemoveTorrent(string hash, RTorrentSettings settings);
|
||||
void SetTorrentPriority(string hash, RTorrentPriority priority, RTorrentSettings settings);
|
||||
void SetTorrentLabel(string hash, string label, RTorrentSettings settings);
|
||||
void SetTorrentDownloadDirectory(string hash, string directory, RTorrentSettings settings);
|
||||
bool HasHashTorrent(string hash, RTorrentSettings settings);
|
||||
void StartTorrent(string hash, RTorrentSettings settings);
|
||||
void SetDeferredMagnetProperties(string hash, string category, string directory, RTorrentPriority priority, RTorrentSettings settings);
|
||||
}
|
||||
|
||||
public interface IRTorrent : IXmlRpcProxy
|
||||
@@ -29,35 +24,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
[XmlRpcMethod("d.multicall2")]
|
||||
object[] TorrentMulticall(params string[] parameters);
|
||||
|
||||
[XmlRpcMethod("load.normal")]
|
||||
int LoadUrl(string target, string data);
|
||||
[XmlRpcMethod("load.start")]
|
||||
int LoadStart(string target, string data, params string[] commands);
|
||||
|
||||
[XmlRpcMethod("load.raw")]
|
||||
int LoadBinary(string target, byte[] data);
|
||||
[XmlRpcMethod("load.raw_start")]
|
||||
int LoadRawStart(string target, byte[] data, params string[] commands);
|
||||
|
||||
[XmlRpcMethod("d.erase")]
|
||||
int Remove(string hash);
|
||||
|
||||
[XmlRpcMethod("d.custom1.set")]
|
||||
string SetLabel(string hash, string label);
|
||||
|
||||
[XmlRpcMethod("d.priority.set")]
|
||||
int SetPriority(string hash, long priority);
|
||||
|
||||
[XmlRpcMethod("d.directory.set")]
|
||||
int SetDirectory(string hash, string directory);
|
||||
|
||||
[XmlRpcMethod("method.set_key")]
|
||||
int SetKey(string target, string key, string cmd_key, string value);
|
||||
|
||||
[XmlRpcMethod("d.name")]
|
||||
string GetName(string hash);
|
||||
|
||||
[XmlRpcMethod("system.client_version")]
|
||||
string GetVersion();
|
||||
|
||||
[XmlRpcMethod("system.multicall")]
|
||||
object[] SystemMulticall(object[] parameters);
|
||||
}
|
||||
|
||||
public class RTorrentProxy : IRTorrentProxy
|
||||
@@ -101,20 +81,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
var items = new List<RTorrentTorrent>();
|
||||
foreach (object[] torrent in ret)
|
||||
{
|
||||
var labelDecoded = System.Web.HttpUtility.UrlDecode((string) torrent[3]);
|
||||
var labelDecoded = System.Web.HttpUtility.UrlDecode((string)torrent[3]);
|
||||
|
||||
var item = new RTorrentTorrent();
|
||||
item.Name = (string) torrent[0];
|
||||
item.Hash = (string) torrent[1];
|
||||
item.Path = (string) torrent[2];
|
||||
item.Name = (string)torrent[0];
|
||||
item.Hash = (string)torrent[1];
|
||||
item.Path = (string)torrent[2];
|
||||
item.Category = labelDecoded;
|
||||
item.TotalSize = (long) torrent[4];
|
||||
item.RemainingSize = (long) torrent[5];
|
||||
item.DownRate = (long) torrent[6];
|
||||
item.Ratio = (long) torrent[7];
|
||||
item.IsOpen = Convert.ToBoolean((long) torrent[8]);
|
||||
item.IsActive = Convert.ToBoolean((long) torrent[9]);
|
||||
item.IsFinished = Convert.ToBoolean((long) torrent[10]);
|
||||
item.TotalSize = (long)torrent[4];
|
||||
item.RemainingSize = (long)torrent[5];
|
||||
item.DownRate = (long)torrent[6];
|
||||
item.Ratio = (long)torrent[7];
|
||||
item.IsOpen = Convert.ToBoolean((long)torrent[8]);
|
||||
item.IsActive = Convert.ToBoolean((long)torrent[9]);
|
||||
item.IsFinished = Convert.ToBoolean((long)torrent[10]);
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
@@ -122,26 +102,26 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
return items;
|
||||
}
|
||||
|
||||
public void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings)
|
||||
public void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: load.normal");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
var response = client.LoadUrl("", torrentUrl);
|
||||
var response = client.LoadStart("", torrentUrl, GetCommands(label, priority, directory));
|
||||
if (response != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTorrentFromFile(string fileName, byte[] fileContent, RTorrentSettings settings)
|
||||
public void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: load.raw");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
var response = client.LoadBinary("", fileContent);
|
||||
var response = client.LoadRawStart("", fileContent, GetCommands(label, priority, directory));
|
||||
if (response != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not add torrent: {0}.", fileName);
|
||||
@@ -161,94 +141,26 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTorrentPriority(string hash, RTorrentPriority priority, RTorrentSettings settings)
|
||||
private string[] GetCommands(string label, RTorrentPriority priority, string directory)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.priority.set");
|
||||
var result = new List<string>();
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
var response = client.SetPriority(hash, (long) priority);
|
||||
if (response != 0)
|
||||
if (label.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
throw new DownloadClientException("Could not set priority on torrent: {0}.", hash);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTorrentLabel(string hash, string label, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.custom1.set");
|
||||
|
||||
var labelEncoded = System.Web.HttpUtility.UrlEncode(label);
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
var setLabel = client.SetLabel(hash, labelEncoded);
|
||||
if (setLabel != labelEncoded)
|
||||
{
|
||||
throw new DownloadClientException("Could set label on torrent: {0}.", hash);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTorrentDownloadDirectory(string hash, string directory, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.directory.set");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
var response = client.SetDirectory(hash, directory);
|
||||
if (response != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not set directory for torrent: {0}.", hash);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDeferredMagnetProperties(string hash, string category, string directory, RTorrentPriority priority, RTorrentSettings settings)
|
||||
{
|
||||
var commands = new List<string>();
|
||||
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
commands.Add("d.custom1.set=" + category);
|
||||
}
|
||||
|
||||
if (directory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
commands.Add("d.directory.set=" + directory);
|
||||
result.Add("d.custom1.set=" + label);
|
||||
}
|
||||
|
||||
if (priority != RTorrentPriority.Normal)
|
||||
{
|
||||
commands.Add("d.priority.set=" + (long)priority);
|
||||
result.Add("d.priority.set=" + (int)priority);
|
||||
}
|
||||
|
||||
// Ensure it gets started if the user doesn't have schedule=...,start_tied=
|
||||
commands.Add("d.open=");
|
||||
commands.Add("d.start=");
|
||||
|
||||
if (commands.Any())
|
||||
if (directory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var key = "event.download.inserted_new";
|
||||
var cmd_key = "sonarr_deferred_" + hash;
|
||||
|
||||
commands.Add(string.Format("print=\"Applying deferred properties to {0}\"", hash));
|
||||
|
||||
// Remove event handler once triggered.
|
||||
commands.Add(string.Format("\"method.set_key={0},{1}\"", key, cmd_key));
|
||||
|
||||
var setKeyValue = string.Format("branch=\"equal=d.hash=,cat={0}\",{{{1}}}", hash, string.Join(",", commands));
|
||||
|
||||
_logger.Debug("Executing remote method: method.set_key = {0},{1},{2}", key, cmd_key, setKeyValue);
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
// Commands need a target, in this case the target is an empty string
|
||||
// See: https://github.com/rakshasa/rtorrent/issues/227
|
||||
var response = client.SetKey("", key, cmd_key, setKeyValue);
|
||||
if (response != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could set properties for torrent: {0}.", hash);
|
||||
}
|
||||
result.Add("d.directory.set=" + directory);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public bool HasHashTorrent(string hash, RTorrentSettings settings)
|
||||
@@ -270,32 +182,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
}
|
||||
}
|
||||
|
||||
public void StartTorrent(string hash, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote methods: d.open and d.start");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
var multicallResponse = client.SystemMulticall(new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
methodName = "d.open",
|
||||
@params = new[] { hash }
|
||||
},
|
||||
new
|
||||
{
|
||||
methodName = "d.start",
|
||||
@params = new[] { hash }
|
||||
},
|
||||
}).SelectMany(c => ((IEnumerable<int>)c));
|
||||
|
||||
if (multicallResponse.Any(r => r != 0))
|
||||
{
|
||||
throw new DownloadClientException("Could not start torrent: {0}.", hash);
|
||||
}
|
||||
}
|
||||
|
||||
private IRTorrent BuildClient(RTorrentSettings settings)
|
||||
{
|
||||
var client = XmlRpcProxyGen.Create<IRTorrent>();
|
||||
@@ -316,4 +202,4 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ namespace NzbDrone.Core.Download
|
||||
public ValidationResult Test()
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
Test(failures);
|
||||
|
||||
@@ -117,45 +117,43 @@ namespace NzbDrone.Core.Download.Pending
|
||||
|
||||
foreach (var pendingRelease in GetPendingReleases())
|
||||
{
|
||||
//foreach (var episode in pendingRelease.RemoteEpisode.Episodes)
|
||||
//{
|
||||
var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteMovie));
|
||||
var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteMovie));
|
||||
|
||||
if (ect < nextRssSync.Value)
|
||||
{
|
||||
ect = nextRssSync.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ect = ect.AddMinutes(_configService.RssSyncInterval);
|
||||
}
|
||||
if (ect < nextRssSync.Value)
|
||||
{
|
||||
ect = nextRssSync.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ect = ect.AddMinutes(_configService.RssSyncInterval);
|
||||
}
|
||||
|
||||
var queue = new Queue.Queue
|
||||
{
|
||||
Id = GetQueueId(pendingRelease, pendingRelease.RemoteMovie.Movie),
|
||||
Series = null,
|
||||
Episode = null,
|
||||
Movie = pendingRelease.RemoteMovie.Movie,
|
||||
Quality = pendingRelease.RemoteMovie.ParsedMovieInfo.Quality,
|
||||
Title = pendingRelease.Title,
|
||||
Size = pendingRelease.RemoteMovie.Release.Size,
|
||||
Sizeleft = pendingRelease.RemoteMovie.Release.Size,
|
||||
RemoteMovie = pendingRelease.RemoteMovie,
|
||||
Timeleft = ect.Subtract(DateTime.UtcNow),
|
||||
EstimatedCompletionTime = ect,
|
||||
Status = "Pending",
|
||||
Protocol = pendingRelease.RemoteMovie.Release.DownloadProtocol
|
||||
};
|
||||
queued.Add(queue);
|
||||
//}
|
||||
var queue = new Queue.Queue
|
||||
{
|
||||
Id = GetQueueId(pendingRelease, pendingRelease.RemoteMovie.Movie),
|
||||
Series = null,
|
||||
Episode = null,
|
||||
Movie = pendingRelease.RemoteMovie.Movie,
|
||||
Quality = pendingRelease.RemoteMovie.ParsedMovieInfo?.Quality ?? new QualityModel(),
|
||||
Title = pendingRelease.Title,
|
||||
Size = pendingRelease.RemoteMovie.Release.Size,
|
||||
Sizeleft = pendingRelease.RemoteMovie.Release.Size,
|
||||
RemoteMovie = pendingRelease.RemoteMovie,
|
||||
Timeleft = ect.Subtract(DateTime.UtcNow),
|
||||
EstimatedCompletionTime = ect,
|
||||
Status = "Pending",
|
||||
Protocol = pendingRelease.RemoteMovie.Release.DownloadProtocol
|
||||
};
|
||||
|
||||
queued.Add(queue);
|
||||
}
|
||||
|
||||
//Return best quality release for each episode
|
||||
var deduped = queued.GroupBy(q => q.Episode.Id).Select(g =>
|
||||
var deduped = queued.GroupBy(q => q.Movie.Id).Select(g =>
|
||||
{
|
||||
var series = g.First().Series;
|
||||
var movies = g.First().Movie;
|
||||
|
||||
return g.OrderByDescending(e => e.Quality, new QualityModelComparer(series.Profile))
|
||||
return g.OrderByDescending(e => e.Quality, new QualityModelComparer(movies.Profile))
|
||||
.ThenBy(q => PrioritizeDownloadProtocol(q.Movie, q.Protocol))
|
||||
.First();
|
||||
});
|
||||
@@ -220,14 +218,15 @@ namespace NzbDrone.Core.Download.Pending
|
||||
|
||||
private void Insert(DownloadDecision decision)
|
||||
{
|
||||
_repository.Insert(new PendingRelease
|
||||
{
|
||||
MovieId = decision.RemoteMovie.Movie.Id,
|
||||
ParsedMovieInfo = decision.RemoteMovie.ParsedMovieInfo,
|
||||
Release = decision.RemoteMovie.Release,
|
||||
Title = decision.RemoteMovie.Release.Title,
|
||||
Added = DateTime.UtcNow
|
||||
});
|
||||
var release = new PendingRelease
|
||||
{
|
||||
MovieId = decision.RemoteMovie.Movie.Id,
|
||||
ParsedMovieInfo = decision.RemoteMovie.ParsedMovieInfo,
|
||||
Release = decision.RemoteMovie.Release,
|
||||
Title = decision.RemoteMovie.Release.Title,
|
||||
Added = DateTime.UtcNow
|
||||
};
|
||||
_repository.Insert(release);
|
||||
|
||||
_eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent());
|
||||
}
|
||||
@@ -254,12 +253,12 @@ namespace NzbDrone.Core.Download.Pending
|
||||
return new[] { delay, minimumAge }.Max();
|
||||
}
|
||||
|
||||
private void RemoveGrabbed(RemoteMovie remoteEpisode)
|
||||
private void RemoveGrabbed(RemoteMovie remoteMovie)
|
||||
{
|
||||
var pendingReleases = GetPendingReleases();
|
||||
|
||||
|
||||
var existingReports = pendingReleases.Where(r => r.RemoteMovie.Movie.Id == remoteEpisode.Movie.Id)
|
||||
var existingReports = pendingReleases.Where(r => r.RemoteMovie.Movie.Id == remoteMovie.Movie.Id)
|
||||
.ToList();
|
||||
|
||||
if (existingReports.Empty())
|
||||
@@ -267,11 +266,11 @@ namespace NzbDrone.Core.Download.Pending
|
||||
return;
|
||||
}
|
||||
|
||||
var profile = remoteEpisode.Movie.Profile.Value;
|
||||
var profile = remoteMovie.Movie.Profile.Value;
|
||||
|
||||
foreach (var existingReport in existingReports)
|
||||
{
|
||||
var compare = new QualityModelComparer(profile).Compare(remoteEpisode.ParsedMovieInfo.Quality,
|
||||
var compare = new QualityModelComparer(profile).Compare(remoteMovie.ParsedMovieInfo.Quality,
|
||||
existingReport.RemoteMovie.ParsedMovieInfo.Quality);
|
||||
|
||||
//Only remove lower/equal quality pending releases
|
||||
|
||||
@@ -15,12 +15,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM EpisodeFiles
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MovieFiles
|
||||
WHERE Id IN (
|
||||
SELECT EpisodeFiles.Id FROM EpisodeFiles
|
||||
LEFT OUTER JOIN Episodes
|
||||
ON EpisodeFiles.Id = Episodes.EpisodeFileId
|
||||
WHERE Episodes.Id IS NULL)");
|
||||
SELECT MovieFiles.Id FROM MovieFiles
|
||||
LEFT OUTER JOIN Movies
|
||||
ON MovieFiles.Id = Movies.MovieFileId
|
||||
WHERE Movies.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
var usedTags = new[] { "Series", "Notifications", "DelayProfiles", "Restrictions" }
|
||||
var usedTags = new[] { "Movies", "Series", "Notifications", "DelayProfiles", "Restrictions" }
|
||||
.SelectMany(v => GetUsedTags(v, mapper))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
@@ -6,21 +6,21 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
public class UpdateCleanTitleForSeries : IHousekeepingTask
|
||||
{
|
||||
private readonly ISeriesRepository _seriesRepository;
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
|
||||
public UpdateCleanTitleForSeries(ISeriesRepository seriesRepository)
|
||||
public UpdateCleanTitleForSeries(IMovieRepository movieRepository)
|
||||
{
|
||||
_seriesRepository = seriesRepository;
|
||||
_movieRepository = movieRepository;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var series = _seriesRepository.All().ToList();
|
||||
var movies = _movieRepository.All().ToList();
|
||||
|
||||
series.ForEach(s =>
|
||||
movies.ForEach(m =>
|
||||
{
|
||||
s.CleanTitle = s.CleanTitle.CleanSeriesTitle();
|
||||
_seriesRepository.Update(s);
|
||||
m.CleanTitle = m.CleanTitle.CleanSeriesTitle();
|
||||
_movieRepository.Update(m);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -64,6 +62,7 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
|
||||
Subtitles = x.Element("subtitles").Value,
|
||||
EncodeStatus = x.Element("encodestatus").Value,
|
||||
Freeleech = x.Element("freeleech").Value,
|
||||
ImdbId = x.Element("imdb").Value
|
||||
}).ToList();
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
@@ -80,7 +79,8 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
|
||||
InfoUrl = GetInfoUrl(torrent.GroupId, id),
|
||||
Seeders = int.Parse(torrent.Seeders),
|
||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||
PublishDate = torrent.Time.ToUniversalTime()
|
||||
PublishDate = torrent.Time.ToUniversalTime(),
|
||||
ImdbId = int.Parse(torrent.ImdbId.Substring(2))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
|
||||
public class ImdbInfo
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
public int Id { get; set; }
|
||||
public string EnglishTitle { get; set; }
|
||||
public string OriginalTitle { get; set; }
|
||||
public int? Year { get; set; }
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.HDBits
|
||||
{
|
||||
|
||||
@@ -64,7 +64,8 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||
Seeders = result.Seeders,
|
||||
Peers = result.Leechers + result.Seeders,
|
||||
PublishDate = result.Added.ToUniversalTime(),
|
||||
Internal = internalRelease
|
||||
Internal = internalRelease,
|
||||
ImdbId = result.ImdbInfo?.Id ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
@@ -49,15 +51,30 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
var request = new HttpRequest(url, HttpAccept.Rss);
|
||||
|
||||
HttpResponse response;
|
||||
|
||||
try
|
||||
{
|
||||
var response = _httpClient.Get(request);
|
||||
|
||||
capabilities = ParseCapabilities(response);
|
||||
response = _httpClient.Get(request);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug(ex, string.Format("Failed to get capabilities from {0}: {1}", indexerSettings.Url, ex.Message));
|
||||
_logger.Debug(ex, "Failed to get newznab api capabilities from {0}", indexerSettings.Url);
|
||||
throw;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
capabilities = ParseCapabilities(response);
|
||||
}
|
||||
catch (XmlException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Failed to parse newznab api capabilities for {0}.", indexerSettings.Url);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to determine newznab api capabilities for {0}, using the defaults instead till Sonarr restarts.", indexerSettings.Url);
|
||||
}
|
||||
|
||||
return capabilities;
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
if (SupportsMovieSearch)
|
||||
if (SupportsMovieSearch && searchCriteria.Movie.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie", $"&imdbid={searchCriteria.Movie.ImdbId.Substring(2)}"));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -41,8 +41,8 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
}
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<PassThePopcornResponse>(indexerResponse.Content);
|
||||
if (jsonResponse.TotalResults == "0" ||
|
||||
jsonResponse.TotalResults.IsNullOrWhiteSpace() ||
|
||||
if (jsonResponse.TotalResults == "0" ||
|
||||
jsonResponse.TotalResults.IsNullOrWhiteSpace() ||
|
||||
jsonResponse.Movies == null)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, "No results were found");
|
||||
@@ -81,7 +81,8 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
PublishDate = torrent.UploadTime.ToUniversalTime(),
|
||||
Golden = torrent.GoldenPopcorn,
|
||||
Scene = torrent.Scene,
|
||||
Approved = torrent.Checked
|
||||
Approved = torrent.Checked,
|
||||
ImdbId = (result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0)
|
||||
});
|
||||
}
|
||||
// Add all torrents
|
||||
@@ -99,7 +100,8 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
PublishDate = torrent.UploadTime.ToUniversalTime(),
|
||||
Golden = torrent.GoldenPopcorn,
|
||||
Scene = torrent.Scene,
|
||||
Approved = torrent.Checked
|
||||
Approved = torrent.Checked,
|
||||
ImdbId = (result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0)
|
||||
});
|
||||
}
|
||||
// Don't add any torrents
|
||||
@@ -118,10 +120,10 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
return
|
||||
torrentInfos.OrderByDescending(o => o.PublishDate)
|
||||
.ThenBy(o => ((dynamic)o).Golden ? 0 : 1)
|
||||
.ThenBy(o => ((dynamic) o).Scene ? 0 : 1)
|
||||
.ThenBy(o => ((dynamic)o).Scene ? 0 : 1)
|
||||
.ToArray();
|
||||
}
|
||||
return
|
||||
return
|
||||
torrentInfos.OrderByDescending(o => o.PublishDate)
|
||||
.ThenBy(o => ((dynamic)o).Golden ? 0 : 1)
|
||||
.ToArray();
|
||||
@@ -130,14 +132,14 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
// prefer scene
|
||||
if (_settings.Scene)
|
||||
{
|
||||
return
|
||||
return
|
||||
torrentInfos.OrderByDescending(o => o.PublishDate)
|
||||
.ThenBy(o => ((dynamic)o).Scene ? 0 : 1)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
// order by date
|
||||
return
|
||||
return
|
||||
torrentInfos
|
||||
.OrderByDescending(o => o.PublishDate)
|
||||
.ToArray();
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IMovieFileRepository _movieFileRepository;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DiskScanService(IDiskProvider diskProvider,
|
||||
@@ -55,6 +56,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
IMediaFileTableCleanupService mediaFileTableCleanupService,
|
||||
IEventAggregator eventAggregator,
|
||||
IMovieService movieService,
|
||||
IMovieFileRepository movieFileRepository,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
@@ -66,6 +68,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
_mediaFileTableCleanupService = mediaFileTableCleanupService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_movieService = movieService;
|
||||
_movieFileRepository = movieFileRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -136,14 +139,14 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
if (!_diskProvider.FolderExists(rootFolder))
|
||||
{
|
||||
_logger.Warn("Series' root folder ({0}) doesn't exist.", rootFolder);
|
||||
_logger.Warn("Movies' root folder ({0}) doesn't exist.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderDoesNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_diskProvider.GetDirectories(rootFolder).Empty())
|
||||
{
|
||||
_logger.Warn("Series' root folder ({0}) is empty.", rootFolder);
|
||||
_logger.Warn("Movies' root folder ({0}) is empty.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderIsEmpty));
|
||||
return;
|
||||
}
|
||||
@@ -155,13 +158,20 @@ namespace NzbDrone.Core.MediaFiles
|
||||
if (_configService.CreateEmptySeriesFolders &&
|
||||
_diskProvider.FolderExists(rootFolder))
|
||||
{
|
||||
_logger.Debug("Creating missing series folder: {0}", movie.Path);
|
||||
_logger.Debug("Creating missing movies folder: {0}", movie.Path);
|
||||
_diskProvider.CreateFolder(movie.Path);
|
||||
SetPermissions(movie.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Series folder doesn't exist: {0}", movie.Path);
|
||||
// Delete Movie from MovieFiles
|
||||
_movieFileRepository.Delete(movie.MovieFileId);
|
||||
|
||||
// Update Movie
|
||||
movie.MovieFileId = 0;
|
||||
_movieService.UpdateMovie(movie);
|
||||
|
||||
_logger.Debug("Movies folder doesn't exist: {0}", movie.Path);
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.MovieFolderDoesNotExist));
|
||||
|
||||
@@ -135,16 +135,16 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
localMovie.Size = _diskProvider.GetFileSize(file);
|
||||
|
||||
_logger.Debug("Size: {0}", localMovie.Size);
|
||||
|
||||
var current = localMovie.Quality;
|
||||
//TODO: make it so media info doesn't ruin the import process of a new series
|
||||
if (sceneSource)
|
||||
if (sceneSource && ShouldCheckQualityForParsedQuality(current.Quality))
|
||||
{
|
||||
localMovie.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
||||
if (shouldCheckQuality)
|
||||
{
|
||||
_logger.Debug("Checking quality for this video file to make sure nothing mismatched.");
|
||||
var width = localMovie.MediaInfo.Width;
|
||||
var current = localMovie.Quality;
|
||||
|
||||
var qualityName = current.Quality.Name.ToLower();
|
||||
QualityModel updated = null;
|
||||
if (width > 2000)
|
||||
@@ -565,5 +565,20 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ShouldCheckQualityForParsedQuality(Quality quality)
|
||||
{
|
||||
List<Quality> shouldNotCheck = new List<Quality> { Quality.WORKPRINT, Quality.TELECINE, Quality.TELESYNC,
|
||||
Quality.DVDSCR, Quality.DVD, Quality.CAM, Quality.DVDR, Quality.Remux1080p, Quality.Remux2160p, Quality.REGIONAL
|
||||
};
|
||||
|
||||
if (shouldNotCheck.Contains(quality))
|
||||
{
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
|
||||
public int Open(Stream stream)
|
||||
{
|
||||
if (stream.Length < 1024)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var isValid = (int)MediaInfo_Open_Buffer_Init(_handle, stream.Length, 0);
|
||||
if (isValid == 1)
|
||||
{
|
||||
@@ -203,7 +208,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
totalRead += bufferRead;
|
||||
|
||||
var status = (BufferStatus)MediaInfo_Open_Buffer_Continue(_handle, buffer, (IntPtr)bufferRead);
|
||||
|
||||
|
||||
if (status.HasFlag(BufferStatus.Finalized) || status <= 0 || bufferRead == 0)
|
||||
{
|
||||
Logger.Trace("Read file offset {0}-{1} ({2} bytes)", seekStart, stream.Position, stream.Position - seekStart);
|
||||
|
||||
@@ -84,9 +84,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
.Build();
|
||||
|
||||
request.AllowAutoRedirect = true;
|
||||
request.SuppressHttpError = true;
|
||||
// request.SuppressHttpError = true;
|
||||
|
||||
var response = _httpClient.Get<MovieResourceRoot>(request);
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new HttpException(request, response);
|
||||
}
|
||||
|
||||
if (response.Headers.ContentType != HttpAccept.JsonCharset.Value)
|
||||
{
|
||||
throw new HttpException(request, response);
|
||||
}
|
||||
|
||||
// The dude abides, so should us, Lets be nice to TMDb
|
||||
// var allowed = int.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()); // get allowed
|
||||
@@ -301,19 +310,28 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
return movie;
|
||||
}
|
||||
|
||||
public Movie GetMovieInfo(string ImdbId)
|
||||
public Movie GetMovieInfo(string imdbId)
|
||||
{
|
||||
var request = _movieBuilder.Create()
|
||||
.SetSegment("route", "find")
|
||||
.SetSegment("id", ImdbId)
|
||||
.SetSegment("id", imdbId)
|
||||
.SetSegment("secondaryRoute", "")
|
||||
.AddQueryParam("external_source", "imdb_id")
|
||||
.Build();
|
||||
|
||||
request.AllowAutoRedirect = true;
|
||||
request.SuppressHttpError = true;
|
||||
// request.SuppressHttpError = true;
|
||||
|
||||
var response = _httpClient.Get<FindRoot>(request);
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new HttpException(request, response);
|
||||
}
|
||||
|
||||
if (response.Headers.ContentType != HttpAccept.JsonCharset.Value)
|
||||
{
|
||||
throw new HttpException(request, response);
|
||||
}
|
||||
|
||||
// The dude abides, so should us, Lets be nice to TMDb
|
||||
// var allowed = int.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()); // get allowed
|
||||
@@ -711,39 +729,47 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
|
||||
public Movie MapMovieToTmdbMovie(Movie movie)
|
||||
{
|
||||
Movie newMovie = movie;
|
||||
if (movie.TmdbId > 0)
|
||||
{
|
||||
newMovie = GetMovieInfo(movie.TmdbId);
|
||||
}
|
||||
else if (movie.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
newMovie = GetMovieInfo(movie.ImdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var yearStr = "";
|
||||
if (movie.Year > 1900)
|
||||
{
|
||||
yearStr = $" {movie.Year}";
|
||||
}
|
||||
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault();
|
||||
}
|
||||
try
|
||||
{
|
||||
Movie newMovie = movie;
|
||||
if (movie.TmdbId > 0)
|
||||
{
|
||||
newMovie = GetMovieInfo(movie.TmdbId);
|
||||
}
|
||||
else if (movie.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
newMovie = GetMovieInfo(movie.ImdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var yearStr = "";
|
||||
if (movie.Year > 1900)
|
||||
{
|
||||
yearStr = $" {movie.Year}";
|
||||
}
|
||||
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (newMovie == null)
|
||||
{
|
||||
_logger.Warn("Couldn't map movie {0} to a movie on The Movie DB. It will not be added :(", movie.Title);
|
||||
return null;
|
||||
}
|
||||
if (newMovie == null)
|
||||
{
|
||||
_logger.Warn("Couldn't map movie {0} to a movie on The Movie DB. It will not be added :(", movie.Title);
|
||||
return null;
|
||||
}
|
||||
|
||||
newMovie.Path = movie.Path;
|
||||
newMovie.RootFolderPath = movie.RootFolderPath;
|
||||
newMovie.ProfileId = movie.ProfileId;
|
||||
newMovie.Monitored = movie.Monitored;
|
||||
newMovie.MovieFile = movie.MovieFile;
|
||||
newMovie.MinimumAvailability = movie.MinimumAvailability;
|
||||
newMovie.Path = movie.Path;
|
||||
newMovie.RootFolderPath = movie.RootFolderPath;
|
||||
newMovie.ProfileId = movie.ProfileId;
|
||||
newMovie.Monitored = movie.Monitored;
|
||||
newMovie.MovieFile = movie.MovieFile;
|
||||
newMovie.MinimumAvailability = movie.MinimumAvailability;
|
||||
|
||||
return newMovie;
|
||||
return newMovie;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Couldn't map movie {0} to a movie on The Movie DB. It will not be added :(", movie.Title);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,19 @@ namespace NzbDrone.Core.NetImport
|
||||
|
||||
_logger.Debug("Found {0} movies from list(s) {1}", movies.Count, string.Join(", ", lists.Select(l => l.Definition.Name)));
|
||||
|
||||
return movies;
|
||||
return movies.DistinctBy(x => {
|
||||
if (x.TmdbId != 0)
|
||||
{
|
||||
return x.TmdbId.ToString();
|
||||
}
|
||||
|
||||
if (x.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return x.ImdbId;
|
||||
}
|
||||
|
||||
return x.Title;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -114,29 +126,29 @@ namespace NzbDrone.Core.NetImport
|
||||
// listedMovies = listedMovies.Where(ah => importExclusions.Any(h => ah.TmdbId.ToString() != h)).ToList();
|
||||
}
|
||||
|
||||
var downloadedCount = 0;
|
||||
//var downloadedCount = 0;
|
||||
foreach (var movie in listedMovies)
|
||||
{
|
||||
var mapped = _movieSearch.MapMovieToTmdbMovie(movie);
|
||||
if (mapped != null && !importExclusions.Any(x => x == mapped.TmdbId.ToString()))
|
||||
{
|
||||
List<DownloadDecision> decisions;
|
||||
//List<DownloadDecision> decisions;
|
||||
mapped.AddOptions = new AddMovieOptions {SearchForMovie = true};
|
||||
_movieService.AddMovie(mapped);
|
||||
|
||||
// Search for movie
|
||||
try
|
||||
{
|
||||
decisions = _nzbSearchService.MovieSearch(mapped.Id, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, $"Unable to search in list for movie {mapped.Id}");
|
||||
continue;
|
||||
}
|
||||
//// Search for movie
|
||||
//try
|
||||
//{
|
||||
// decisions = _nzbSearchService.MovieSearch(mapped.Id, false);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// _logger.Error(ex, $"Unable to search in list for movie {mapped.Id}");
|
||||
// continue;
|
||||
//}
|
||||
|
||||
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
||||
downloadedCount += processed.Grabbed.Count;
|
||||
//var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
||||
//downloadedCount += processed.Grabbed.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -147,7 +159,7 @@ namespace NzbDrone.Core.NetImport
|
||||
}
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount);
|
||||
//_logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount);
|
||||
}
|
||||
|
||||
private void CleanLibrary(List<Movie> movies)
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.NetImport.StevenLu
|
||||
movies.AddIfNotNull(new Tv.Movie()
|
||||
{
|
||||
Title = item.title,
|
||||
ImdbId = item.imdb_id
|
||||
ImdbId = item.imdb_id,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace NzbDrone.Core.NetImport.Trakt
|
||||
{
|
||||
var link = Settings.Link.Trim();
|
||||
|
||||
var filtersAndLimit = $"?years={Settings.Years}&genres={Settings.Genres.ToLower()}&ratings={Settings.Rating}&certifications={Settings.Ceritification.ToLower()}&limit={Settings.Limit}";
|
||||
var filtersAndLimit = $"?years={Settings.Years}&genres={Settings.Genres.ToLower()}&ratings={Settings.Rating}&certifications={Settings.Ceritification.ToLower()}&limit={Settings.Limit}{Settings.TraktAdditionalParameters}";
|
||||
|
||||
switch (Settings.ListType)
|
||||
{
|
||||
@@ -129,7 +129,7 @@ namespace NzbDrone.Core.NetImport.Trakt
|
||||
break;
|
||||
}
|
||||
|
||||
Authenticate();
|
||||
Authenticate();
|
||||
|
||||
var request = new NetImportRequest($"{link}", HttpAccept.Json);
|
||||
request.HttpRequest.Headers.Add("trakt-api-version", "2");
|
||||
|
||||
@@ -96,6 +96,9 @@ namespace NzbDrone.Core.NetImport.Trakt
|
||||
[FieldDefinition(8, Label = "Limit", HelpText = "Limit the number of movies to get")]
|
||||
public int Limit { get; set; }
|
||||
|
||||
[FieldDefinition(9, Label = "Additional Parameters", HelpText = "Additional Trakt API parameters", Advanced = true)]
|
||||
public string TraktAdditionalParameters { get; set; }
|
||||
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
|
||||
@@ -408,8 +408,9 @@
|
||||
<Compile Include="DecisionEngine\Specifications\UpgradeDiskSpecification.cs" />
|
||||
<Compile Include="DiskSpace\DiskSpace.cs" />
|
||||
<Compile Include="DiskSpace\DiskSpaceService.cs" />
|
||||
<Compile Include="Download\Clients\DownloadStation\Proxies\DownloadStationInfoProxy.cs" />
|
||||
<Compile Include="Download\Clients\DownloadStation\TorrentDownloadStation.cs" />
|
||||
<Compile Include="Download\Clients\DownloadStation\Proxies\DownloadStationProxy.cs" />
|
||||
<Compile Include="Download\Clients\DownloadStation\Proxies\DownloadStationTaskProxy.cs" />
|
||||
<Compile Include="Download\Clients\DownloadStation\DownloadStationSettings.cs" />
|
||||
<Compile Include="Download\Clients\DownloadStation\DownloadStationTask.cs" />
|
||||
<Compile Include="Download\Clients\DownloadStation\DownloadStationTaskAdditional.cs" />
|
||||
@@ -505,6 +506,7 @@
|
||||
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdPriorityTypeConverter.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdStringArrayConverter.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdQueueTimeConverter.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdFullStatusResponse.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdRetryResponse.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdAddResponse.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdCategoryResponse.cs" />
|
||||
@@ -513,6 +515,7 @@
|
||||
<Compile Include="Download\Clients\Sabnzbd\Sabnzbd.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\SabnzbdCategory.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\SabnzbdDownloadStatus.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\SabnzbdFullStatus.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\SabnzbdHistory.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\SabnzbdHistoryItem.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\SabnzbdJsonError.cs" />
|
||||
@@ -991,6 +994,12 @@
|
||||
<Compile Include="Parser\Model\LocalMovie.cs" />
|
||||
<Compile Include="Parser\Model\ParsedMovieInfo.cs" />
|
||||
<Compile Include="Parser\Model\RemoteMovie.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\ArabicRomanNumeral.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\IRomanNumeral.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\RomanNumeral.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\RomanNumeralParser.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\SimpleArabicNumeral.cs" />
|
||||
<Compile Include="Parser\RomanNumerals\SimpleRomanNumeral.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
@@ -7,6 +8,7 @@ using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Parser.RomanNumerals;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Parser
|
||||
@@ -33,20 +35,7 @@ namespace NzbDrone.Core.Parser
|
||||
private readonly ISceneMappingService _sceneMappingService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly Logger _logger;
|
||||
private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string>
|
||||
{
|
||||
{ "1", "I"},
|
||||
{ "2", "II"},
|
||||
{ "3", "III"},
|
||||
{ "4", "IV"},
|
||||
{ "5", "V"},
|
||||
{ "6", "VI"},
|
||||
{ "7", "VII"},
|
||||
{ "8", "VII"},
|
||||
{ "9", "IX"},
|
||||
{ "10", "X"},
|
||||
|
||||
}; //If a movie has more than 10 parts fuck 'em.
|
||||
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings;
|
||||
|
||||
public ParsingService(IEpisodeService episodeService,
|
||||
ISeriesService seriesService,
|
||||
@@ -59,6 +48,11 @@ namespace NzbDrone.Core.Parser
|
||||
_sceneMappingService = sceneMappingService;
|
||||
_movieService = movieService;
|
||||
_logger = logger;
|
||||
|
||||
if (_arabicRomanNumeralMappings == null)
|
||||
{
|
||||
_arabicRomanNumeralMappings = RomanNumeralParser.GetArabicRomanNumeralsMapping();
|
||||
}
|
||||
}
|
||||
|
||||
public LocalEpisode GetLocalEpisode(string filename, Series series)
|
||||
@@ -354,85 +348,113 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
// TODO: Answer me this: Wouldn't it be smarter to start out looking for a movie if we have an ImDb Id?
|
||||
if (!String.IsNullOrWhiteSpace(imdbId))
|
||||
{
|
||||
Movie movieByImDb;
|
||||
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out movieByImDb))
|
||||
{
|
||||
return movieByImDb;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
var possibleTitles = new List<string>();
|
||||
|
||||
Movie possibleMovie = null;
|
||||
|
||||
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
|
||||
|
||||
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
|
||||
Movie movieBySearchCriteria;
|
||||
if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out movieBySearchCriteria))
|
||||
{
|
||||
possibleTitles.Add(altTitle.CleanSeriesTitle());
|
||||
return movieBySearchCriteria;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Movie movieByTitleAndOrYear;
|
||||
if (TryGetMovieByTitleAndOrYear(parsedMovieInfo, out movieByTitleAndOrYear))
|
||||
{
|
||||
return movieByTitleAndOrYear;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing found up to here => logging that and returning null
|
||||
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryGetMovieByImDbId(ParsedMovieInfo parsedMovieInfo, string imdbId, out Movie movie)
|
||||
{
|
||||
movie = _movieService.FindByImdbId(imdbId);
|
||||
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
|
||||
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
|
||||
{
|
||||
movie = null;
|
||||
return false;
|
||||
}
|
||||
return movie != null;
|
||||
}
|
||||
|
||||
private bool TryGetMovieByTitleAndOrYear(ParsedMovieInfo parsedMovieInfo, out Movie movieByTitleAndOrYear)
|
||||
{
|
||||
Func<Movie, bool> isNotNull = movie => movie != null;
|
||||
if (parsedMovieInfo.Year > 1800)
|
||||
{
|
||||
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
|
||||
if (isNotNull(movieByTitleAndOrYear))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||
if (isNotNull(movieByTitleAndOrYear))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
movieByTitleAndOrYear = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out Movie possibleMovie)
|
||||
{
|
||||
possibleMovie = null;
|
||||
List<string> possibleTitles = new List<string>();
|
||||
|
||||
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
|
||||
|
||||
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
|
||||
{
|
||||
possibleTitles.Add(altTitle.CleanSeriesTitle());
|
||||
}
|
||||
|
||||
foreach (string title in possibleTitles)
|
||||
{
|
||||
if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
foreach (string title in possibleTitles)
|
||||
foreach (ArabicRomanNumeral numeralMapping in _arabicRomanNumeralMappings)
|
||||
{
|
||||
if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
string arabicNumeral = numeralMapping.ArabicNumeralAsString;
|
||||
string romanNumeral = numeralMapping.RomanNumeralLowerCase;
|
||||
|
||||
if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
|
||||
if (title.Replace(romanNumeral, arabicNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
string num = entry.Key;
|
||||
string roman = entry.Value.ToLower();
|
||||
|
||||
if (title.Replace(num, roman) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
if (title.Replace(roman, num) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year))
|
||||
{
|
||||
return possibleMovie;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Movie movie = null;
|
||||
|
||||
if (searchCriteria == null)
|
||||
if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year))
|
||||
{
|
||||
if (parsedMovieInfo.Year > 1900)
|
||||
{
|
||||
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
|
||||
}
|
||||
else
|
||||
{
|
||||
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||
}
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
movie = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||
}
|
||||
// return movie;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (movie == null && imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
movie = _movieService.FindByImdbId(imdbId);
|
||||
}
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return movie;
|
||||
possibleMovie = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria)
|
||||
|
||||
18
src/NzbDrone.Core/Parser/RomanNumerals/ArabicRomanNumeral.cs
Normal file
18
src/NzbDrone.Core/Parser/RomanNumerals/ArabicRomanNumeral.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
public class ArabicRomanNumeral
|
||||
{
|
||||
public ArabicRomanNumeral(int arabicNumeral, string arabicNumeralAsString, string romanNumeral)
|
||||
{
|
||||
ArabicNumeral = arabicNumeral;
|
||||
ArabicNumeralAsString = arabicNumeralAsString;
|
||||
RomanNumeral = romanNumeral;
|
||||
}
|
||||
|
||||
public int ArabicNumeral { get; private set; }
|
||||
public string ArabicNumeralAsString { get; private set; }
|
||||
public string RomanNumeral { get; private set; }
|
||||
|
||||
public string RomanNumeralLowerCase => RomanNumeral.ToLower();
|
||||
}
|
||||
}
|
||||
12
src/NzbDrone.Core/Parser/RomanNumerals/IRomanNumeral.cs
Normal file
12
src/NzbDrone.Core/Parser/RomanNumerals/IRomanNumeral.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
public interface IRomanNumeral
|
||||
{
|
||||
int CompareTo(object obj);
|
||||
int CompareTo(RomanNumeral other);
|
||||
bool Equals(RomanNumeral other);
|
||||
int ToInt();
|
||||
long ToLong();
|
||||
string ToString();
|
||||
}
|
||||
}
|
||||
357
src/NzbDrone.Core/Parser/RomanNumerals/RomanNumeral.cs
Normal file
357
src/NzbDrone.Core/Parser/RomanNumerals/RomanNumeral.cs
Normal file
@@ -0,0 +1,357 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the numeric system used in ancient Rome, employing combinations of letters from the Latin alphabet to signify values.
|
||||
/// Implementation adapted from: http://www.c-sharpcorner.com/Blogs/14255/converting-to-and-from-roman-numerals.aspx
|
||||
/// </summary>
|
||||
public class RomanNumeral : IComparable, IComparable<RomanNumeral>, IEquatable<RomanNumeral>, IRomanNumeral
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// The numeric value of the roman numeral.
|
||||
/// </summary>
|
||||
private readonly int _value;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the smallest possible value of an <see cref="T:RomanNumeral"/>. This field is constant.
|
||||
/// </summary>
|
||||
public static readonly int MinValue = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the largest possible value of an <see cref="T:RomanNumeral"/>. This field is constant.
|
||||
/// </summary>
|
||||
public static readonly int MaxValue = 3999;
|
||||
|
||||
private static readonly string[] Thousands = { "MMM", "MM", "M" };
|
||||
private static readonly string[] Hundreds = { "CM", "DCCC", "DCC", "DC", "D", "CD", "CCC", "CC", "C" };
|
||||
private static readonly string[] Tens = { "XC", "LXXX", "LXX", "LX", "L", "XL", "XXX", "XX", "X" };
|
||||
private static readonly string[] Units = { "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I" };
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
|
||||
/// </summary>
|
||||
public RomanNumeral()
|
||||
{
|
||||
_value = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public RomanNumeral(int value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RomanNumeral"/> class.
|
||||
/// </summary>
|
||||
/// <param name="romanNumeral">The roman numeral.</param>
|
||||
public RomanNumeral(string romanNumeral)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (TryParse(romanNumeral, out value))
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Converts this instance to an integer.
|
||||
/// </summary>
|
||||
/// <returns>A numeric int representation.</returns>
|
||||
public int ToInt()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this instance to a long.
|
||||
/// </summary>
|
||||
/// <returns>A numeric long representation.</returns>
|
||||
public long ToLong()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the string representation of a number to its 32-bit signed integer equivalent. A return value indicates whether the conversion succeeded.
|
||||
/// </summary>
|
||||
/// <param name="text">A string containing a number to convert. </param>
|
||||
/// <param name="value">When this method returns, contains the 32-bit signed integer value equivalent of the number contained in <paramref name="text"/>,
|
||||
/// if the conversion succeeded, or zero if the conversion failed. The conversion fails if the <paramref name="text"/> parameter is null or
|
||||
/// <see cref="F:System.String.Empty"/>, is not of the correct format, or represents a number less than <see cref="F:System.Int32.MinValue"/> or greater than <see cref="F:System.Int32.MaxValue"/>. This parameter is passed uninitialized. </param><filterpriority>1</filterpriority>
|
||||
/// <returns>
|
||||
/// true if <paramref name="text"/> was converted successfully; otherwise, false.
|
||||
/// </returns>
|
||||
public static bool TryParse(string text, out int value)
|
||||
{
|
||||
value = 0;
|
||||
if (string.IsNullOrEmpty(text)) return false;
|
||||
text = text.ToUpper();
|
||||
int len = 0;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (text.StartsWith(Thousands[i]))
|
||||
{
|
||||
value += 1000 * (3 - i);
|
||||
len = Thousands[i].Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
text = text.Substring(len);
|
||||
len = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
if (text.StartsWith(Hundreds[i]))
|
||||
{
|
||||
value += 100 * (9 - i);
|
||||
len = Hundreds[i].Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
text = text.Substring(len);
|
||||
len = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
if (text.StartsWith(Tens[i]))
|
||||
{
|
||||
value += 10 * (9 - i);
|
||||
len = Tens[i].Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
text = text.Substring(len);
|
||||
len = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
if (text.StartsWith(Units[i]))
|
||||
{
|
||||
value += 9 - i;
|
||||
len = Units[i].Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (text.Length > len)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a number into a roman numeral.
|
||||
/// </summary>
|
||||
/// <param name="number">The number.</param>
|
||||
/// <returns></returns>
|
||||
private static string ToRomanNumeral(int number)
|
||||
{
|
||||
RangeGuard(number);
|
||||
int thousands, hundreds, tens, units;
|
||||
thousands = number / 1000;
|
||||
number %= 1000;
|
||||
hundreds = number / 100;
|
||||
number %= 100;
|
||||
tens = number / 10;
|
||||
units = number % 10;
|
||||
var sb = new StringBuilder();
|
||||
if (thousands > 0) sb.Append(Thousands[3 - thousands]);
|
||||
if (hundreds > 0) sb.Append(Hundreds[9 - hundreds]);
|
||||
if (tens > 0) sb.Append(Tens[9 - tens]);
|
||||
if (units > 0) sb.Append(Units[9 - units]);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Roman numeral that was passed in as either an Arabic numeral
|
||||
/// or a Roman numeral.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="System.String" /> representing a Roman Numeral</returns>
|
||||
public string ToRomanNumeral()
|
||||
{
|
||||
return ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a given number is within the valid range of values for a roman numeral.
|
||||
/// </summary>
|
||||
/// <param name="number">The number to validate.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// $Roman numerals can not be larger than {MaxValue}.
|
||||
/// or
|
||||
/// $Roman numerals can not be smaller than {MinValue}.
|
||||
/// </exception>
|
||||
private static void RangeGuard(int number)
|
||||
{
|
||||
if (number > MaxValue)
|
||||
throw new ArgumentOutOfRangeException(nameof(number), number,
|
||||
$"Roman numerals can not be larger than {MaxValue}.");
|
||||
if (number < MinValue)
|
||||
throw new ArgumentOutOfRangeException(nameof(number), number,
|
||||
$"Roman numerals can not be smaller than {MinValue}.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operators
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator *.
|
||||
/// </summary>
|
||||
/// <param name="firstNumeral">The first numeral.</param>
|
||||
/// <param name="secondNumeral">The second numeral.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static RomanNumeral operator *(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
|
||||
{
|
||||
return new RomanNumeral(firstNumeral._value * secondNumeral._value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator /.
|
||||
/// </summary>
|
||||
/// <param name="numerator">The numerator.</param>
|
||||
/// <param name="denominator">The denominator.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static RomanNumeral operator /(RomanNumeral numerator, RomanNumeral denominator)
|
||||
{
|
||||
return new RomanNumeral(numerator._value / denominator._value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator +.
|
||||
/// </summary>
|
||||
/// <param name="firstNumeral">The first numeral.</param>
|
||||
/// <param name="secondNumeral">The second numeral.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static RomanNumeral operator +(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
|
||||
{
|
||||
return new RomanNumeral(firstNumeral._value + secondNumeral._value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -.
|
||||
/// </summary>
|
||||
/// <param name="firstNumeral">The first numeral.</param>
|
||||
/// <param name="secondNumeral">The second numeral.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static RomanNumeral operator -(RomanNumeral firstNumeral, RomanNumeral secondNumeral)
|
||||
{
|
||||
return new RomanNumeral(firstNumeral._value - secondNumeral._value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interface Implementations
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns></returns>
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is sbyte
|
||||
|| obj is byte
|
||||
|| obj is short
|
||||
|| obj is ushort
|
||||
|| obj is int
|
||||
|| obj is uint
|
||||
|| obj is long
|
||||
|| obj is ulong
|
||||
|| obj is float
|
||||
|| obj is double
|
||||
|| obj is decimal)
|
||||
{
|
||||
var value = (int)obj;
|
||||
return _value.CompareTo(value);
|
||||
}
|
||||
else if (obj is string)
|
||||
{
|
||||
int value;
|
||||
var numeral = obj as string;
|
||||
|
||||
if (TryParse(numeral, out value))
|
||||
{
|
||||
return _value.CompareTo(value);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares to.
|
||||
/// </summary>
|
||||
/// <param name="other">The other.</param>
|
||||
/// <returns></returns>
|
||||
public int CompareTo(RomanNumeral other)
|
||||
{
|
||||
return _value.CompareTo(other._value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equalses the specified other.
|
||||
/// </summary>
|
||||
/// <param name="other">The other.</param>
|
||||
/// <returns></returns>
|
||||
public bool Equals(RomanNumeral other)
|
||||
{
|
||||
return _value == other._value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Roman Numeral which was passed to this Instance
|
||||
/// during creation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents a Roman Numeral.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return ToRomanNumeral(_value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
146
src/NzbDrone.Core/Parser/RomanNumerals/RomanNumeralParser.cs
Normal file
146
src/NzbDrone.Core/Parser/RomanNumerals/RomanNumeralParser.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
|
||||
|
||||
public static class RomanNumeralParser
|
||||
{
|
||||
private const int DICTIONARY_PREPOPULATION_SIZE = 20;
|
||||
|
||||
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralsMapping;
|
||||
|
||||
private static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> _simpleArabicNumeralMappings;
|
||||
|
||||
static RomanNumeralParser()
|
||||
{
|
||||
PopluateDictionariesReasonablyLarge();
|
||||
}
|
||||
|
||||
private static void PopluateDictionariesReasonablyLarge()
|
||||
{
|
||||
if(_simpleArabicNumeralMappings != null || _arabicRomanNumeralsMapping != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_arabicRomanNumeralsMapping = new HashSet<ArabicRomanNumeral>();
|
||||
_simpleArabicNumeralMappings = new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>();
|
||||
foreach (int arabicNumeral in Enumerable.Range(1,DICTIONARY_PREPOPULATION_SIZE +1))
|
||||
{
|
||||
string romanNumeralAsString, arabicNumeralAsString;
|
||||
GenerateRomanNumerals(arabicNumeral, out romanNumeralAsString, out arabicNumeralAsString);
|
||||
ArabicRomanNumeral arm = new ArabicRomanNumeral(arabicNumeral, arabicNumeralAsString, romanNumeralAsString);
|
||||
_arabicRomanNumeralsMapping.Add(arm);
|
||||
|
||||
SimpleArabicNumeral sam = new SimpleArabicNumeral(arabicNumeral);
|
||||
SimpleRomanNumeral srm = new SimpleRomanNumeral(romanNumeralAsString);
|
||||
_simpleArabicNumeralMappings.Add(sam, srm);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateRomanNumerals(int arabicNumeral, out string romanNumeral, out string arabicNumeralAsString)
|
||||
{
|
||||
RomanNumeral romanNumeralObject = new RomanNumeral(arabicNumeral);
|
||||
romanNumeral = romanNumeralObject.ToRomanNumeral();
|
||||
arabicNumeralAsString = Convert.ToString(arabicNumeral);
|
||||
}
|
||||
|
||||
private static HashSet<ArabicRomanNumeral> GenerateAdditionalMappings(int offset, int length)
|
||||
{
|
||||
HashSet<ArabicRomanNumeral> additionalArabicRomanNumerals = new HashSet<ArabicRomanNumeral>();
|
||||
foreach (int arabicNumeral in Enumerable.Range(offset, length))
|
||||
{
|
||||
string romanNumeral;
|
||||
string arabicNumeralAsString;
|
||||
GenerateRomanNumerals(arabicNumeral, out romanNumeral, out arabicNumeralAsString);
|
||||
ArabicRomanNumeral arm = new ArabicRomanNumeral(arabicNumeral, arabicNumeralAsString, romanNumeral);
|
||||
additionalArabicRomanNumerals.Add(arm);
|
||||
}
|
||||
return additionalArabicRomanNumerals;
|
||||
}
|
||||
|
||||
public static HashSet<ArabicRomanNumeral> GetArabicRomanNumeralsMapping(int upToArabicNumber = DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
if (upToArabicNumber == DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
return new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping.Take(upToArabicNumber));
|
||||
}
|
||||
|
||||
if (upToArabicNumber < DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
return
|
||||
(HashSet<ArabicRomanNumeral>)
|
||||
new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping).Take(upToArabicNumber);
|
||||
}
|
||||
if (upToArabicNumber >= DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
if (_arabicRomanNumeralsMapping.Count >= upToArabicNumber)
|
||||
{
|
||||
return new HashSet<ArabicRomanNumeral>(_arabicRomanNumeralsMapping.Take(upToArabicNumber));
|
||||
}
|
||||
HashSet<ArabicRomanNumeral> largerMapping = GenerateAdditionalMappings(DICTIONARY_PREPOPULATION_SIZE + 1, upToArabicNumber);
|
||||
_arabicRomanNumeralsMapping = (HashSet<ArabicRomanNumeral>)_arabicRomanNumeralsMapping.Union(largerMapping);
|
||||
}
|
||||
return _arabicRomanNumeralsMapping;
|
||||
}
|
||||
|
||||
public static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> GetArabicRomanNumeralAsDictionary(
|
||||
int upToArabicNumer = DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
Func
|
||||
<Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>, int,
|
||||
Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>> take =
|
||||
(mapping, amountToTake) =>
|
||||
new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>(
|
||||
mapping.Take(amountToTake).ToDictionary(key => key.Key, value => value.Value));
|
||||
if (upToArabicNumer == DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
return take(_simpleArabicNumeralMappings, upToArabicNumer);
|
||||
}
|
||||
if (upToArabicNumer > DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
if (_simpleArabicNumeralMappings.Count >= upToArabicNumer)
|
||||
{
|
||||
return take(_simpleArabicNumeralMappings, upToArabicNumer);
|
||||
}
|
||||
var moreSimpleNumerals = GenerateAdditionalSimpleNumerals(DICTIONARY_PREPOPULATION_SIZE, upToArabicNumer);
|
||||
_simpleArabicNumeralMappings =
|
||||
(Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>)
|
||||
_simpleArabicNumeralMappings.Union(moreSimpleNumerals);
|
||||
return take(_simpleArabicNumeralMappings, _arabicRomanNumeralsMapping.Count);
|
||||
}
|
||||
if (upToArabicNumer < DICTIONARY_PREPOPULATION_SIZE)
|
||||
{
|
||||
return take(_simpleArabicNumeralMappings, upToArabicNumer);
|
||||
}
|
||||
return _simpleArabicNumeralMappings;
|
||||
}
|
||||
|
||||
|
||||
private static Dictionary<SimpleArabicNumeral, SimpleRomanNumeral> GenerateAdditionalSimpleNumerals(int offset,
|
||||
int length)
|
||||
{
|
||||
Dictionary<SimpleArabicNumeral,SimpleRomanNumeral> moreNumerals = new Dictionary<SimpleArabicNumeral, SimpleRomanNumeral>();
|
||||
foreach (int arabicNumeral in Enumerable.Range(offset, length))
|
||||
{
|
||||
string romanNumeral;
|
||||
string arabicNumeralAsString;
|
||||
GenerateRomanNumerals(arabicNumeral, out romanNumeral, out arabicNumeralAsString);
|
||||
SimpleArabicNumeral san = new SimpleArabicNumeral(arabicNumeral);
|
||||
SimpleRomanNumeral srn = new SimpleRomanNumeral(romanNumeral);
|
||||
moreNumerals.Add(san,srn);
|
||||
}
|
||||
return moreNumerals;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
public class SimpleArabicNumeral
|
||||
{
|
||||
public SimpleArabicNumeral(int numeral)
|
||||
{
|
||||
Numeral = numeral;
|
||||
}
|
||||
|
||||
public int Numeral { get; private set; }
|
||||
public string NumeralAsString => Convert.ToString(Numeral);
|
||||
}
|
||||
}
|
||||
13
src/NzbDrone.Core/Parser/RomanNumerals/SimpleRomanNumeral.cs
Normal file
13
src/NzbDrone.Core/Parser/RomanNumerals/SimpleRomanNumeral.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace NzbDrone.Core.Parser.RomanNumerals
|
||||
{
|
||||
public class SimpleRomanNumeral
|
||||
{
|
||||
public SimpleRomanNumeral(string numeral)
|
||||
{
|
||||
Numeral = numeral;
|
||||
}
|
||||
|
||||
public string Numeral { get; private set; }
|
||||
public string NumeralLowerCase => Numeral.ToLower();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using Marr.Data.QGen;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.RomanNumerals;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using CoreParser = NzbDrone.Core.Parser.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
@@ -28,21 +30,6 @@ namespace NzbDrone.Core.Tv
|
||||
|
||||
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
|
||||
{
|
||||
private readonly Dictionary<string, string> romanNumeralsMapper = new Dictionary<string, string>
|
||||
{
|
||||
{ "1", "I"},
|
||||
{ "2", "II"},
|
||||
{ "3", "III"},
|
||||
{ "4", "IV"},
|
||||
{ "5", "V"},
|
||||
{ "6", "VI"},
|
||||
{ "7", "VII"},
|
||||
{ "8", "VII"},
|
||||
{ "9", "IX"},
|
||||
{ "10", "X"},
|
||||
|
||||
}; //If a movie has more than 10 parts fuck 'em.
|
||||
|
||||
protected IMainDatabase _database;
|
||||
|
||||
public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
@@ -58,94 +45,12 @@ namespace NzbDrone.Core.Tv
|
||||
|
||||
public Movie FindByTitle(string cleanTitle)
|
||||
{
|
||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||
|
||||
var cleanRoman = cleanTitle;
|
||||
|
||||
var cleanNum = cleanTitle;
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
|
||||
{
|
||||
string num = entry.Key;
|
||||
string roman = entry.Value.ToLower();
|
||||
|
||||
cleanRoman = cleanRoman.Replace(num, roman);
|
||||
|
||||
cleanNum = cleanNum.Replace(roman, num);
|
||||
}
|
||||
|
||||
var result = Query.Where(s => s.CleanTitle == cleanTitle).FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman).FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
var movies = this.All();
|
||||
|
||||
result = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle ||
|
||||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman ||
|
||||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum)).FirstOrDefault();
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return FindByTitle(cleanTitle, null);
|
||||
}
|
||||
|
||||
public Movie FindByTitle(string cleanTitle, int year)
|
||||
{
|
||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||
|
||||
var cleanRoman = cleanTitle;
|
||||
|
||||
var cleanNum = cleanTitle;
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in romanNumeralsMapper)
|
||||
{
|
||||
string num = entry.Key;
|
||||
string roman = entry.Value.ToLower();
|
||||
|
||||
cleanRoman = cleanRoman.Replace(num, roman);
|
||||
|
||||
cleanNum = cleanNum.Replace(roman, num);
|
||||
}
|
||||
|
||||
var results = Query.Where(s => s.CleanTitle == cleanTitle);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
results = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
var movies = this.All();
|
||||
|
||||
var listResults = movies.Where(m => m.AlternativeTitles.Any(t => Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanTitle ||
|
||||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanRoman ||
|
||||
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum));
|
||||
|
||||
return listResults.Where(m => m.Year == year).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
return results.Where(m => m.Year == year).FirstOrDefault();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return results.Where(m => m.Year == year).FirstOrDefault();
|
||||
}
|
||||
return FindByTitle(cleanTitle, year as int?);
|
||||
}
|
||||
|
||||
public Movie FindByImdbId(string imdbid)
|
||||
@@ -318,6 +223,46 @@ namespace NzbDrone.Core.Tv
|
||||
return string.Format("({0})", string.Join(" OR ", clauses));
|
||||
}
|
||||
|
||||
private Movie FindByTitle(string cleanTitle, int? year)
|
||||
{
|
||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||
string cleanTitleWithRomanNumbers = cleanTitle;
|
||||
string cleanTitleWithArabicNumbers = cleanTitle;
|
||||
|
||||
|
||||
foreach (ArabicRomanNumeral arabicRomanNumeral in RomanNumeralParser.GetArabicRomanNumeralsMapping())
|
||||
{
|
||||
string arabicNumber = arabicRomanNumeral.ArabicNumeralAsString;
|
||||
string romanNumber = arabicRomanNumeral.RomanNumeral;
|
||||
cleanTitleWithRomanNumbers = cleanTitleWithRomanNumbers.Replace(arabicNumber, romanNumber);
|
||||
cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber);
|
||||
}
|
||||
|
||||
IEnumerable<Movie> results = Query.Where(s => s.CleanTitle == cleanTitle);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
results = Query.Where(movie => movie.CleanTitle == cleanTitleWithArabicNumbers) ??
|
||||
Query.Where(movie => movie.CleanTitle == cleanTitleWithRomanNumbers);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
IEnumerable<Movie> movies = All();
|
||||
Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower());
|
||||
Func<IEnumerable<string>, string, bool> altTitleComparer =
|
||||
(alternativeTitles, atitle) =>
|
||||
alternativeTitles.Any(altTitle => altTitle == titleCleaner(atitle));
|
||||
|
||||
results = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
|
||||
altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) ||
|
||||
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers));
|
||||
}
|
||||
}
|
||||
return year.HasValue
|
||||
? results?.FirstOrDefault(movie => movie.Year == year.Value)
|
||||
: results?.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Movie FindByTmdbId(int tmdbid)
|
||||
{
|
||||
return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.24720.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{57A04B72-8088-4F75-A582-1158CF8291F7}"
|
||||
|
||||
@@ -33,11 +33,15 @@ module.exports = Marionette.Layout.extend({
|
||||
id : 'add-movies-screen'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
initialize : function(options) {
|
||||
ProfileCollection.fetch();
|
||||
RootFolderCollection.fetch().done(function() {
|
||||
RootFolderCollection.synced = true;
|
||||
});
|
||||
|
||||
if (options.action === "search") {
|
||||
this._addMovies(options);
|
||||
}
|
||||
},
|
||||
|
||||
_toggleExisting : function(e) {
|
||||
@@ -50,7 +54,7 @@ module.exports = Marionette.Layout.extend({
|
||||
|
||||
onShow : function() {
|
||||
|
||||
this.workspace.show(new AddMoviesView());
|
||||
this.workspace.show(new AddMoviesView(this.options));
|
||||
this.ui.$existing.hide();
|
||||
},
|
||||
|
||||
@@ -72,8 +76,8 @@ module.exports = Marionette.Layout.extend({
|
||||
AppLayout.modalRegion.show(this.rootFolderLayout);
|
||||
},
|
||||
|
||||
_addMovies : function() {
|
||||
this.workspace.show(new AddMoviesView());
|
||||
_addMovies : function(options) {
|
||||
this.workspace.show(new AddMoviesView(options));
|
||||
},
|
||||
|
||||
_addFromList : function() {
|
||||
|
||||
@@ -26,7 +26,6 @@ module.exports = Marionette.Layout.extend({
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
|
||||
this.isExisting = options.isExisting;
|
||||
this.collection = new AddMoviesCollection();
|
||||
|
||||
@@ -49,6 +48,11 @@ module.exports = Marionette.Layout.extend({
|
||||
});
|
||||
|
||||
this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
|
||||
|
||||
if (options.action === "search") {
|
||||
this.search({term: options.query});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
|
||||
@@ -13,6 +13,10 @@ module.exports = NzbDroneCell.extend({
|
||||
var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
|
||||
|
||||
|
||||
if (status === 'released') {
|
||||
this.$el.html('<i class="icon-sonarr-movie-released grid-icon" title="Released"></i>');
|
||||
this._setStatusWeight(3);
|
||||
}
|
||||
if (status === 'inCinemas') {
|
||||
this.$el.html('<i class="icon-sonarr-movie-cinemas grid-icon" title="In Cinemas"></i>');
|
||||
this._setStatusWeight(2);
|
||||
|
||||
@@ -213,6 +213,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.no-movies-found {
|
||||
color: #fff;
|
||||
font-size: 1em;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: #cccccc;
|
||||
opacity: 0.25;
|
||||
|
||||
@@ -19,9 +19,9 @@ module.exports = NzbDroneController.extend({
|
||||
this.showMainRegion(new AddSeriesLayout({ action : action }));
|
||||
},
|
||||
|
||||
addMovies : function(action) {
|
||||
addMovies : function(action, query) {
|
||||
this.setTitle("Add Movie");
|
||||
this.showMainRegion(new AddMoviesLayout({action : action}));
|
||||
this.showMainRegion(new AddMoviesLayout({ action : action, query : query }));
|
||||
},
|
||||
|
||||
calendar : function() {
|
||||
|
||||
@@ -34,7 +34,7 @@ var view = Marionette.ItemView.extend({
|
||||
|
||||
_onBeforeSave : function() {
|
||||
var qualityId = this.ui.quality.val();
|
||||
var quality = this.qualities.find(function(m){return m.get("quality").id === qualityId;}).get("quality");
|
||||
var quality = this.qualities.find(function(m){return m.get("quality").id === parseInt(qualityId);}).get("quality");
|
||||
var mQuality = this.model.get("quality");
|
||||
mQuality.quality = quality;
|
||||
this.model.set({ quality : mQuality });
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
<div class="row">
|
||||
<div class="well col-md-12">
|
||||
<i class="icon-sonarr-comment"/>
|
||||
You must be new around here, You should add some movies.
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<a href="/addmovies" class='btn btn-lg btn-block btn-success x-add-movies'>
|
||||
<i class='icon-sonarr-add'></i>
|
||||
Add Movie
|
||||
</a>
|
||||
You must be new around here, before you start adding movies you may want to check out the following links on our <a href="https://github.com/Radarr/Radarr/wiki">wiki</a>:
|
||||
<ul>
|
||||
<li><a href="https://github.com/Radarr/Radarr/wiki/Setup-Guide">Our setup guide</a></li>
|
||||
<li><a href="https://github.com/Radarr/Radarr/wiki/Common-Problems">Common Problems</a></li>
|
||||
<li><a href="https://github.com/Radarr/Radarr/wiki/FAQ">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -315,8 +315,8 @@ module.exports = Marionette.Layout.extend({
|
||||
|
||||
//this._showToolbar();
|
||||
//debugger;
|
||||
self = this;
|
||||
setTimeout(function(){self._showToolbar();}, 0);//wtf???
|
||||
var self = this;
|
||||
setTimeout(function(){self._showToolbar();}, 0); // jshint ignore:line
|
||||
//this._renderView();
|
||||
},
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ var filterModes = {
|
||||
"inCinemas",
|
||||
//function(model) { return model.getStatus() == "inCinemas"; }
|
||||
]
|
||||
}//Hacky, I know
|
||||
}; //Hacky, I know
|
||||
|
||||
|
||||
var Collection = PageableCollection.extend({
|
||||
@@ -273,7 +273,7 @@ var filterMode = Config.getValue("series.filterMode", "all");
|
||||
var sortKey = Config.getValue("movie.sortKey", "sortTitle");
|
||||
var sortDir = Config.getValue("movie.sortDirection", -1);
|
||||
var sortD = "asc";
|
||||
if (sortDir == 1) {
|
||||
if (sortDir === 1) {
|
||||
sortD = "desc";
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ module.exports = NzbDroneController.extend({
|
||||
this._renderMovieDetails(query);
|
||||
//debugger;
|
||||
} else {
|
||||
self = this;
|
||||
var self = this;
|
||||
$.getJSON(window.NzbDrone.ApiRoot + '/movie/titleslug/'+query, { }, function(data) {
|
||||
FullMovieCollection.add(data)
|
||||
FullMovieCollection.add(data);
|
||||
self._renderMovieDetails(query);
|
||||
});
|
||||
this.listenTo(FullMovieCollection, 'sync', function(model, options) {
|
||||
|
||||
@@ -14,7 +14,7 @@ var substringMatcher = function() {
|
||||
var matches = _.select(FullMovieCollection.toJSON(), function(series) {
|
||||
return series.title.toLowerCase().indexOf(q.toLowerCase()) > -1;
|
||||
});
|
||||
cb(matches);
|
||||
cb(matches);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -28,6 +28,11 @@ $.fn.bindSearch = function() {
|
||||
displayKey : function(series) {
|
||||
return series.title + ' (' + series.year + ')';
|
||||
},
|
||||
templates : {
|
||||
empty : function(input) {
|
||||
return '<div class="tt-dataset-series"><span class="tt-suggestions" style="display: block;"><div class="tt-suggestion"><p style="white-space: normal;"><a class="no-movies-found" href="/addmovies/search/' + input.query + '">Search for "' + input.query + '"</a></p></div></span></div>';
|
||||
},
|
||||
},
|
||||
source : substringMatcher()
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
var Marionette = require('marionette');
|
||||
var AsModelBoundView = require('../../../Mixins/AsModelBoundView');
|
||||
var AsValidatedView = require('../../../Mixins/AsValidatedView');
|
||||
require('../../../Mixins/TagInput');
|
||||
require('bootstrap');
|
||||
require('bootstrap.tagsinput');
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template : 'Settings/Indexers/Options/IndexerOptionsViewTemplate'
|
||||
template : 'Settings/Indexers/Options/IndexerOptionsViewTemplate',
|
||||
|
||||
ui : {
|
||||
hcwhitelist : '.x-hcwhitelist',
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
this.ui.hcwhitelist.tagsinput({
|
||||
trimValue : true,
|
||||
allowDuplicates: true,
|
||||
tagClass : 'label label-success'
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
module.exports = view;
|
||||
module.exports = view;
|
||||
|
||||
@@ -37,6 +37,39 @@
|
||||
<input type="number" name="rssSyncInterval" class="form-control" min="0" max="720"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Whitelisted Subtitle Tags Tags</label>
|
||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="All subtitle tags set here will not be considered hardcoded (e.g. dksub). This field is caseinsensitive. Tags must be put in singular (dksub instead of dksubs)."/>
|
||||
</div>
|
||||
<div class="col-sm-2 col-sm-pull-1">
|
||||
<input type="text" name="whitelistedHardcodedSubs" class="form-control x-hcwhitelist"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Allow Hardcoded Subs</label>
|
||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="If set to yes, all detected hardcoded subs will be downloaded automatically."/>
|
||||
</div>
|
||||
<div class="col-sm-2 col-sm-pull-1">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="allowHardcodedSubs" class="x-completed-download-handling"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<legend>Availability Options</legend>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Availability Delay</label>
|
||||
|
||||
@@ -32,7 +32,7 @@ var view = Marionette.ItemView.extend({
|
||||
var refresh=params.get('refresh');
|
||||
if (oauth && refresh){
|
||||
//var callback_url = window.location.href;
|
||||
history.pushState('object', 'title', (window.location.href).replace(window.location.search, ''));
|
||||
history.pushState('object', 'title', (window.location.href).replace(window.location.search, '')); // jshint ignore:line
|
||||
this.ui.authToken.val(oauth).trigger('change');
|
||||
this.ui.refreshToken.val(refresh).trigger('change');
|
||||
//Config.setValue("traktAuthToken", oauth);
|
||||
@@ -101,7 +101,7 @@ var view = Marionette.ItemView.extend({
|
||||
//if (event.item.startsWith('tt')) {
|
||||
// newText = newText+'['+event.item+']';
|
||||
//}
|
||||
event.item = response['titleSlug'];//+' ('+response['year']+')-'+response['tmdbId'];
|
||||
event.item = response.titleSlug;//+' ('+response['year']+')-'+response['tmdbId'];
|
||||
});
|
||||
|
||||
promise.error(function(request, status, error) {
|
||||
|
||||
@@ -3,6 +3,7 @@ var AppLayout = require('../AppLayout');
|
||||
var Marionette = require('marionette');
|
||||
var NotFoundView = require('./NotFoundView');
|
||||
var Messenger = require('./Messenger');
|
||||
var Config = require('../Config');
|
||||
|
||||
module.exports = Marionette.AppRouter.extend({
|
||||
initialize : function() {
|
||||
@@ -41,7 +42,7 @@ module.exports = Marionette.AppRouter.extend({
|
||||
var label = window.location.pathname === window.NzbDrone.UrlBase + '/system/updates' ? 'Reload' : 'View Changes';
|
||||
|
||||
Messenger.show({
|
||||
message : 'Radarr has been updated',
|
||||
message : 'Radarr has been updated, some UI configuration has been reset',
|
||||
hideAfter : 0,
|
||||
id : 'sonarrUpdated',
|
||||
actions : {
|
||||
@@ -54,6 +55,12 @@ module.exports = Marionette.AppRouter.extend({
|
||||
}
|
||||
});
|
||||
|
||||
// Only for pre-release development
|
||||
var pageSize = Config.getValue("pageSize");
|
||||
window.localStorage.clear();
|
||||
Config.setValue("pageSize", pageSize);
|
||||
// Remove above when out of pre-release :)
|
||||
|
||||
this.pendingUpdate = true;
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user