1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-14 15:46:43 -04:00

Compare commits

...

58 Commits

Author SHA1 Message Date
Leonardo Galli
81ebbcad70 Now hidden files are ignored :). Fixes #166. 2017-01-11 23:05:11 +01:00
Leonardo Galli
92e9dc6ee1 Fix sorting of unkown release date. 2017-01-11 22:39:07 +01:00
Leonardo Galli
5e8b617625 Sorting now working according to quality in release collection. Fixes #85. 2017-01-11 22:27:37 +01:00
Leonardo Galli
2cbe17151d Correctly check if inCinemas date is present. Creates issue with sorting, but eh. Fixes 140. 2017-01-11 22:02:24 +01:00
Devin Buhl
95bd615718 Merge pull request #169 from Radarr/patch/big-movie-revenues
Problem with Avatar (2009) #168
2017-01-11 15:53:16 -05:00
Leonardo Galli
8e8c4ff497 Update parser to recognize [] and year at the beginning. Fixes #155, fixes #137 and fixes #136. 2017-01-11 21:49:59 +01:00
Devin Buhl
5eddcc1660 Problem with Avatar (2009) #168 2017-01-11 15:48:19 -05:00
Tim Turner
d42165a93a Clean up basic movie naming
Fixes #132
2017-01-11 15:03:55 -05:00
Devin Buhl
99012d8a40 Merge pull request #163 from Radarr/patch/fix-plex-notifs
update plex movie libraries instead of series
2017-01-11 12:35:01 -05:00
Devin Buhl
8bc42c76e4 update plex movie libraries instead of series 2017-01-11 12:25:27 -05:00
Leonardo Galli
b7f72c6259 Merge pull request #160 from Radarr/patch/fix-newznab
fix some spelling mistakes and update the newznab api 'imdbid'
2017-01-11 16:59:09 +01:00
Devin Buhl
64ef8db037 fix some spelling mistakes and update the newznab api 'imdbid' 2017-01-11 10:51:06 -05:00
Leonardo Galli
3b5887bf09 Merge pull request #133 from Radarr/fix-manual-import
Fix Manual Import & Drone Factory
2017-01-11 16:39:09 +01:00
Leonardo Galli
40a75949ba Merge pull request #156 from CHBMB/develop
aarch64 docker container added to readme.
2017-01-11 16:33:36 +01:00
Neil
6747267d19 aarch64 docker container added to readme. 2017-01-11 15:12:26 +00:00
Devin Buhl
13db03f97c Merge pull request #154 from Radarr/patch/remove-fanzub
removed indexer Fanzub - site shutdown
2017-01-11 10:12:03 -05:00
Devin Buhl
cd68eea790 removed indexer Fanzub - site shutdown 2017-01-11 09:40:42 -05:00
Devin Buhl
cfe55d00ae Merge pull request #148 from Radarr/patch/newznab-imdb
search imdbid for usenet indexers that support it
2017-01-11 08:53:28 -05:00
Devin Buhl
1d88313424 #146 search imdbid for usenet indexers that support it 2017-01-11 08:42:29 -05:00
Mike
9513068467 Get rid of unnecessary AppVeyor builds. 2017-01-11 08:57:15 +01:00
Tim Turner
9206258370 Fixes Manual Import and DroneFactory
Automatic Import still doesn't work - waiting for answer from Sonarr
team.
2017-01-10 21:12:47 -05:00
Tim Turner
0fa1509ca6 Manual Import works
Automatically importing still broken
2017-01-10 19:37:17 -05:00
Devin Buhl
c5eb772f7a Merge pull request #130 from Radarr/newznab-patch
Add category 2035 to Newznab providers for WEB-DL search support. #123
2017-01-10 19:14:21 -05:00
Devin Buhl
04fec6d4d8 Add category 2035 to Newznab providers for WEB-DL search support. #123 2017-01-10 19:09:12 -05:00
Devin Buhl
d0b5e380d7 Merge pull request #122 from Radarr/transmission-patch
Fix transmission
2017-01-10 17:43:16 -05:00
Devin Buhl
df69c58d2b Fix transmission 2017-01-10 17:37:23 -05:00
Leonardo Galli
27114c9399 Merge pull request #119 from CHBMB/develop
Update readme.md
2017-01-10 23:25:05 +01:00
Neil
cfca07996b Update readme.md 2017-01-10 22:19:12 +00:00
Leonardo Galli
e97b80f630 Update readme.md 2017-01-10 23:19:00 +01:00
Devin Buhl
792679fd81 Merge pull request #118 from lxh87/patch-1
Update SystemLayout.js
2017-01-10 16:50:15 -05:00
lxh87
ecf47d4b17 Update SystemLayout.js
Change restart/shutdown message from"Sonarr" to "Radarr"
2017-01-10 22:42:44 +01:00
Leonardo Galli
3b1d49a78f Update readme.md 2017-01-10 22:30:36 +01:00
Devin Buhl
753f3eb863 Merge pull request #117 from Radarr/wombles-patch
Fix Wombles for movies
2017-01-10 16:29:02 -05:00
Devin Buhl
9fc2d22d19 Fix Wombles for movies 2017-01-10 16:28:16 -05:00
Tim Turner
3cb42f06c2 Clean up Feature Requests 2017-01-10 16:05:56 -05:00
Devin Buhl
a6c396a595 Merge pull request #111 from Radarr/addmovie-patch
fix #108 - Links to IMDB not working when searching for movies
2017-01-10 15:45:39 -05:00
Devin Buhl
bac9076b1e fix #108 - Links to IMDB not working when searching for movies 2017-01-10 15:39:25 -05:00
Tim Turner
b228273be0 Update Info page.
Updates #73
2017-01-10 15:34:43 -05:00
Leonardo Galli
e7fa4cba19 Fix download rejections being ignored. 2017-01-10 21:25:36 +01:00
Leonardo Galli
0506cc4185 Merge branch 'develop' of https://github.com/galli-leo/Radarr into develop 2017-01-10 20:07:01 +01:00
Leonardo Galli
cde8b4dd97 Added MovieFileResource. This allows the UI to interact with movie files better. Downloaded Quality is now shown in the table. 2017-01-10 20:06:53 +01:00
Leonardo Galli
0a0a44162c Merge pull request #105 from galli-leo/linux-tmp-path-patch
Fixes #104 - Backup/update fail Access to the path "/tmp/nzbdrone_backup/config.xml" is denied
2017-01-10 19:31:12 +01:00
Devin Buhl
125f46fbec Fixes #104 - Backup/update fail Access to the path "/tmp/nzbdrone_backup/config.xml" is denied 2017-01-10 13:21:54 -05:00
Leonardo Galli
f3222ca7c7 Merge pull request #102 from galli-leo/addmovies-patch
When adding a movie, monitored toggle doesn't apply and always defaults to being monitored
2017-01-10 19:21:24 +01:00
Devin Buhl
d252a8b232 Fixes #100 - When adding a movie, monitored toggle doesn't apply and always defaults to being monitored 2017-01-10 12:39:28 -05:00
Leonardo Galli
48559cf964 Updated legend with number of movies 2017-01-10 17:36:04 +01:00
Leonardo Galli
c734e8bc7e Update legend for missing status colors. 2017-01-10 17:07:32 +01:00
Leonardo Galli
3c7d7756e6 Fix issues with media managment config not getting saved. 2017-01-10 17:05:37 +01:00
Leonardo Galli
683bda49d8 Movie Editor works now. Fixes #99. 2017-01-10 16:51:56 +01:00
Leonardo Galli
52fb29ee18 Fixes a few things with importing: Sample check is done even when file is already in movie folder. Fixed importing of movies with "DC". 2017-01-10 16:23:07 +01:00
Leonardo Galli
236e16c9a5 Update sample detection runtime minutes. Some trailers can be long. 2017-01-10 15:43:35 +01:00
Leonardo Galli
0584038273 Fix queue specification. 2017-01-10 15:33:39 +01:00
Leonardo Galli
6685aea144 Movie search should now work, even when titles returned from the TMDB do not have a release date set. Fixes #27. 2017-01-10 15:29:20 +01:00
Leonardo Galli
e4f7aa52df History now correctly shows movie title. Fixes #92 2017-01-10 15:15:15 +01:00
Leonardo Galli
f61c4feb00 Redownloading failed downloads works again. Fixes #89. 2017-01-10 14:15:27 +01:00
Leonardo Galli
04e8c635e0 Use correct Modal for editing movies in table view. Fixes #90 2017-01-10 14:05:01 +01:00
Leonardo Galli
93ea5cfdee Merge pull request #88 from schumi2004/develop
Replace Sonarr with Radarr in Test notification messages
2017-01-10 11:31:39 +01:00
schumi2004
1b7288e7cb Replace Sonarr with Radarr in Test notification messages 2017-01-10 11:21:09 +01:00
92 changed files with 1211 additions and 721 deletions

View File

@@ -35,4 +35,11 @@ artifacts:
cache:
- '%USERPROFILE%\.nuget\packages'
- node_modules
- node_modules
only_commits:
files:
- src/
- osx/
- gulp/
- logo/

View File

@@ -2,7 +2,7 @@
| Service | Master | Develop |
|----------|:---------------------------:|:----------------------------:|
| AppVeyor | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/develop.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) |
| AppVeyor | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr-usby1/develop.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
| Travis | [![Travis](https://img.shields.io/travis/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://travis-ci.org/galli-leo/Radarr) | [![Travis](https://img.shields.io/travis/galli-leo/Radarr/develop.svg?maxAge=60&style=flat-square)](https://travis-ci.org/galli-leo/Radarr) |
This fork of Sonarr aims to turn it into something like Couchpotato.
@@ -26,6 +26,11 @@ This fork of Sonarr aims to turn it into something like Couchpotato.
## Download
The latest precompiled binary versions can be found here: https://github.com/galli-leo/Radarr/releases.
Docker containers from [linuxserver.io](https://linuxserver.io) can be found here.
* [Radarr (x64)](https://hub.docker.com/r/linuxserver/radarr/)
* [Radarr (armhf)](https://hub.docker.com/r/lsioarmhf/radarr/)
* [Radarr (aarch64)](https://hub.docker.com/r/lsioarmhf/radarr-aarch64/)
For more up to date versions (but also sometimes broken), daily builds can be found here:
* [OSX](https://leonardogalli.ch/radarr/builds/latest.php?os=osx)
* [Windows](https://leonardogalli.ch/radarr/builds/latest.php?os=windows)

View File

@@ -34,11 +34,11 @@ namespace NzbDrone.Api.Config
Get["/samples"] = x => GetExamples(this.Bind<NamingConfigResource>());
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5);
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
/*SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();*/
SharedValidator.RuleFor(c => c.StandardMovieFormat).ValidMovieFormat();
SharedValidator.RuleFor(c => c.MovieFolderFormat).ValidMovieFolderFormat();
}

View File

@@ -25,7 +25,7 @@ namespace NzbDrone.Api.Frontend.Mappers
public override bool CanHandle(string resourceUrl)
{
return resourceUrl.StartsWith("/backup/") && resourceUrl.ContainsIgnoreCase("nzbdrone_backup_") && resourceUrl.EndsWith(".zip");
return resourceUrl.StartsWith("/backup/") && resourceUrl.ContainsIgnoreCase("radarr_backup_") && resourceUrl.EndsWith(".zip");
}
}
}

View File

@@ -97,7 +97,7 @@ namespace NzbDrone.Api.Indexers
{
Guid = releaseInfo.Guid,
Quality = parsedMovieInfo.Quality,
//QualityWeight
QualityWeight = parsedMovieInfo.Quality.Quality.Id, //Id kinda hacky for wheight, but what you gonna do? TODO: Fix this shit!
Age = releaseInfo.Age,
AgeHours = releaseInfo.AgeHours,
AgeMinutes = releaseInfo.AgeMinutes,

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Movie
{
public class MovieEditorModule : NzbDroneApiModule
{
private readonly IMovieService _movieService;
public MovieEditorModule(IMovieService movieService)
: base("/movie/editor")
{
_movieService = movieService;
Put["/"] = Movie => SaveAll();
}
private Response SaveAll()
{
var resources = Request.Body.FromJson<List<MovieResource>>();
var Movie = resources.Select(MovieResource => MovieResource.ToModel(_movieService.GetMovie(MovieResource.Id))).ToList();
return _movieService.UpdateMovie(Movie)
.ToResource()
.AsResponse(HttpStatusCode.Accepted);
}
}
}

View File

@@ -119,6 +119,7 @@
<Compile Include="Movies\MovieModule.cs" />
<Compile Include="Movies\RenameMovieModule.cs" />
<Compile Include="Movies\RenameMovieResource.cs" />
<Compile Include="Movies\MovieEditorModule.cs" />
<Compile Include="Parse\ParseModule.cs" />
<Compile Include="Parse\ParseResource.cs" />
<Compile Include="ManualImport\ManualImportModule.cs" />
@@ -231,6 +232,7 @@
<Compile Include="RootFolders\RootFolderResource.cs" />
<Compile Include="SeasonPass\SeasonPassResource.cs" />
<Compile Include="Series\AlternateTitleResource.cs" />
<Compile Include="Series\MovieFileResource.cs" />
<Compile Include="Series\SeasonResource.cs" />
<Compile Include="SeasonPass\SeasonPassModule.cs" />
<Compile Include="Series\SeriesEditorModule.cs" />

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Api.Movie
{
public class MovieFileResource : RestResource
{
public MovieFileResource()
{
}
//Todo: Sorters should be done completely on the client
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
//Todo: We should get the entire Profile instead of ID and Name separately
public int MovieId { get; set; }
public string RelativePath { get; set; }
public string Path { get; set; }
public long Size { get; set; }
public DateTime DateAdded { get; set; }
public string SceneName { get; set; }
public string ReleaseGroup { get; set; }
public QualityModel Quality { get; set; }
public MovieResource Movie { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
}
public static class MovieFileResourceMapper
{
public static MovieFileResource ToResource(this MovieFile model)
{
if (model == null) return null;
MovieResource movie = null;
if (model.Movie != null)
{
model.Movie.LazyLoad();
if (model.Movie.Value != null)
{
//movie = model.Movie.Value.ToResource();
}
}
return new MovieFileResource
{
Id = model.Id,
RelativePath = model.RelativePath,
Path = model.Path,
Size = model.Size,
DateAdded = model.DateAdded,
ReleaseGroup = model.ReleaseGroup,
Quality = model.Quality,
Movie = movie,
};
}
public static MovieFile ToModel(this MovieFileResource resource)
{
if (resource == null) return null;
return new MovieFile
{
};
}
public static List<MovieFileResource> ToResource(this IEnumerable<MovieFile> movies)
{
return movies.Select(ToResource).ToList();
}
}
}

View File

@@ -55,6 +55,7 @@ namespace NzbDrone.Api.Movie
public AddMovieOptions AddOptions { get; set; }
public Ratings Ratings { get; set; }
public List<string> AlternativeTitles { get; set; }
public MovieFileResource MovieFile { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
@@ -84,6 +85,7 @@ namespace NzbDrone.Api.Movie
long size = 0;
bool downloaded = false;
MovieFileResource movieFile = null;
if(model.MovieFile != null)
@@ -95,6 +97,7 @@ namespace NzbDrone.Api.Movie
{
size = model.MovieFile.Value.Size;
downloaded = true;
movieFile = model.MovieFile.Value.ToResource();
}
return new MovieResource
@@ -140,7 +143,8 @@ namespace NzbDrone.Api.Movie
Added = model.Added,
AddOptions = model.AddOptions,
AlternativeTitles = model.AlternativeTitles,
Ratings = model.Ratings
Ratings = model.Ratings,
MovieFile = movieFile
};
}

View File

@@ -1,51 +0,0 @@
using System;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Fanzub;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.FanzubTests
{
[TestFixture]
public class FanzubFixture : CoreTest<Fanzub>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "Fanzub",
Settings = new FanzubSettings()
};
}
[Test]
public void should_parse_recent_feed_from_fanzub()
{
var recentFeed = ReadAllText(@"Files/Indexers/Fanzub/fanzub.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(3);
var releaseInfo = releases.First();
releaseInfo.Title.Should().Be("[Vivid] Hanayamata - 10 [A33D6606]");
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
releaseInfo.DownloadUrl.Should().Be("http://fanzub.com/nzb/296464/Vivid%20Hanayamata%20-%2010.nzb");
releaseInfo.InfoUrl.Should().BeNullOrEmpty();
releaseInfo.CommentUrl.Should().BeNullOrEmpty();
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("2014/09/13 12:56:53"));
releaseInfo.Size.Should().Be(556246858);
}
}
}

View File

@@ -249,7 +249,6 @@
<Compile Include="IndexerTests\NewznabTests\NewznabFixture.cs" />
<Compile Include="IndexerTests\NewznabTests\NewznabRequestGeneratorFixture.cs" />
<Compile Include="IndexerTests\NewznabTests\NewznabSettingFixture.cs" />
<Compile Include="IndexerTests\FanzubTests\FanzubFixture.cs" />
<Compile Include="IndexerTests\OmgwtfnzbsTests\OmgwtfnzbsFixture.cs" />
<Compile Include="IndexerTests\SeasonSearchFixture.cs" />
<Compile Include="IndexerTests\TestIndexer.cs" />

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Backup
private string _backupTempFolder;
private static readonly Regex BackupFileRegex = new Regex(@"nzbdrone_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex BackupFileRegex = new Regex(@"radarr_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public BackupService(IMainDatabase maindDb,
IDiskTransferService diskTransferService,
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Backup
_archiveService = archiveService;
_logger = logger;
_backupTempFolder = Path.Combine(_appFolderInfo.TempFolder, "nzbdrone_backup");
_backupTempFolder = Path.Combine(_appFolderInfo.TempFolder, "radarr_backup");
}
public void Backup(BackupType backupType)
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Backup
_diskProvider.EnsureFolder(_backupTempFolder);
_diskProvider.EnsureFolder(GetBackupFolder(backupType));
var backupFilename = string.Format("nzbdrone_backup_{0:yyyy.MM.dd_HH.mm.ss}.zip", DateTime.Now);
var backupFilename = string.Format("radarr_backup_{0:yyyy.MM.dd_HH.mm.ss}.zip", DateTime.Now);
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
Cleanup();

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(114)]
public class remove_fanzub : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.FromTable("Indexers").Row(new { Implementation = "Fanzub" });
}
}
}

View File

@@ -83,7 +83,7 @@ namespace NzbDrone.Core.DecisionEngine
{
if (parsedEpisodeInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
{
remoteEpisode.DownloadAllowed = true;
remoteEpisode.DownloadAllowed = false;
decision = new DownloadDecision(remoteEpisode, new Rejection("Hardcoded subs found: " + parsedEpisodeInfo.Quality.HardcodedSubs));
}
else
@@ -257,7 +257,7 @@ namespace NzbDrone.Core.DecisionEngine
}
catch (NotImplementedException e)
{
_logger.Info("Spec " + spec.GetType().Name + " does not care about movies.");
_logger.Trace("Spec " + spec.GetType().Name + " does not care about movies.");
}
catch (Exception e)
{
@@ -265,7 +265,6 @@ namespace NzbDrone.Core.DecisionEngine
e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title + ", with spec: " + spec.GetType().Name);
return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));//TODO UPDATE SPECS!
return null;
}
return null;

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
foreach (var remoteEpisode in matchingSeries)
{
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteEpisode.ParsedMovieInfo.Quality);
if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Movie.Profile, remoteEpisode.ParsedMovieInfo.Quality, subject.ParsedMovieInfo.Quality))
{

View File

@@ -165,6 +165,31 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return hash;
}
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
{
_proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings);
if (remoteMovie.Release.Age < 14 && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
remoteMovie.Release.Age > 14 && Settings.OlderTvPriority == (int)TransmissionPriority.First)
{
_proxy.MoveTorrentToTopInQueue(hash, Settings);
}
return hash;
}
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
{
_proxy.AddTorrentFromData(fileContent, GetDownloadDirectory(), Settings);
if (remoteMovie.Release.Age < 14 && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
remoteMovie.Release.Age > 14 && Settings.OlderTvPriority == (int)TransmissionPriority.First)
{
_proxy.MoveTorrentToTopInQueue(hash, Settings);
}
return hash;
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());

View File

@@ -56,10 +56,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
public string TvDirectory { get; set; }
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing movies that we're released within the last 14 days")]
public int RecentTvPriority { get; set; }
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing movies that we're released over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]

View File

@@ -94,7 +94,6 @@ namespace NzbDrone.Core.Download
return;
}
var series = _parsingService.GetSeries(trackedDownload.DownloadItem.Title);
if (series == null)
@@ -156,7 +155,7 @@ namespace NzbDrone.Core.Download
trackedDownload.Warn(statusMessages);
}
}
else
else if (trackedDownload.RemoteEpisode.Series != null)
{
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);

View File

@@ -13,6 +13,7 @@ namespace NzbDrone.Core.Download
}
public int SeriesId { get; set; }
public int MovieId { get; set; }
public List<int> EpisodeIds { get; set; }
public QualityModel Quality { get; set; }
public string SourceTitle { get; set; }

View File

@@ -88,6 +88,7 @@ namespace NzbDrone.Core.Download
var downloadFailedEvent = new DownloadFailedEvent
{
SeriesId = historyItem.SeriesId,
MovieId = historyItem.MovieId,
EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
Quality = historyItem.Quality,
SourceTitle = historyItem.SourceTitle,

View File

@@ -51,6 +51,12 @@ namespace NzbDrone.Core.Download
continue;
}
if (report.Rejections.Any())
{
_logger.Debug("Rejecting release {0} because {1}", report.ToString(), report.Rejections.First().Reason);
continue;
}
if (remoteMovie == null || remoteMovie.Movie == null)
{
continue;

View File

@@ -34,6 +34,15 @@ namespace NzbDrone.Core.Download
return;
}
if (message.MovieId != 0)
{
_logger.Debug("Failed download contains a movie, searching again.");
_commandQueueManager.Push(new MoviesSearchCommand { MovieId = message.MovieId });
return;
}
if (message.EpisodeIds.Count == 1)
{
_logger.Debug("Failed download only contains one episode, searching again");

View File

@@ -1,30 +0,0 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Indexers.Fanzub
{
public class Fanzub : HttpIndexerBase<FanzubSettings>
{
public override string Name => "Fanzub";
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public Fanzub(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, indexerStatusService, configService, parsingService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new FanzubRequestGenerator() { Settings = Settings };
}
public override IParseIndexerResponse GetParser()
{
return new RssParser() { UseEnclosureUrl = true, UseEnclosureLength = true };
}
}
}

View File

@@ -1,94 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.Fanzub
{
public class FanzubRequestGenerator : IIndexerRequestGenerator
{
private static readonly Regex RemoveCharactersRegex = new Regex(@"[!?`]", RegexOptions.Compiled);
public FanzubSettings Settings { get; set; }
public int PageSize { get; set; }
public FanzubRequestGenerator()
{
PageSize = 100;
}
public virtual IndexerPageableRequestChain GetRecentRequests()
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(null));
return pageableRequests;
}
public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var searchTitles = searchCriteria.QueryTitles.SelectMany(v => GetTitleSearchStrings(v, searchCriteria.AbsoluteEpisodeNumber)).ToList();
pageableRequests.Add(GetPagedRequests(string.Join("|", searchTitles)));
return pageableRequests;
}
public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
private IEnumerable<IndexerRequest> GetPagedRequests(string query)
{
var url = new StringBuilder();
url.AppendFormat("{0}?cat=anime&max={1}", Settings.BaseUrl, PageSize);
if (query.IsNotNullOrWhiteSpace())
{
url.AppendFormat("&q={0}", query);
}
yield return new IndexerRequest(url.ToString(), HttpAccept.Rss);
}
private IEnumerable<string> GetTitleSearchStrings(string title, int absoluteEpisodeNumber)
{
var formats = new[] { "{0}%20{1:00}", "{0}%20-%20{1:00}" };
return formats.Select(s => "\"" + string.Format(s, CleanTitle(title), absoluteEpisodeNumber) + "\"");
}
private string CleanTitle(string title)
{
return RemoveCharactersRegex.Replace(title, "");
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
}
}

View File

@@ -1,33 +0,0 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Fanzub
{
public class FanzubSettingsValidator : AbstractValidator<FanzubSettings>
{
public FanzubSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
}
}
public class FanzubSettings : IProviderConfig
{
private static readonly FanzubSettingsValidator Validator = new FanzubSettingsValidator();
public FanzubSettings()
{
BaseUrl = "http://fanzub.com/rss/";
}
[FieldDefinition(0, Label = "Rss URL", HelpText = "Enter to URL to an Fanzub compatible RSS feed")]
public string BaseUrl { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -106,8 +106,8 @@ namespace NzbDrone.Core.Indexers.Newznab
}
if (capabilities.SupportedTvSearchParameters != null &&
new[] { "q", "imdb" }.Any(v => capabilities.SupportedMovieSearchParamters.Contains(v)) &&
new[] { "imdbtitle", "imdbyear" }.All(v => capabilities.SupportedMovieSearchParamters.Contains(v)))
new[] { "q", "imdbid" }.Any(v => capabilities.SupportedMovieSearchParameters.Contains(v)) &&
new[] { "imdbtitle", "imdbyear" }.All(v => capabilities.SupportedMovieSearchParameters.Contains(v)))
{
return null;
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public int MaxPageSize { get; set; }
public string[] SupportedSearchParameters { get; set; }
public string[] SupportedTvSearchParameters { get; set; }
public string[] SupportedMovieSearchParamters { get; set; }
public string[] SupportedMovieSearchParameters { get; set; }
public bool SupportsAggregateIdSearch { get; set; }
public List<NewznabCategory> Categories { get; set; }
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Newznab
DefaultPageSize = 100;
MaxPageSize = 100;
SupportedSearchParameters = new[] { "q" };
SupportedMovieSearchParamters = new[] { "q", "imdb", "imdbtitle", "imdbyear" };
SupportedMovieSearchParameters = new[] { "q", "imdbid", "imdbtitle", "imdbyear" };
SupportedTvSearchParameters = new[] { "q", "rid", "season", "ep" }; // This should remain 'rid' for older newznab installs.
SupportsAggregateIdSearch = false;
Categories = new List<NewznabCategory>();

View File

@@ -102,11 +102,11 @@ namespace NzbDrone.Core.Indexers.Newznab
var xmlMovieSearch = xmlSearching.Element("movie-search");
if (xmlMovieSearch == null || xmlMovieSearch.Attribute("available").Value != "yes")
{
capabilities.SupportedMovieSearchParamters = null;
capabilities.SupportedMovieSearchParameters = null;
}
else if (xmlMovieSearch.Attribute("supportedParams") != null)
{
capabilities.SupportedMovieSearchParamters = xmlMovieSearch.Attribute("supportedParams").Value.Split(',');
capabilities.SupportedMovieSearchParameters = xmlMovieSearch.Attribute("supportedParams").Value.Split(',');
capabilities.SupportsAggregateIdSearch = true;
}
}

View File

@@ -91,8 +91,8 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
return capabilities.SupportedMovieSearchParamters != null &&
capabilities.SupportedMovieSearchParamters.Contains("imdb");
return capabilities.SupportedMovieSearchParameters != null &&
capabilities.SupportedMovieSearchParameters.Contains("imdbid");
}
}
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Indexers.Newznab
var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
if (capabilities.SupportedMovieSearchParamters != null)
if (capabilities.SupportedMovieSearchParameters != null)
{
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories.Concat(Settings.AnimeCategories), "movie", ""));
}
@@ -124,7 +124,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var pageableRequests = new IndexerPageableRequestChain();
if(SupportsMovieSearch)
if (SupportsMovieSearch)
{
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie",
string.Format("&imdbid={0}", searchCriteria.Movie.ImdbId.Substring(2)))); //strip off the "tt" - VERY HACKY

View File

@@ -48,9 +48,7 @@ namespace NzbDrone.Core.Indexers.Newznab
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
{
releaseInfo = base.ProcessItem(item, releaseInfo);
releaseInfo.TvdbId = GetTvdbId(item);
releaseInfo.TvRageId = GetTvRageId(item);
releaseInfo.ImdbId = GetImdbId(item);
return releaseInfo;
}
@@ -114,27 +112,14 @@ namespace NzbDrone.Core.Indexers.Newznab
return url;
}
protected virtual int GetTvdbId(XElement item)
protected virtual int GetImdbId(XElement item)
{
var tvdbIdString = TryGetNewznabAttribute(item, "tvdbid");
int tvdbId;
var imdbIdString = TryGetNewznabAttribute(item, "imdb");
int imdbId;
if (!tvdbIdString.IsNullOrWhiteSpace() && int.TryParse(tvdbIdString, out tvdbId))
if (!imdbIdString.IsNullOrWhiteSpace() && int.TryParse(imdbIdString, out imdbId))
{
return tvdbId;
}
return 0;
}
protected virtual int GetTvRageId(XElement item)
{
var tvRageIdString = TryGetNewznabAttribute(item, "rageid");
int tvRageId;
if (!tvRageIdString.IsNullOrWhiteSpace() && int.TryParse(tvRageIdString, out tvRageId))
{
return tvRageId;
return imdbId;
}
return 0;

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public NewznabSettings()
{
Categories = new[] { 2030, 2040, 2050 };
Categories = new[] { 2030, 2035, 2040, 2050 };
AnimeCategories = Enumerable.Empty<int>();
}

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Indexers.Wombles
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new RssIndexerRequestGenerator("http://newshost.co.za/rss/?sec=TV&fr=false");
return new RssIndexerRequestGenerator("http://newshost.co.za/rss/?sec=Movies&fr=false");
}
public Wombles(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)

View File

@@ -0,0 +1,17 @@
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class DownloadedMovieScanCommand : Command
{
public override bool SendUpdatesToClient => SendUpdates;
public bool SendUpdates { get; set; }
// Properties used by third-party apps, do not modify.
public string Path { get; set; }
public string DownloadClientId { get; set; }
public ImportMode ImportMode { get; set; }
}
}

View File

@@ -1,265 +1,107 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
using System.Text;
namespace NzbDrone.Core.MediaFiles
{
public interface IDownloadedMovieImportService
{
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null);
bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie);
}
public class DownloadedMovieImportService : IDownloadedMovieImportService
public class DownloadedMovieCommandService : IExecute<DownloadedMovieScanCommand>
{
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
private readonly IMovieService _movieService;
private readonly IParsingService _parsingService;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedMovie _importApprovedMovie;
private readonly IDetectSample _detectSample;
private readonly IConfigService _configService;
private readonly Logger _logger;
public DownloadedMovieImportService(IDiskProvider diskProvider,
IDiskScanService diskScanService,
IMovieService movieService,
IParsingService parsingService,
IMakeImportDecision importDecisionMaker,
IImportApprovedMovie importApprovedMovie,
IDetectSample detectSample,
Logger logger)
public DownloadedMovieCommandService(IDownloadedMovieImportService downloadedMovieImportService,
ITrackedDownloadService trackedDownloadService,
IDiskProvider diskProvider,
IConfigService configService,
Logger logger)
{
_downloadedMovieImportService = downloadedMovieImportService;
_trackedDownloadService = trackedDownloadService;
_diskProvider = diskProvider;
_diskScanService = diskScanService;
_movieService = movieService;
_parsingService = parsingService;
_importDecisionMaker = importDecisionMaker;
_importApprovedMovie = importApprovedMovie;
_detectSample = detectSample;
_configService = configService;
_logger = logger;
}
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo)
private List<ImportResult> ProcessDroneFactoryFolder()
{
var results = new List<ImportResult>();
var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName))
if (string.IsNullOrEmpty(downloadedEpisodesFolder))
{
var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null);
results.AddRange(folderResults);
}
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false))
{
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
results.AddRange(fileResults);
}
return results;
}
public List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null)
{
if (_diskProvider.FolderExists(path))
{
var directoryInfo = new DirectoryInfo(path);
if (movie == null)
{
return ProcessFolder(directoryInfo, importMode, downloadClientItem);
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
if (_diskProvider.FileExists(path))
{
var fileInfo = new FileInfo(path);
if (movie == null)
{
return ProcessFile(fileInfo, importMode, downloadClientItem);
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
_logger.Error("Import failed, path does not exist or is not accessible by Sonarr: {0}", path);
return new List<ImportResult>();
}
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie)
{
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
foreach (var videoFile in videoFiles)
{
var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile));
if (episodeParseResult == null)
{
_logger.Warn("Unable to parse file on import: [{0}]", videoFile);
return false;
}
var size = _diskProvider.GetFileSize(videoFile);
var quality = QualityParser.ParseQuality(videoFile);
if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode))
{
_logger.Warn("Non-sample file detected: [{0}]", videoFile);
return false;
}
}
if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes()))
{
_logger.Warn("RAR file detected, will require manual cleanup");
return false;
}
return true;
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var movie = _parsingService.GetMovie(cleanedUpName);
if (movie == null)
{
_logger.Debug("Unknown Movie {0}", cleanedUpName);
return new List<ImportResult>
{
UnknownMovieResult("Unknown Movie")
};
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
{
if (_movieService.MoviePathExists(directoryInfo.FullName))
{
_logger.Warn("Unable to process folder that is mapped to an existing show");
_logger.Trace("Drone Factory folder is not configured");
return new List<ImportResult>();
}
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name);
if (folderInfo != null)
if (!_diskProvider.FolderExists(downloadedEpisodesFolder))
{
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
_logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder);
return new List<ImportResult>();
}
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
return _downloadedMovieImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder));
}
if (downloadClientItem == null)
private List<ImportResult> ProcessPath(DownloadedMovieScanCommand message)
{
if (!_diskProvider.FolderExists(message.Path) && !_diskProvider.FileExists(message.Path))
{
foreach (var videoFile in videoFiles)
_logger.Warn("Folder/File specified for import scan [{0}] doesn't exist.", message.Path);
return new List<ImportResult>();
}
if (message.DownloadClientId.IsNotNullOrWhiteSpace())
{
var trackedDownload = _trackedDownloadService.Find(message.DownloadClientId);
if (trackedDownload != null)
{
if (_diskProvider.IsFileLocked(videoFile))
{
return new List<ImportResult>
{
FileIsLockedResult(videoFile)
};
}
_logger.Debug("External directory scan request for known download {0}. [{1}]", message.DownloadClientId, message.Path);
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode, trackedDownload.RemoteMovie.Movie, trackedDownload.DownloadItem);
}
else
{
_logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path);
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
}
}
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true);
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
}
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
importResults.Any(i => i.Result == ImportResultType.Imported) &&
ShouldDeleteFolder(directoryInfo, movie))
public void Execute(DownloadedMovieScanCommand message)
{
List<ImportResult> importResults;
if (message.Path.IsNotNullOrWhiteSpace())
{
_logger.Debug("Deleting folder after importing valid files");
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
importResults = ProcessPath(message);
}
else
{
importResults = ProcessDroneFactoryFolder();
}
return importResults;
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name));
if (movie == null)
if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported))
{
_logger.Debug("Unknown Movie for file: {0}", fileInfo.Name);
return new List<ImportResult>
{
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
};
// Atm we don't report it as a command failure, coz that would cause the download to be failed.
// Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later.
//message.SetMessage("Failed to import");
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
{
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
{
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
return new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
};
}
if (downloadClientItem == null)
{
if (_diskProvider.IsFileLocked(fileInfo.FullName))
{
return new List<ImportResult>
{
FileIsLockedResult(fileInfo.FullName)
};
}
}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, null, true);
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
}
private string GetCleanedUpFolderName(string folder)
{
folder = folder.Replace("_UNPACK_", "")
.Replace("_FAILED_", "");
return folder;
}
private ImportResult FileIsLockedResult(string videoFile)
{
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
}
private ImportResult UnknownMovieResult(string message, string videoFile = null)
{
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message);
}
}
}

View File

@@ -0,0 +1,266 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.MediaFiles.Commands;
namespace NzbDrone.Core.MediaFiles
{
public interface IDownloadedMovieImportService
{
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null);
bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie);
}
public class DownloadedMovieImportService : IDownloadedMovieImportService
{
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
private readonly IMovieService _movieService;
private readonly IParsingService _parsingService;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedMovie _importApprovedMovie;
private readonly IDetectSample _detectSample;
private readonly Logger _logger;
public DownloadedMovieImportService(IDiskProvider diskProvider,
IDiskScanService diskScanService,
IMovieService movieService,
IParsingService parsingService,
IMakeImportDecision importDecisionMaker,
IImportApprovedMovie importApprovedMovie,
IDetectSample detectSample,
Logger logger)
{
_diskProvider = diskProvider;
_diskScanService = diskScanService;
_movieService = movieService;
_parsingService = parsingService;
_importDecisionMaker = importDecisionMaker;
_importApprovedMovie = importApprovedMovie;
_detectSample = detectSample;
_logger = logger;
}
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo)
{
var results = new List<ImportResult>();
foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName))
{
var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null);
results.AddRange(folderResults);
}
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false))
{
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
results.AddRange(fileResults);
}
return results;
}
public List<ImportResult> ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null)
{
if (_diskProvider.FolderExists(path))
{
var directoryInfo = new DirectoryInfo(path);
if (movie == null)
{
return ProcessFolder(directoryInfo, importMode, downloadClientItem);
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
if (_diskProvider.FileExists(path))
{
var fileInfo = new FileInfo(path);
if (movie == null)
{
return ProcessFile(fileInfo, importMode, downloadClientItem);
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
_logger.Error("Import failed, path does not exist or is not accessible by Radarr: {0}", path);
return new List<ImportResult>();
}
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie)
{
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
foreach (var videoFile in videoFiles)
{
var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile));
if (episodeParseResult == null)
{
_logger.Warn("Unable to parse file on import: [{0}]", videoFile);
return false;
}
var size = _diskProvider.GetFileSize(videoFile);
var quality = QualityParser.ParseQuality(videoFile);
if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode))
{
_logger.Warn("Non-sample file detected: [{0}]", videoFile);
return false;
}
}
if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes()))
{
_logger.Warn("RAR file detected, will require manual cleanup");
return false;
}
return true;
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var movie = _parsingService.GetMovie(cleanedUpName);
if (movie == null)
{
_logger.Debug("Unknown Movie {0}", cleanedUpName);
return new List<ImportResult>
{
UnknownMovieResult("Unknown Movie")
};
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
{
if (_movieService.MoviePathExists(directoryInfo.FullName))
{
_logger.Warn("Unable to process folder that is mapped to an existing show");
return new List<ImportResult>();
}
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name);
if (folderInfo != null)
{
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
}
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
if (downloadClientItem == null)
{
foreach (var videoFile in videoFiles)
{
if (_diskProvider.IsFileLocked(videoFile))
{
return new List<ImportResult>
{
FileIsLockedResult(videoFile)
};
}
}
}
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true);
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
importResults.Any(i => i.Result == ImportResultType.Imported) &&
ShouldDeleteFolder(directoryInfo, movie))
{
_logger.Debug("Deleting folder after importing valid files");
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
}
return importResults;
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
{
var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name));
if (movie == null)
{
_logger.Debug("Unknown Movie for file: {0}", fileInfo.Name);
return new List<ImportResult>
{
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
};
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem)
{
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
{
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
return new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
};
}
if (downloadClientItem == null)
{
if (_diskProvider.IsFileLocked(fileInfo.FullName))
{
return new List<ImportResult>
{
FileIsLockedResult(fileInfo.FullName)
};
}
}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, null, true);
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
}
private string GetCleanedUpFolderName(string folder)
{
folder = folder.Replace("_UNPACK_", "")
.Replace("_FAILED_", "");
return folder;
}
private ImportResult FileIsLockedResult(string videoFile)
{
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
}
private ImportResult UnknownMovieResult(string message, string videoFile = null)
{
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message);
}
}
}

View File

@@ -153,7 +153,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
private int GetMinimumAllowedRuntime(Movie movie)
{
return 120; //2 minutes
return 360; //6 minutes
}
private int GetMinimumAllowedRuntime(Series series)

View File

@@ -10,5 +10,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public List<int> EpisodeIds { get; set; }
public QualityModel Quality { get; set; }
public string DownloadId { get; set; }
public int MovieId { get; set; }
}
}

View File

@@ -17,5 +17,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public QualityModel Quality { get; set; }
public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
public Movie Movie { get; set; }
}
}

View File

@@ -30,11 +30,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
private readonly IDiskScanService _diskScanService;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly ISeriesService _seriesService;
private readonly IMovieService _movieService;
private readonly IEpisodeService _episodeService;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
private readonly IImportApprovedMovie _importApprovedMovie;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
@@ -43,11 +46,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
IDiskScanService diskScanService,
IMakeImportDecision importDecisionMaker,
ISeriesService seriesService,
IMovieService movieService,
IEpisodeService episodeService,
IVideoFileInfoReader videoFileInfoReader,
IImportApprovedEpisodes importApprovedEpisodes,
IImportApprovedMovie importApprovedMovie,
ITrackedDownloadService trackedDownloadService,
IDownloadedEpisodesImportService downloadedEpisodesImportService,
IDownloadedMovieImportService downloadedMovieImportService,
IEventAggregator eventAggregator,
Logger logger)
{
@@ -56,11 +62,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
_diskScanService = diskScanService;
_importDecisionMaker = importDecisionMaker;
_seriesService = seriesService;
_movieService = movieService;
_episodeService = episodeService;
_videoFileInfoReader = videoFileInfoReader;
_importApprovedEpisodes = importApprovedEpisodes;
_importApprovedMovie = importApprovedMovie;
_trackedDownloadService = trackedDownloadService;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
_downloadedMovieImportService = downloadedMovieImportService;
_eventAggregator = eventAggregator;
_logger = logger;
}
@@ -126,62 +135,128 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
var relativeFile = folder.GetRelativePath(file);
var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
var movie = _parsingService.GetMovie(relativeFile.Split('\\', '/')[0]);
if (series == null)
if (movie == null)
{
series = _parsingService.GetSeries(relativeFile);
movie = _parsingService.GetMovie(relativeFile);
}
if (series == null && downloadId.IsNotNullOrWhiteSpace())
if (movie == null && downloadId.IsNotNullOrWhiteSpace())
{
var trackedDownload = _trackedDownloadService.Find(downloadId);
series = trackedDownload.RemoteEpisode.Series;
movie = trackedDownload.RemoteMovie.Movie;
}
if (series == null)
if (movie == null)
{
var localEpisode = new LocalEpisode();
localEpisode.Path = file;
localEpisode.Quality = QualityParser.ParseQuality(file);
localEpisode.Size = _diskProvider.GetFileSize(file);
var localMovie = new LocalMovie()
{
Path = file,
Quality = QualityParser.ParseQuality(file),
Size = _diskProvider.GetFileSize(file)
};
return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), folder, downloadId);
}
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
series, null, SceneSource(series, folder));
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file },
movie, null, SceneSource(movie, folder));
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
}
//private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
//{
// if (folder.IsNullOrWhiteSpace())
// {
// folder = new FileInfo(file).Directory.FullName;
// }
// var relativeFile = folder.GetRelativePath(file);
// var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
// if (series == null)
// {
// series = _parsingService.GetSeries(relativeFile);
// }
// if (series == null && downloadId.IsNotNullOrWhiteSpace())
// {
// var trackedDownload = _trackedDownloadService.Find(downloadId);
// series = trackedDownload.RemoteEpisode.Series;
// }
// if (series == null)
// {
// var localEpisode = new LocalEpisode();
// localEpisode.Path = file;
// localEpisode.Quality = QualityParser.ParseQuality(file);
// localEpisode.Size = _diskProvider.GetFileSize(file);
// return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
// }
// var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
// series, null, SceneSource(series, folder));
// return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
//}
private bool SceneSource(Series series, string folder)
{
return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder));
}
private bool SceneSource(Movie movie, string folder)
{
return !(movie.Path.PathEquals(folder) || movie.Path.IsParentPath(folder));
}
//private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
//{
// var item = new ManualImportItem();
// item.Path = decision.LocalEpisode.Path;
// item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
// item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
// item.DownloadId = downloadId;
// if (decision.LocalEpisode.Series != null)
// {
// item.Series = decision.LocalEpisode.Series;
// }
// if (decision.LocalEpisode.Episodes.Any())
// {
// item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
// item.Episodes = decision.LocalEpisode.Episodes;
// }
// item.Quality = decision.LocalEpisode.Quality;
// item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
// item.Rejections = decision.Rejections;
// return item;
//}
private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
{
var item = new ManualImportItem();
item.Path = decision.LocalEpisode.Path;
item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
item.Path = decision.LocalMovie.Path;
item.RelativePath = folder.GetRelativePath(decision.LocalMovie.Path);
item.Name = Path.GetFileNameWithoutExtension(decision.LocalMovie.Path);
item.DownloadId = downloadId;
if (decision.LocalEpisode.Series != null)
if (decision.LocalMovie.Movie != null)
{
item.Series = decision.LocalEpisode.Series;
item.Movie = decision.LocalMovie.Movie;
}
if (decision.LocalEpisode.Episodes.Any())
{
item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
item.Episodes = decision.LocalEpisode.Episodes;
}
item.Quality = decision.LocalEpisode.Quality;
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
item.Quality = decision.LocalMovie.Quality;
item.Size = _diskProvider.GetFileSize(decision.LocalMovie.Path);
item.Rejections = decision.Rejections;
return item;
@@ -199,45 +274,43 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
var file = message.Files[i];
var series = _seriesService.GetSeries(file.SeriesId);
var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
var movie = _movieService.GetMovie(file.MovieId);
var parsedMovieInfo = Parser.Parser.ParseMoviePath(file.Path) ?? new ParsedMovieInfo();
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
var existingFile = series.Path.IsParentPath(file.Path);
var existingFile = movie.Path.IsParentPath(file.Path);
var localEpisode = new LocalEpisode
var localMovie = new LocalMovie
{
ExistingFile = false,
Episodes = episodes,
MediaInfo = mediaInfo,
ParsedEpisodeInfo = parsedEpisodeInfo,
ParsedMovieInfo = parsedMovieInfo,
Path = file.Path,
Quality = file.Quality,
Series = series,
Movie = movie,
Size = 0
};
//TODO: Cleanup non-tracked downloads
var importDecision = new ImportDecision(localEpisode);
var importDecision = new ImportDecision(localMovie);
if (file.DownloadId.IsNullOrWhiteSpace())
{
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
imported.AddRange(_importApprovedMovie.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
}
else
{
var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
var importResult = _importApprovedMovie.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
imported.Add(importResult);
importedTrackedDownload.Add(new ManuallyImportedFile
{
TrackedDownload = trackedDownload,
ImportResult = importResult
});
{
TrackedDownload = trackedDownload,
ImportResult = importResult
});
}
}
@@ -249,20 +322,98 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{
if (_downloadedEpisodesImportService.ShouldDeleteFolder(
if (_downloadedMovieImportService.ShouldDeleteFolder(
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
trackedDownload.RemoteMovie.Movie) && !trackedDownload.DownloadItem.IsReadOnly)
{
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
}
}
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, 1)) //TODO: trackedDownload.RemoteMovie.Movie.Count is always 1?
{
trackedDownload.State = TrackedDownloadStage.Imported;
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
}
}
}
//public void Execute(ManualImportCommand message)
//{
// _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode);
// var imported = new List<ImportResult>();
// var importedTrackedDownload = new List<ManuallyImportedFile>();
// for (int i = 0; i < message.Files.Count; i++)
// {
// _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
// var file = message.Files[i];
// var series = _seriesService.GetSeries(file.SeriesId);
// var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
// var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
// var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
// var existingFile = series.Path.IsParentPath(file.Path);
// var localEpisode = new LocalEpisode
// {
// ExistingFile = false,
// Episodes = episodes,
// MediaInfo = mediaInfo,
// ParsedEpisodeInfo = parsedEpisodeInfo,
// Path = file.Path,
// Quality = file.Quality,
// Series = series,
// Size = 0
// };
// //TODO: Cleanup non-tracked downloads
// var importDecision = new ImportDecision(localEpisode);
// if (file.DownloadId.IsNullOrWhiteSpace())
// {
// imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
// }
// else
// {
// var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
// var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
// imported.Add(importResult);
// importedTrackedDownload.Add(new ManuallyImportedFile
// {
// TrackedDownload = trackedDownload,
// ImportResult = importResult
// });
// }
// }
// _logger.ProgressTrace("Manually imported {0} files", imported.Count);
// foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
// {
// var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
// if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
// {
// if (_downloadedEpisodesImportService.ShouldDeleteFolder(
// new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
// trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
// {
// _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
// }
// }
// if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
// {
// trackedDownload.State = TrackedDownloadStage.Imported;
// _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
// }
// }
//}
}
}

View File

@@ -40,12 +40,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
public Decision IsSatisfiedBy(LocalMovie localEpisode)
{
if (localEpisode.ExistingFile)
{
_logger.Debug("Existing file, skipping sample check");
return Decision.Accept();
}
var sample = _detectSample.IsSample(localEpisode.Movie,
localEpisode.Quality,
localEpisode.Path,

View File

@@ -55,7 +55,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public Production_Companies[] production_companies { get; set; }
public Production_Countries[] production_countries { get; set; }
public string release_date { get; set; }
public int revenue { get; set; }
public long revenue { get; set; }
public int runtime { get; set; }
public Spoken_Languages[] spoken_languages { get; set; }
public string status { get; set; }

View File

@@ -92,8 +92,11 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
movie.CleanTitle = Parser.Parser.CleanSeriesTitle(movie.Title);
movie.Overview = resource.overview;
movie.Website = resource.homepage;
movie.InCinemas = DateTime.Parse(resource.release_date);
movie.Year = movie.InCinemas.Value.Year;
if (resource.release_date.IsNotNullOrWhiteSpace())
{
movie.InCinemas = DateTime.Parse(resource.release_date);
movie.Year = movie.InCinemas.Value.Year;
}
var slugResult = _movieService.FindByTitleSlug(movie.TitleSlug);
if (slugResult != null)
@@ -186,7 +189,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{
var lowerTitle = title.ToLower();
var parserResult = Parser.Parser.ParseMovieTitle(title);
var parserResult = Parser.Parser.ParseMovieTitle(title, true);
var yearTerm = "";
@@ -328,7 +331,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
imdbMovie.Title = result.title;
string titleSlug = result.title;
imdbMovie.TitleSlug = titleSlug.ToLower().Replace(" ", "-");
imdbMovie.Year = DateTime.Parse(result.release_date).Year;
if (result.release_date.IsNotNullOrWhiteSpace())
{
imdbMovie.Year = DateTime.Parse(result.release_date).Year;
}
var slugResult = _movieService.FindByTitleSlug(imdbMovie.TitleSlug);
if (slugResult != null)

View File

@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Notifications.Boxcar
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
const string body = "This is a test message from Radarr";
SendNotification(title, body, settings);
return null;

View File

@@ -146,7 +146,7 @@ namespace NzbDrone.Core.Notifications.Growl
Register(settings.Host, settings.Port, settings.Password);
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
const string body = "This is a test message from Radarr";
SendNotification(title, body, "TEST", settings.Host, settings.Port, settings.Password);
}

View File

@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Notifications.Join
public ValidationFailure Test(JoinSettings settings)
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr.";
const string body = "This is a test message from Radarr.";
try
{

View File

@@ -69,7 +69,7 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
const string body = "This is a test message from Radarr";
Verify(settings.ApiKey);
SendNotification(title, body, settings.ApiKey, (NotifyMyAndroidPriority)settings.Priority);
}

View File

@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Notifications.Plex
{
return Json.Deserialize<PlexMediaContainerLegacy>(response.Content)
.Sections
.Where(d => d.Type == "show")
.Where(d => d.Type == "movie")
.Select(s => new PlexSection
{
Id = s.Id,
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.Notifications.Plex
return Json.Deserialize<PlexResponse<PlexSectionsContainer>>(response.Content)
.MediaContainer
.Sections
.Where(d => d.Type == "show")
.Where(d => d.Type == "movie")
.ToList();
}

View File

@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Notifications.Prowl
Verify(settings.ApiKey);
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
const string body = "This is a test message from Radarr";
SendNotification(title, body, settings.ApiKey);
}

View File

@@ -93,7 +93,7 @@ namespace NzbDrone.Core.Notifications.PushBullet
try
{
const string title = "Sonarr - Test Notification";
const string body = "This is a test message from Sonarr";
const string body = "This is a test message from Radarr";
SendNotification(title, body, settings);
}

View File

@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Notifications.Pushalot
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
const string body = "This is a test message from Radarr";
SendNotification(title, body, settings);
}

View File

@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Notifications.Pushover
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
const string body = "This is a test message from Radarr";
SendNotification(title, body, settings);
}

View File

@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Notifications.Telegram
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
const string body = "This is a test message from Radarr";
SendNotification(title, body, settings);
}

View File

@@ -185,6 +185,7 @@
<Compile Include="Datastore\Migration\004_updated_history.cs" />
<Compile Include="Datastore\Migration\111_remove_bitmetv.cs" />
<Compile Include="Datastore\Migration\112_remove_torrentleech.cs" />
<Compile Include="Datastore\Migration\114_remove_fanzub.cs" />
<Compile Include="Datastore\Migration\113_remove_broadcasthenet.cs" />
<Compile Include="Datastore\Migration\108_update_schedule_interval.cs" />
<Compile Include="Datastore\Migration\107_fix_movie_files.cs" />
@@ -582,9 +583,6 @@
<Compile Include="Indexers\Exceptions\RequestLimitReachedException.cs" />
<Compile Include="Indexers\Exceptions\UnsupportedFeedException.cs" />
<Compile Include="Indexers\EzrssTorrentRssParser.cs" />
<Compile Include="Indexers\Fanzub\Fanzub.cs" />
<Compile Include="Indexers\Fanzub\FanzubRequestGenerator.cs" />
<Compile Include="Indexers\Fanzub\FanzubSettings.cs" />
<Compile Include="Indexers\FetchAndParseRssService.cs" />
<Compile Include="Indexers\PassThePopcorn\PassThePopcorn.cs" />
<Compile Include="Indexers\PassThePopcorn\PassThePopcornApi.cs" />
@@ -707,10 +705,12 @@
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedMovieScanCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieFilesCommand.cs" />
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
<Compile Include="MediaFiles\DownloadedMovieCommandService.cs" />
<Compile Include="MediaFiles\DownloadedMovieImportService.cs" />
<Compile Include="MediaFiles\MovieFileMovingService.cs" />
<Compile Include="MediaFiles\Events\MovieDownloadedEvent.cs" />
<Compile Include="MediaFiles\Events\MovieFileAddedEvent.cs" />

View File

@@ -67,6 +67,8 @@ namespace NzbDrone.Core.Organizer
{
var value = context.PropertyValue as string;
return true;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameValidation.OriginalTokenRegex.IsMatch(value))
{
@@ -89,6 +91,8 @@ namespace NzbDrone.Core.Organizer
{
var value = context.PropertyValue as string;
return true;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AirDateRegex.IsMatch(value) &&
!FileNameValidation.OriginalTokenRegex.IsMatch(value))
@@ -112,6 +116,8 @@ namespace NzbDrone.Core.Organizer
{
var value = context.PropertyValue as string;
return true;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AbsoluteEpisodePatternRegex.IsMatch(value) &&
!FileNameValidation.OriginalTokenRegex.IsMatch(value))

View File

@@ -17,6 +17,7 @@ namespace NzbDrone.Core.Parser.Model
public DownloadProtocol DownloadProtocol { get; set; }
public int TvdbId { get; set; }
public int TvRageId { get; set; }
public int ImdbId { get; set; }
public DateTime PublishDate { get; set; }
public string Origin { get; set; }
@@ -82,6 +83,7 @@ namespace NzbDrone.Core.Parser.Model
stringBuilder.AppendLine("DownloadProtocol: " + DownloadProtocol ?? "Empty");
stringBuilder.AppendLine("TvdbId: " + TvdbId ?? "Empty");
stringBuilder.AppendLine("TvRageId: " + TvRageId ?? "Empty");
stringBuilder.AppendLine("ImdbId: " + ImdbId ?? "Empty");
stringBuilder.AppendLine("PublishDate: " + PublishDate ?? "Empty");
return stringBuilder.ToString();
default:

View File

@@ -36,6 +36,15 @@ namespace NzbDrone.Core.Parser
//PassThePopcorn Torrent names: Star.Wars[PassThePopcorn]
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//That did not work? Maybe some tool uses [] for years. Who would do that?
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
};
private static readonly Regex[] ReportMovieTitleFolderRegex = new[]
{
//When year comes first.
new Regex(@"^(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?<title>.+?)?$")
};
private static readonly Regex[] ReportTitleRegex = new[]
@@ -327,7 +336,7 @@ namespace NzbDrone.Core.Parser
{
var fileInfo = new FileInfo(path);
var result = ParseMovieTitle(fileInfo.Name);
var result = ParseMovieTitle(fileInfo.Name, true);
if (result == null)
{
@@ -345,7 +354,7 @@ namespace NzbDrone.Core.Parser
}
public static ParsedMovieInfo ParseMovieTitle(string title)
public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false)
{
ParsedMovieInfo realResult = null;
@@ -376,7 +385,14 @@ namespace NzbDrone.Core.Parser
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle, string.Empty);
foreach (var regex in ReportMovieTitleRegex)
var allRegexes = ReportMovieTitleRegex.ToList();
if (isDir)
{
allRegexes.AddRange(ReportMovieTitleFolderRegex);
}
foreach (var regex in allRegexes)
{
var match = regex.Matches(simpleTitle);

View File

@@ -192,6 +192,11 @@ namespace NzbDrone.Core.Parser
parsedEpisodeInfo.MovieTitleInfo.Year);
}
if (series == null)
{
series = _movieService.FindByTitle(parsedEpisodeInfo.MovieTitle.Replace("DC", "").Trim());
}
return series;
}

View File

@@ -149,7 +149,11 @@ namespace NzbDrone.Core.RootFolders
foreach (string unmappedFolder in unmappedFolders)
{
var di = new DirectoryInfo(unmappedFolder.Normalize());
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName });
if (!di.Attributes.HasFlag(FileAttributes.System) && !di.Attributes.HasFlag(FileAttributes.Hidden))
{
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName });
}
}
var setToRemove = SpecialFolders;

View File

@@ -63,11 +63,11 @@ namespace NzbDrone.Core.Tv
cleanNum = cleanNum.Replace(roman, num);
}
var result = Query.Where(s => s.CleanTitle == cleanTitle).SingleOrDefault();
var result = Query.Where(s => s.CleanTitle == cleanTitle).FirstOrDefault();
if (result == null)
{
result = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman).SingleOrDefault();
result = Query.Where(s => s.CleanTitle == cleanNum).OrWhere(s => s.CleanTitle == cleanRoman).FirstOrDefault();
if (result == null)
{
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Tv
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)).SingleOrDefault();
Parser.Parser.CleanSeriesTitle(t.ToLower()) == cleanNum)).FirstOrDefault();
return result;
}

View File

@@ -2,7 +2,7 @@ var Marionette = require('marionette');
var Backgrid = require('backgrid');
var HistoryCollection = require('./HistoryCollection');
var EventTypeCell = require('../../Cells/EventTypeCell');
var MovieTitleCell = require('../../Cells/MovieTitleCell');
var MovieTitleCell = require('../../Cells/MovieTitleHistoryCell');
var EpisodeNumberCell = require('../../Cells/EpisodeNumberCell');
var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell');
var HistoryQualityCell = require('./HistoryQualityCell');
@@ -31,7 +31,7 @@ module.exports = Marionette.Layout.extend({
{
name : 'movies',
label : 'Movie Title',
cell : MovieTitleCell
cell : MovieTitleCell,
},
/*{
name : 'episode',

View File

@@ -58,7 +58,7 @@ var view = Marionette.ItemView.extend({
var defaultProfile = Config.getValue(Config.Keys.DefaultProfileId);
var defaultRoot = Config.getValue(Config.Keys.DefaultRootFolderId);
var useSeasonFolder = Config.getValueBoolean(Config.Keys.UseSeasonFolder, true);
var defaultMonitorEpisodes = Config.getValue(Config.Keys.MonitorEpisodes, 'missing');
var defaultMonitorEpisodes = Config.getValue(Config.Keys.MonitorEpisodes, 'all');
if (Profiles.get(defaultProfile)) {
this.ui.profile.val(defaultProfile);
@@ -169,6 +169,7 @@ var view = Marionette.ItemView.extend({
var profile = this.ui.profile.val();
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
var monitor = this.ui.monitor.val();
var options = this._getAddMoviesOptions();
options.searchForMovie = searchForMovie;
@@ -178,7 +179,7 @@ var view = Marionette.ItemView.extend({
profileId : profile,
rootFolderPath : rootFolderPath,
addOptions : options,
monitored : true
monitored : (monitor === 'all' ? true : false)
}, { silent : true });
var self = this;
@@ -229,44 +230,10 @@ var view = Marionette.ItemView.extend({
},
_getAddMoviesOptions : function() {
var monitor = this.ui.monitor.val();
var options = {
return {
ignoreEpisodesWithFiles : false,
ignoreEpisodesWithoutFiles : false
};
if (monitor === 'all') {
return options;
}
else if (monitor === 'future') {
options.ignoreEpisodesWithFiles = true;
options.ignoreEpisodesWithoutFiles = true;
}
// else if (monitor === 'latest') {
// this.model.setSeasonPass(lastSeason.seasonNumber);
// }
// else if (monitor === 'first') {
// this.model.setSeasonPass(lastSeason.seasonNumber + 1);
// this.model.setSeasonMonitored(firstSeason.seasonNumber);
// }
else if (monitor === 'missing') {
options.ignoreEpisodesWithFiles = true;
}
else if (monitor === 'existing') {
options.ignoreEpisodesWithoutFiles = true;
}
// else if (monitor === 'none') {
// this.model.setSeasonPass(lastSeason.seasonNumber + 1);
// }
return options;
}
});

View File

@@ -1,7 +1,7 @@
<div class="search-item {{#unless isExisting}}search-item-new{{/unless}}">
<div class="row">
<div class="col-md-2">
<a href="{{imdbUrl}}" target="_blank">
<a href="{{tmdbUrl}}" target="_blank">
{{#if remotePoster}}
{{remotePoster}}
{{else}}
@@ -41,9 +41,9 @@
<div class="form-group col-md-2">
<label>Monitor <i class="icon-sonarr-form-info monitor-tooltip x-monitor-tooltip"></i></label>
<select class="form-control col-md-2 x-monitor">
<option value="all">All</option>
<option value="missing">Missing</option>
<option value="none">None</option>
<option value="all">Yes</option>
<!-- <option value="missing">Missing</option> -->
<option value="none">No</option>
</select>
</div>

View File

@@ -7,10 +7,16 @@ module.exports = TemplatedCell.extend({
var monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
var cinemasDate = new Date(this.model.get("inCinemas"));
var year = cinemasDate.getFullYear();
var month = monthNames[cinemasDate.getMonth()];
this.$el.html(month + " " + year); //Hack, but somehow handlebar helper does not work.
return this;
this.$el.html("");
if (this.model.get("inCinemas")) {
var cinemasDate = new Date(this.model.get("inCinemas"));
var year = cinemasDate.getFullYear();
var month = monthNames[cinemasDate.getMonth()];
this.$el.html(month + " " + year); //Hack, but somehow handlebar helper does not work.
}
return this;
}
});

View File

@@ -33,7 +33,7 @@ module.exports = NzbDroneCell.extend({
},
_editSeries : function() {
vent.trigger(vent.Commands.EditSeriesCommand, { series : this.model });
vent.trigger(vent.Commands.EditMovieCommand, { movie : this.model });
},
_refreshSeries : function() {

View File

@@ -1 +1 @@
<span class="label label-{{DownloadedStatusColor}}">{{DownloadedStatus}}</span>
<span class="label label-{{DownloadedStatusColor}}" title="{{DownloadedQuality}}">{{DownloadedStatus}}</span>

View File

@@ -4,8 +4,4 @@ module.exports = TemplatedCell.extend({
className : 'series-title-cell',
template : 'Cells/SeriesTitleTemplate',
// render : function() {
// this.$el.html(this.model.get("movie").get("title")); //Hack, but somehow handlebar helper does not work.
// return this;
// }
});

View File

@@ -1,6 +0,0 @@
var TemplatedCell = require('./TemplatedCell');
module.exports = TemplatedCell.extend({
className : 'series-title-cell',
template : 'Cells/SeriesTitleTemplate',
});

View File

@@ -0,0 +1,14 @@
var TemplatedCell = require('./TemplatedCell');
module.exports = TemplatedCell.extend({
className : 'series-title-cell',
template : 'Cells/SeriesTitleTemplate',
render : function() {
this.$el.html(this.model.get("movie").get("title")); //Hack, but somehow handlebar helper does not work.
debugger;
return this;
}
});

View File

@@ -4,5 +4,7 @@ var QualityCellEditor = require('./Edit/QualityCellEditor');
module.exports = TemplatedCell.extend({
className : 'quality-cell',
template : 'Cells/QualityCellTemplate',
editor : QualityCellEditor
});
editor : QualityCellEditor,
});

View File

@@ -161,6 +161,14 @@ Handlebars.registerHelper('DownloadedStatus', function() {
return "Missing";
});
Handlebars.registerHelper("DownloadedQuality", function() {
if (this.movieFile) {
return this.movieFile.quality.quality.name;
}
return "";
})
Handlebars.registerHelper('inCinemas', function() {
var monthNames = ["January", "February", "March", "April", "May", "June",
@@ -173,10 +181,13 @@ Handlebars.registerHelper('inCinemas', function() {
var year = d.getFullYear();
return "Available: " + day + ". " + month + " " + year;
}
var cinemasDate = new Date(this.inCinemas);
var year = cinemasDate.getFullYear();
var month = monthNames[cinemasDate.getMonth()];
return "In Cinemas: " + month + " " + year;
if (this.inCinemas) {
var cinemasDate = new Date(this.inCinemas);
var year = cinemasDate.getFullYear();
var month = monthNames[cinemasDate.getMonth()];
return "In Cinemas: " + month + " " + year;
}
return "To be announced";
});
Handlebars.registerHelper('tvRageUrl', function() {

View File

@@ -0,0 +1,43 @@
var vent = require('../../vent');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var SelectMovieLayout = require('../Movie/SelectMovieLayout');
module.exports = NzbDroneCell.extend({
className : 'series-title-cell editable',
events : {
'click' : '_onClick'
},
render : function() {
this.$el.empty();
var movie = this.model.get('movie');
if (movie)
{
this.$el.html(movie.title + " (" + movie.year + ")" );
}
this.delegateEvents();
return this;
},
_onClick : function () {
var view = new SelectMovieLayout();
this.listenTo(view, 'manualimport:selected:movie', this._setMovie);
vent.trigger(vent.Commands.OpenModal2Command, view);
},
_setMovie : function (e) {
if (this.model.has('movie') && e.model.id === this.model.get('movie').id) {
return;
}
this.model.set({
movie : e.model.toJSON()
});
}
});

View File

@@ -16,6 +16,7 @@ var QualityCell = require('./Cells/QualityCell');
var FileSizeCell = require('../Cells/FileSizeCell');
var ApprovalStatusCell = require('../Cells/ApprovalStatusCell');
var ManualImportCollection = require('./ManualImportCollection');
var MovieCell = require('./Cells/MovieCell');
var Messenger = require('../Shared/Messenger');
module.exports = Marionette.Layout.extend({
@@ -49,23 +50,29 @@ module.exports = Marionette.Layout.extend({
sortable : true
},
{
name : 'series',
label : 'Series',
cell : SeriesCell,
name : 'movie',
label : 'Movie',
cell : MovieCell,
sortable : true
},
{
name : 'seasonNumber',
label : 'Season',
cell : SeasonCell,
sortable : true
},
{
name : 'episodes',
label : 'Episode(s)',
cell : EpisodesCell,
sortable : false
},
// {
// name : 'series',
// label : 'Series',
// cell : SeriesCell,
// sortable : true
// },
// {
// name : 'seasonNumber',
// label : 'Season',
// cell : SeasonCell,
// sortable : true
// },
// {
// name : 'episodes',
// label : 'Episode(s)',
// cell : EpisodesCell,
// sortable : false
// },
{
name : 'quality',
label : 'Quality',
@@ -161,8 +168,8 @@ module.exports = Marionette.Layout.extend({
},
_automaticImport : function (e) {
CommandController.Execute('downloadedEpisodesScan', {
name : 'downloadedEpisodesScan',
CommandController.Execute('downloadedMovieScan', {
name : 'downloadedMovieScan',
path : e.folder
});
@@ -176,29 +183,36 @@ module.exports = Marionette.Layout.extend({
return;
}
if (_.any(selected, function (model) {
return !model.has('series');
})) {
this._showErrorMessage('Series must be chosen for each selected file');
if(_.any(selected, function(model) {
return !model.has('movie');
})) {
this._showErrorMessage('Movie must be chosen for each selected file');
return;
}
if (_.any(selected, function (model) {
return !model.has('seasonNumber');
})) {
// if (_.any(selected, function (model) {
// return !model.has('series');
// })) {
this._showErrorMessage('Season must be chosen for each selected file');
return;
}
// this._showErrorMessage('Series must be chosen for each selected file');
// return;
// }
if (_.any(selected, function (model) {
return !model.has('episodes') || model.get('episodes').length === 0;
})) {
// if (_.any(selected, function (model) {
// return !model.has('seasonNumber');
// })) {
this._showErrorMessage('One or more episodes must be chosen for each selected file');
return;
}
// this._showErrorMessage('Season must be chosen for each selected file');
// return;
// }
// if (_.any(selected, function (model) {
// return !model.has('episodes') || model.get('episodes').length === 0;
// })) {
// this._showErrorMessage('One or more episodes must be chosen for each selected file');
// return;
// }
var importMode = this.ui.importMode.val();
@@ -207,8 +221,9 @@ module.exports = Marionette.Layout.extend({
files : _.map(selected, function (file) {
return {
path : file.get('path'),
seriesId : file.get('series').id,
episodeIds : _.map(file.get('episodes'), 'id'),
movieId : file.get('movie').id,
// seriesId : file.get('series').id,
// episodeIds : _.map(file.get('episodes'), 'id'),
quality : file.get('quality'),
downloadId : file.get('downloadId')
};

View File

@@ -0,0 +1,101 @@
var _ = require('underscore');
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var MoviesCollection = require('../../Movies/MoviesCollection');
var SelectRow = require('./SelectMovieRow');
module.exports = Marionette.Layout.extend({
template : 'ManualImport/Movie/SelectMovieLayoutTemplate',
regions : {
movie : '.x-movie'
},
ui : {
filter : '.x-filter'
},
columns : [
{
name : 'title',
label : 'Title',
cell : 'String',
sortValue : 'sortTitle'
}
],
initialize : function() {
this.movieCollection = MoviesCollection.clone();
this._setModelCollection();
this.listenTo(this.movieCollection, 'row:selected', this._onSelected);
this.listenTo(this, 'modal:afterShow', this._setFocus);
},
onRender : function() {
this.movieView = new Backgrid.Grid({
columns : this.columns,
collection : this.movieCollection,
className : 'table table-hover season-grid',
row : SelectRow
});
this.movie.show(this.movieView);
this._setupFilter();
},
_setupFilter : function () {
var self = this;
//TODO: This should be a mixin (same as Add Series searching)
this.ui.filter.keyup(function(e) {
if (_.contains([
9,
16,
17,
18,
19,
20,
33,
34,
35,
36,
37,
38,
39,
40,
91,
92,
93
], e.keyCode)) {
return;
}
self._filter(self.ui.filter.val());
});
},
_filter : function (term) {
this.movieCollection.setFilter(['title', term, 'contains']);
this._setModelCollection();
},
_onSelected : function (e) {
this.trigger('manualimport:selected:movie', { model: e.model });
vent.trigger(vent.Commands.CloseModal2Command);
},
_setFocus : function () {
this.ui.filter.focus();
},
_setModelCollection: function () {
var self = this;
_.each(this.movieCollection.models, function (model) {
model.collection = self.movieCollection;
});
}
});

View File

@@ -0,0 +1,30 @@
<div class="modal-content">
<div class="manual-import-modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>
Manual Import - Select Movie
</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<input type="text" class="form-control x-filter" placeholder="Filter movies" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 x-movie"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,13 @@
var Backgrid = require('backgrid');
module.exports = Backgrid.Row.extend({
className : 'select-row select-series-row',
events : {
'click' : '_onClick'
},
_onClick : function() {
this.model.collection.trigger('row:selected', { model: this.model });
}
});

View File

@@ -4,16 +4,25 @@ var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'ManualImport/Summary/ManualImportSummaryViewTemplate',
// initialize : function (options) {
// var episodes = _.map(options.episodes, function (episode) {
// return episode.toJSON();
// });
// this.templateHelpers = {
// file : options.file,
// series : options.series,
// season : options.season,
// episodes : episodes,
// quality : options.quality
// };
// }
initialize : function (options) {
var episodes = _.map(options.episodes, function (episode) {
return episode.toJSON();
});
this.templateHelpers = {
file : options.file,
series : options.series,
season : options.season,
episodes : episodes,
movie : options.movie,
quality : options.quality
};
}

View File

@@ -3,16 +3,8 @@
<dt>Path:</dt>
<dd>{{file}}</dd>
<dt>Series:</dt>
<dd>{{series.title}}</dd>
<dt>Season:</dt>
<dd>{{season.seasonNumber}}</dd>
{{#each episodes}}
<dt>Episode:</dt>
<dd>{{episodeNumber}} - {{title}}</dd>
{{/each}}
<dt>Movie:</dt>
<dd>{{movie.title}} ({{movie.year}})</dd>
<dt>Quality:</dt>
<dd>{{quality.name}}</dd>

View File

@@ -26,7 +26,7 @@
{{else}}
<span class="label label-default">Announced</span>
{{/if_eq}}
<span class="label label-{{DownloadedStatusColor}}">{{DownloadedStatus}}</span>
<span class="label label-{{DownloadedStatusColor}}" title="{{DownloadedQuality}}">{{DownloadedStatus}}</span>
</div>
<div class="col-md-4">
<span class="series-info-links">

View File

@@ -1,24 +1,24 @@
<div class="row">
<div class="series-legend legend col-xs-6 col-sm-4">
<ul class='legend-labels'>
<li><span class="progress-bar"></span>Continuing (All episodes downloaded)</li>
<li><span class="progress-bar-success"></span>Ended (All episodes downloaded)</li>
<li><span class="progress-bar-danger"></span>Missing Episodes (Series monitored)</li>
<li><span class="progress-bar-warning"></span>Missing Episodes (Series not monitored)</li>
<li><span class="progress-bar"></span>Missing, but not yet available.</li>
<li><span class="progress-bar-success"></span>Downloaded and imported.</li>
<li><span class="progress-bar-danger"></span>Missing and monitored.</li>
<li><span class="progress-bar-warning"></span>Missing, but not monitored.</li>
</ul>
</div>
<div class="col-xs-5 col-sm-7">
<div class="row">
<div class="series-stats col-sm-4">
<dl class="dl-horizontal">
<dt>Series</dt>
<dt>Movies</dt>
<dd>{{series}}</dd>
<dt>Ended</dt>
<dd>{{ended}}</dd>
<dt>Released</dt>
<dd>{{released}}</dd>
<dt>Continuing</dt>
<dd>{{continuing}}</dd>
<dt>Announced</dt>
<dd>{{announced}}</dd>
</dl>
</div>

View File

@@ -6,7 +6,7 @@ var ListCollectionView = require('./Overview/SeriesOverviewCollectionView');
var EmptyView = require('./EmptyView');
var MoviesCollection = require('../MoviesCollection');
var InCinemasCell = require('../../Cells/InCinemasCell');
var MovieTitleCell = require('../../Cells/MovieTitleCell2');
var MovieTitleCell = require('../../Cells/MovieTitleCell');
var TemplatedCell = require('../../Cells/TemplatedCell');
var ProfileCell = require('../../Cells/ProfileCell');
var MovieLinksCell = require('../../Cells/MovieLinksCell');
@@ -281,18 +281,18 @@ module.exports = Marionette.Layout.extend({
var series = MoviesCollection.models.length;
var episodes = 0;
var episodeFiles = 0;
var ended = 0;
var continuing = 0;
var announced = 0;
var released = 0;
var monitored = 0;
_.each(MoviesCollection.models, function(model) {
episodes += model.get('episodeCount');
episodeFiles += model.get('episodeFileCount');
if (model.get('status').toLowerCase() === 'ended') {
ended++;
if (model.get('status').toLowerCase() === 'released') {
released++;
} else {
continuing++;
announced++;
}
if (model.get('monitored')) {
@@ -302,9 +302,9 @@ module.exports = Marionette.Layout.extend({
footerModel.set({
series : series,
ended : ended,
continuing : continuing,
monitored : monitored,
released : released,
announced : announced,
monitored : monitored,
unmonitored : series - monitored,
episodes : episodes,
episodeFiles : episodeFiles

View File

@@ -41,7 +41,7 @@
{{profile profileId}}
<span class="label label-{{DownloadedStatusColor}}">{{DownloadedStatus}}</span>
<span class="label label-{{DownloadedStatusColor}}" title="{{DownloadedQuality}}">{{DownloadedStatus}}</span>
</div>
<div class="col-md-4 col-xs-4">
<span class="movie-info-links">

View File

@@ -100,7 +100,18 @@ var Collection = PageableCollection.extend({
return percentOfEpisodes + episodeCount / 1000000;
}
},
inCinemas : {
sortValue : function(model, attr) {
var monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
if (model.get("inCinemas")) {
return model.get("inCinemas");
}
return "2100-01-01";
}
},
path : {
sortValue : function(model) {
var path = model.get('path');

View File

@@ -37,7 +37,7 @@ module.exports = Marionette.Layout.extend({
name : 'edition',
label : 'Edition',
cell : EditionCell,
title : "Edition"
title : "Edition",
},
{
name : 'indexer',
@@ -57,7 +57,7 @@ module.exports = Marionette.Layout.extend({
{
name : 'quality',
label : 'Quality',
cell : QualityCell
cell : QualityCell,
},
{
name : 'rejections',

View File

@@ -1,2 +1 @@
<div id="episode-release-grid" class="table-responsive"></div>
<button class="btn x-search-back">Back</button>

View File

@@ -16,7 +16,7 @@ var Collection = PagableCollection.extend({
sortMappings : {
'quality' : {
sortKey : 'qualityWeight'
sortKey : "qualityWeight"
},
'rejections' : {
sortValue : function(model) {
@@ -30,6 +30,9 @@ var Collection = PagableCollection.extend({
return releaseWeight;
}
},
"edition" : {
sortKey : "edition"
},
'download' : {
sortKey : 'releaseWeight'
},

View File

@@ -78,7 +78,7 @@ module.exports = Marionette.Layout.extend({
CommandController.Execute('renameMovieFiles', {
name : 'renameMovieFiles',
movieId : this.model.id,
movieId : this.model.id,
files : files
});

View File

@@ -23,7 +23,7 @@
</div>
<div class="col-sm-2 col-sm-pull-1">
<input type="number" name="downloadedEpisodesScanInterval" class="form-control" />
<input type="number" name="downloadedMovieScanInterval" class="form-control" />
</div>
</div>
</fieldset>

View File

@@ -1,5 +1,5 @@
<div class="form-group">
<label class="col-sm-3 control-label">Include Series Title</label>
{{!--<label class="col-sm-3 control-label">Include Series Title</label>
<div class="col-sm-9">
<div class="input-group">
@@ -36,7 +36,7 @@
</label>
</div>
</div>
</div>
</div>--}}
<div class="form-group">
<label class="col-sm-3 control-label">Include Quality</label>
@@ -88,7 +88,7 @@
</div>
</div>
<div class="form-group">
{{!--<div class="form-group">
<label class="col-sm-3 control-label">Numbering Style</label>
<div class="col-sm-9">
@@ -99,4 +99,4 @@
<option value="s{season:00}e{episode:00}">s01e05</option>
</select>
</div>
</div>
</div>--}}

View File

@@ -10,6 +10,7 @@ var LogDetailsView = require('../../System/Logs/Table/Details/LogDetailsView');
var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout');
var ManualImportLayout = require('../../ManualImport/ManualImportLayout');
var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout');
var MoviesDetailsLayout = require('../../Movies/Details/MoviesDetailsLayout');
module.exports = Marionette.AppRouter.extend({
initialize : function() {

View File

@@ -2,26 +2,31 @@
<legend>More Info</legend>
<dl class="dl-horizontal info">
<dt>Home page</dt>
<dt>Discord</dt>
<dd><a href="https://discord.gg/AD3UP37">Radarr on Discord</a>
<dt>Reddit</dt>
<dd><a href="https://www.reddit.com/r/radarr/">Radarr Subreddit</a>
{{!--<dt>Home page</dt>
<dd><a href="https://radarr.tdb/">radarr.tdb</a></dd>
<dt>Wiki</dt>
<dd><a href="https://wiki.radarr.tdb/">wiki.radarr.tdb</a></dd>
<!-- <dt>Forums</dt>
<dt>Forums</dt>
<dd><a href="https://forums.sonarr.tv/">forums.sonarr.tv</a></dd>
<dt>Twitter</dt>
<dd><a href="https://twitter.com/sonarrtv">@sonarrtv</a></dd>
<dt>IRC</dt>
<dd><a href="irc://irc.freenode.net/#sonarr">#sonarr on Freenode</a> or (<a href="http://webchat.freenode.net/?channels=#sonarr">webchat</a>)</dd>-->
<dd><a href="irc://irc.freenode.net/#sonarr">#sonarr on Freenode</a> or (<a href="http://webchat.freenode.net/?channels=#sonarr">webchat</a>)</dd>--}}
<dt>Source</dt>
<dd><a href="https://github.com/galli-leo/Radarr/">github.com/galli-leo/Radarr</a></dd>
<dd><a href="https://github.com/Radarr/Radarr">Radarr on Github</a></dd>
<dt>Feature Requests</dt>
<!--<dd><a href="https://forums.sonarr.tv/">forums.sonarr.tv</a></dd>-->
<dd><a href="https://github.com/galli-leo/Radarr/issues">github.com/galli-leo/Radarr/issues</a></dd>
<dd><a href="https://github.com/Radarr/Radarr/issues">Github Issues</a></dd>
</dl>
</fieldset>

View File

@@ -131,7 +131,7 @@ module.exports = Marionette.Layout.extend({
});
Messenger.show({
message : 'Sonarr will shutdown shortly',
message : 'Radarr will shutdown shortly',
type : 'info'
});
},
@@ -143,8 +143,8 @@ module.exports = Marionette.Layout.extend({
});
Messenger.show({
message : 'Sonarr will restart shortly',
message : 'Radarr will restart shortly',
type : 'info'
});
}
});
});

View File

@@ -133,7 +133,7 @@ module.exports = Marionette.Layout.extend({
{
title : 'Rescan Drone Factory Folder',
icon : 'icon-sonarr-refresh',
command : 'downloadedepisodesscan',
command : 'downloadedMovieScan',
properties : { sendUpdates : true }
},
{