From 779809b78beed29d47641d9bccad3dd3fdbefb76 Mon Sep 17 00:00:00 2001 From: ta264 Date: Mon, 14 Oct 2019 21:11:30 +0100 Subject: [PATCH] Fixed: Normalize all line endings to LF in repo --- debian/copyright | 48 +- debian/install | 2 +- .../NetImport/ImportExclusionsModule.cs | 92 +- .../NetImport/ImportExclusionsResource.cs | 92 +- src/NzbDrone.Api/packages.config | 10 - .../BulkImport/AddMultiMoviesFixture.cs | 90 +- .../Files/Indexers/TorrentRss/DanishBits.xml | 622 ++++----- .../Responses/SabnzbdFullStatusResponse.cs | 14 +- .../IndexerSearch/MoviesSearchService.cs | 262 ++-- .../Organizer/FileNameBuilder.cs | 1124 ++++++++--------- .../Validation/Paths/MoviePathValidation.cs | 52 +- src/NzbDrone.Test.Common/App.config | 20 +- 12 files changed, 1209 insertions(+), 1219 deletions(-) delete mode 100644 src/NzbDrone.Api/packages.config diff --git a/debian/copyright b/debian/copyright index 466d37fce..666d9bf2d 100755 --- a/debian/copyright +++ b/debian/copyright @@ -1,24 +1,24 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: nzbdrone -Source: https://github.com/Sonarr/Sonarr - -Files: * -Copyright: 2010-2016 Sonarr - -License: GPL-3.0+ - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This package is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - On Debian systems, the complete text of the GNU General - Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: nzbdrone +Source: https://github.com/Sonarr/Sonarr + +Files: * +Copyright: 2010-2016 Sonarr + +License: GPL-3.0+ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". diff --git a/debian/install b/debian/install index 106b06a9b..1810b9185 100755 --- a/debian/install +++ b/debian/install @@ -1 +1 @@ -nzbdrone_bin/* opt/NzbDrone +nzbdrone_bin/* opt/NzbDrone diff --git a/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs b/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs index 33a396998..4795e8e81 100644 --- a/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs +++ b/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs @@ -1,46 +1,46 @@ -using System.Collections.Generic; -using FluentValidation; -using Radarr.Http.ClientSchema; -using NzbDrone.Core.NetImport; -using NzbDrone.Core.NetImport.ImportExclusions; -using NzbDrone.Core.Validation.Paths; -using Radarr.Http; - -namespace NzbDrone.Api.NetImport -{ - public class ImportExclusionsModule : RadarrRestModule - { - private readonly IImportExclusionsService _exclusionService; - - public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusionsService exclusionService) : base("exclusions") - { - _exclusionService = exclusionService; - GetResourceAll = GetAll; - CreateResource = AddExclusion; - DeleteResource = RemoveExclusion; - GetResourceById = GetById; - } - - public List GetAll() - { - return _exclusionService.GetAllExclusions().ToResource(); - } - - public ImportExclusionsResource GetById(int id) - { - return _exclusionService.GetById(id).ToResource(); - } - - public int AddExclusion(ImportExclusionsResource exclusionResource) - { - var model = exclusionResource.ToModel(); - - return _exclusionService.AddExclusion(model).Id; - } - - public void RemoveExclusion (int id) - { - _exclusionService.RemoveExclusion(new ImportExclusion { Id = id }); - } - } -} +using System.Collections.Generic; +using FluentValidation; +using Radarr.Http.ClientSchema; +using NzbDrone.Core.NetImport; +using NzbDrone.Core.NetImport.ImportExclusions; +using NzbDrone.Core.Validation.Paths; +using Radarr.Http; + +namespace NzbDrone.Api.NetImport +{ + public class ImportExclusionsModule : RadarrRestModule + { + private readonly IImportExclusionsService _exclusionService; + + public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusionsService exclusionService) : base("exclusions") + { + _exclusionService = exclusionService; + GetResourceAll = GetAll; + CreateResource = AddExclusion; + DeleteResource = RemoveExclusion; + GetResourceById = GetById; + } + + public List GetAll() + { + return _exclusionService.GetAllExclusions().ToResource(); + } + + public ImportExclusionsResource GetById(int id) + { + return _exclusionService.GetById(id).ToResource(); + } + + public int AddExclusion(ImportExclusionsResource exclusionResource) + { + var model = exclusionResource.ToModel(); + + return _exclusionService.AddExclusion(model).Id; + } + + public void RemoveExclusion (int id) + { + _exclusionService.RemoveExclusion(new ImportExclusion { Id = id }); + } + } +} diff --git a/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs b/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs index 0e5f26678..e3c598e0a 100644 --- a/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs +++ b/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs @@ -1,46 +1,46 @@ -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Core.NetImport; -using NzbDrone.Core.Movies; - -namespace NzbDrone.Api.NetImport -{ - public class ImportExclusionsResource : ProviderResource - { - //public int Id { get; set; } - public int TmdbId { get; set; } - public string MovieTitle { get; set; } - public int MovieYear { get; set; } - } - - public static class ImportExclusionsResourceMapper - { - public static ImportExclusionsResource ToResource(this Core.NetImport.ImportExclusions.ImportExclusion model) - { - if (model == null) return null; - - return new ImportExclusionsResource - { - Id = model.Id, - TmdbId = model.TmdbId, - MovieTitle = model.MovieTitle, - MovieYear = model.MovieYear - }; - } - - public static List ToResource(this IEnumerable exclusions) - { - return exclusions.Select(ToResource).ToList(); - } - - public static Core.NetImport.ImportExclusions.ImportExclusion ToModel(this ImportExclusionsResource resource) - { - return new Core.NetImport.ImportExclusions.ImportExclusion - { - TmdbId = resource.TmdbId, - MovieTitle = resource.MovieTitle, - MovieYear = resource.MovieYear - }; - } - } -} +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.NetImport; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Api.NetImport +{ + public class ImportExclusionsResource : ProviderResource + { + //public int Id { get; set; } + public int TmdbId { get; set; } + public string MovieTitle { get; set; } + public int MovieYear { get; set; } + } + + public static class ImportExclusionsResourceMapper + { + public static ImportExclusionsResource ToResource(this Core.NetImport.ImportExclusions.ImportExclusion model) + { + if (model == null) return null; + + return new ImportExclusionsResource + { + Id = model.Id, + TmdbId = model.TmdbId, + MovieTitle = model.MovieTitle, + MovieYear = model.MovieYear + }; + } + + public static List ToResource(this IEnumerable exclusions) + { + return exclusions.Select(ToResource).ToList(); + } + + public static Core.NetImport.ImportExclusions.ImportExclusion ToModel(this ImportExclusionsResource resource) + { + return new Core.NetImport.ImportExclusions.ImportExclusion + { + TmdbId = resource.TmdbId, + MovieTitle = resource.MovieTitle, + MovieYear = resource.MovieYear + }; + } + } +} diff --git a/src/NzbDrone.Api/packages.config b/src/NzbDrone.Api/packages.config deleted file mode 100644 index 7d6306763..000000000 --- a/src/NzbDrone.Api/packages.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs b/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs index f54d802ec..fcad2a48e 100644 --- a/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs +++ b/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs @@ -1,28 +1,28 @@ -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using Moq; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Movies; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Movies.Events; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using Moq; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Movies.Events; using System.Collections.Generic; -namespace NzbDrone.Core.Test.BulkImport +namespace NzbDrone.Core.Test.BulkImport { [TestFixture] public class AddMultiMoviesFixture : CoreTest - { + { private List fakeMovies; [SetUp] public void Setup() - { - fakeMovies = Builder.CreateListOfSize(3).BuildList(); + { + fakeMovies = Builder.CreateListOfSize(3).BuildList(); fakeMovies.ForEach(m => - { - m.Path = null; - m.RootFolderPath = @"C:\Test\TV"; + { + m.Path = null; + m.RootFolderPath = @"C:\Test\TV"; }); } @@ -35,41 +35,41 @@ namespace NzbDrone.Core.Test.BulkImport var movies = Subject.AddMovies(fakeMovies); - foreach (Movie movie in movies) - { - movie.Path.Should().NotBeNullOrEmpty(); + foreach (Movie movie in movies) + { + movie.Path.Should().NotBeNullOrEmpty(); } //Subject.GetAllMovies().Should().HaveCount(3); - } - - [Test] - public void movies_added_should_ignore_already_added() - { + } + + [Test] + public void movies_added_should_ignore_already_added() + { Mocker.GetMock() .Setup(s => s.GetMovieFolder(It.IsAny(), null)) - .Returns((Movie m, NamingConfig n) => m.Title); - - Mocker.GetMock().Setup(s => s.All()).Returns(new List { fakeMovies[0] }); - - var movies = Subject.AddMovies(fakeMovies); - - Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); - } - - [Test] - public void movies_added_should_ignore_duplicates() - { - Mocker.GetMock() - .Setup(s => s.GetMovieFolder(It.IsAny(), null)) - .Returns((Movie m, NamingConfig n) => m.Title); - - fakeMovies[2].TmdbId = fakeMovies[0].TmdbId; - - var movies = Subject.AddMovies(fakeMovies); - - Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); + .Returns((Movie m, NamingConfig n) => m.Title); + + Mocker.GetMock().Setup(s => s.All()).Returns(new List { fakeMovies[0] }); + + var movies = Subject.AddMovies(fakeMovies); + + Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); + } + + [Test] + public void movies_added_should_ignore_duplicates() + { + Mocker.GetMock() + .Setup(s => s.GetMovieFolder(It.IsAny(), null)) + .Returns((Movie m, NamingConfig n) => m.Title); + + fakeMovies[2].TmdbId = fakeMovies[0].TmdbId; + + var movies = Subject.AddMovies(fakeMovies); + + Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); } - } + } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Files/Indexers/TorrentRss/DanishBits.xml b/src/NzbDrone.Core.Test/Files/Indexers/TorrentRss/DanishBits.xml index 5b551e732..615802afe 100644 --- a/src/NzbDrone.Core.Test/Files/Indexers/TorrentRss/DanishBits.xml +++ b/src/NzbDrone.Core.Test/Files/Indexers/TorrentRss/DanishBits.xml @@ -1,312 +1,312 @@ - - - - HD Film (RAPiDCOWS) :: Danishbits.org - http://danishbits.org/ - RSS feed for all new HD Film (RAPiDCOWS). - en-us - Thu, 03 May 2018 11:12:03 +0200 - - http://blogs.law.harvard.edu/tech/rss - Gazelle Feed Class - <![CDATA[Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS]]> - - Thu, 03 May 2018 10:56:03 +0200 - https://danishbits.org/torrents.php/Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960749 - - https://danishbits.org/torrents.php/Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960749 - http://danishbits.org/torrents.php?id=961807 - - N/A - - <![CDATA[Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Thu, 03 May 2018 10:14:09 +0200 - https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960732 - - https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960732 - http://danishbits.org/torrents.php?id=961790 - - N/A - - <![CDATA[Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Thu, 03 May 2018 10:14:07 +0200 - https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960731 - - https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960731 - http://danishbits.org/torrents.php?id=961789 - - N/A - - <![CDATA[The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Wed, 02 May 2018 20:54:41 +0200 - https://danishbits.org/torrents.php/The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960314 - - https://danishbits.org/torrents.php/The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960314 - http://danishbits.org/torrents.php?id=961372 - - N/A - - <![CDATA[Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB]]> - - Wed, 02 May 2018 12:18:51 +0200 - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=960040 - - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=960040 - http://danishbits.org/torrents.php?id=961098 - - N/A - - <![CDATA[Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Wed, 02 May 2018 11:04:19 +0200 - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959995 - - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959995 - http://danishbits.org/torrents.php?id=961053 - - N/A - - <![CDATA[Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Wed, 02 May 2018 10:21:45 +0200 - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959975 - - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959975 - http://danishbits.org/torrents.php?id=961033 - - N/A - - <![CDATA[The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Wed, 02 May 2018 01:10:26 +0200 - https://danishbits.org/torrents.php/The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959619 - - https://danishbits.org/torrents.php/The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959619 - http://danishbits.org/torrents.php?id=960677 - - N/A - - <![CDATA[Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Tue, 01 May 2018 20:10:57 +0200 - https://danishbits.org/torrents.php/Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959519 - - https://danishbits.org/torrents.php/Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959519 - http://danishbits.org/torrents.php?id=960577 - - N/A - - <![CDATA[The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 20:00:46 +0200 - https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959512 - - https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959512 - http://danishbits.org/torrents.php?id=960570 - - N/A - - <![CDATA[The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 19:59:30 +0200 - https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959510 - - https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959510 - http://danishbits.org/torrents.php?id=960568 - - N/A - - <![CDATA[The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 18:44:23 +0200 - https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959491 - - https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959491 - http://danishbits.org/torrents.php?id=960549 - - N/A - - <![CDATA[The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 18:44:04 +0200 - https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959490 - - https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959490 - http://danishbits.org/torrents.php?id=960548 - - N/A - - <![CDATA[27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Tue, 01 May 2018 17:58:14 +0200 - https://danishbits.org/torrents.php/27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959484 - - https://danishbits.org/torrents.php/27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959484 - http://danishbits.org/torrents.php?id=960542 - - N/A - - <![CDATA[Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Tue, 01 May 2018 14:18:26 +0200 - https://danishbits.org/torrents.php/Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959367 - - https://danishbits.org/torrents.php/Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959367 - http://danishbits.org/torrents.php?id=960425 - - N/A - - <![CDATA[Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 11:41:52 +0200 - https://danishbits.org/torrents.php/Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959254 - - https://danishbits.org/torrents.php/Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959254 - http://danishbits.org/torrents.php?id=960312 - - N/A - - <![CDATA[90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 03:21:36 +0200 - https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958960 - - https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958960 - http://danishbits.org/torrents.php?id=960018 - - N/A - - <![CDATA[90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 03:19:25 +0200 - https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958958 - - https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958958 - http://danishbits.org/torrents.php?id=960016 - - N/A - - <![CDATA[50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Tue, 01 May 2018 02:30:54 +0200 - https://danishbits.org/torrents.php/50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958929 - - https://danishbits.org/torrents.php/50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958929 - http://danishbits.org/torrents.php?id=959987 - - N/A - - <![CDATA[Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 21:31:23 +0200 - https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958702 - - https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958702 - http://danishbits.org/torrents.php?id=959760 - - N/A - - <![CDATA[Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 21:30:31 +0200 - https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958701 - - https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958701 - http://danishbits.org/torrents.php?id=959759 - - N/A - - <![CDATA[Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 18:09:08 +0200 - https://danishbits.org/torrents.php/Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958582 - - https://danishbits.org/torrents.php/Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958582 - http://danishbits.org/torrents.php?id=959640 - - N/A - - <![CDATA[Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 18:08:59 +0200 - https://danishbits.org/torrents.php/Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958581 - - https://danishbits.org/torrents.php/Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958581 - http://danishbits.org/torrents.php?id=959639 - - N/A - - <![CDATA[Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 13:08:13 +0200 - https://danishbits.org/torrents.php/Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958467 - - https://danishbits.org/torrents.php/Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958467 - http://danishbits.org/torrents.php?id=959525 - - N/A - - <![CDATA[Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Sun, 29 Apr 2018 19:29:45 +0200 - https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957889 - - https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957889 - http://danishbits.org/torrents.php?id=958947 - - N/A - - <![CDATA[Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Sun, 29 Apr 2018 19:29:39 +0200 - https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957888 - - https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957888 - http://danishbits.org/torrents.php?id=958946 - - N/A - - <![CDATA[The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Sun, 29 Apr 2018 18:04:55 +0200 - https://danishbits.org/torrents.php/The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957808 - - https://danishbits.org/torrents.php/The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957808 - http://danishbits.org/torrents.php?id=958866 - - N/A - - <![CDATA[Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> - - Sun, 29 Apr 2018 14:53:03 +0200 - https://danishbits.org/torrents.php/Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957725 - - https://danishbits.org/torrents.php/Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957725 - http://danishbits.org/torrents.php?id=958783 - - N/A - - <![CDATA[Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> - - Sun, 29 Apr 2018 10:28:56 +0200 - https://danishbits.org/torrents.php/Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957570 - - https://danishbits.org/torrents.php/Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957570 - http://danishbits.org/torrents.php?id=958628 - - N/A - - <![CDATA[Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> - - Sun, 29 Apr 2018 08:22:42 +0200 - https://danishbits.org/torrents.php/Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957508 - - https://danishbits.org/torrents.php/Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957508 - http://danishbits.org/torrents.php?id=958566 - - N/A - + + + + HD Film (RAPiDCOWS) :: Danishbits.org + http://danishbits.org/ + RSS feed for all new HD Film (RAPiDCOWS). + en-us + Thu, 03 May 2018 11:12:03 +0200 + + http://blogs.law.harvard.edu/tech/rss + Gazelle Feed Class + <![CDATA[Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS]]> + + Thu, 03 May 2018 10:56:03 +0200 + https://danishbits.org/torrents.php/Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960749 + + https://danishbits.org/torrents.php/Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960749 + http://danishbits.org/torrents.php?id=961807 + + N/A + + <![CDATA[Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Thu, 03 May 2018 10:14:09 +0200 + https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960732 + + https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960732 + http://danishbits.org/torrents.php?id=961790 + + N/A + + <![CDATA[Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Thu, 03 May 2018 10:14:07 +0200 + https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960731 + + https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960731 + http://danishbits.org/torrents.php?id=961789 + + N/A + + <![CDATA[The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Wed, 02 May 2018 20:54:41 +0200 + https://danishbits.org/torrents.php/The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960314 + + https://danishbits.org/torrents.php/The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960314 + http://danishbits.org/torrents.php?id=961372 + + N/A + + <![CDATA[Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB]]> + + Wed, 02 May 2018 12:18:51 +0200 + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=960040 + + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=960040 + http://danishbits.org/torrents.php?id=961098 + + N/A + + <![CDATA[Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Wed, 02 May 2018 11:04:19 +0200 + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959995 + + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959995 + http://danishbits.org/torrents.php?id=961053 + + N/A + + <![CDATA[Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Wed, 02 May 2018 10:21:45 +0200 + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959975 + + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959975 + http://danishbits.org/torrents.php?id=961033 + + N/A + + <![CDATA[The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Wed, 02 May 2018 01:10:26 +0200 + https://danishbits.org/torrents.php/The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959619 + + https://danishbits.org/torrents.php/The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959619 + http://danishbits.org/torrents.php?id=960677 + + N/A + + <![CDATA[Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Tue, 01 May 2018 20:10:57 +0200 + https://danishbits.org/torrents.php/Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959519 + + https://danishbits.org/torrents.php/Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959519 + http://danishbits.org/torrents.php?id=960577 + + N/A + + <![CDATA[The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 20:00:46 +0200 + https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959512 + + https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959512 + http://danishbits.org/torrents.php?id=960570 + + N/A + + <![CDATA[The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 19:59:30 +0200 + https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959510 + + https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959510 + http://danishbits.org/torrents.php?id=960568 + + N/A + + <![CDATA[The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 18:44:23 +0200 + https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959491 + + https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959491 + http://danishbits.org/torrents.php?id=960549 + + N/A + + <![CDATA[The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 18:44:04 +0200 + https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959490 + + https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959490 + http://danishbits.org/torrents.php?id=960548 + + N/A + + <![CDATA[27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Tue, 01 May 2018 17:58:14 +0200 + https://danishbits.org/torrents.php/27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959484 + + https://danishbits.org/torrents.php/27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959484 + http://danishbits.org/torrents.php?id=960542 + + N/A + + <![CDATA[Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Tue, 01 May 2018 14:18:26 +0200 + https://danishbits.org/torrents.php/Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959367 + + https://danishbits.org/torrents.php/Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959367 + http://danishbits.org/torrents.php?id=960425 + + N/A + + <![CDATA[Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 11:41:52 +0200 + https://danishbits.org/torrents.php/Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959254 + + https://danishbits.org/torrents.php/Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959254 + http://danishbits.org/torrents.php?id=960312 + + N/A + + <![CDATA[90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 03:21:36 +0200 + https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958960 + + https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958960 + http://danishbits.org/torrents.php?id=960018 + + N/A + + <![CDATA[90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 03:19:25 +0200 + https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958958 + + https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958958 + http://danishbits.org/torrents.php?id=960016 + + N/A + + <![CDATA[50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Tue, 01 May 2018 02:30:54 +0200 + https://danishbits.org/torrents.php/50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958929 + + https://danishbits.org/torrents.php/50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958929 + http://danishbits.org/torrents.php?id=959987 + + N/A + + <![CDATA[Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 21:31:23 +0200 + https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958702 + + https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958702 + http://danishbits.org/torrents.php?id=959760 + + N/A + + <![CDATA[Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 21:30:31 +0200 + https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958701 + + https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958701 + http://danishbits.org/torrents.php?id=959759 + + N/A + + <![CDATA[Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 18:09:08 +0200 + https://danishbits.org/torrents.php/Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958582 + + https://danishbits.org/torrents.php/Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958582 + http://danishbits.org/torrents.php?id=959640 + + N/A + + <![CDATA[Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 18:08:59 +0200 + https://danishbits.org/torrents.php/Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958581 + + https://danishbits.org/torrents.php/Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958581 + http://danishbits.org/torrents.php?id=959639 + + N/A + + <![CDATA[Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 13:08:13 +0200 + https://danishbits.org/torrents.php/Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958467 + + https://danishbits.org/torrents.php/Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958467 + http://danishbits.org/torrents.php?id=959525 + + N/A + + <![CDATA[Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Sun, 29 Apr 2018 19:29:45 +0200 + https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957889 + + https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957889 + http://danishbits.org/torrents.php?id=958947 + + N/A + + <![CDATA[Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Sun, 29 Apr 2018 19:29:39 +0200 + https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957888 + + https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957888 + http://danishbits.org/torrents.php?id=958946 + + N/A + + <![CDATA[The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Sun, 29 Apr 2018 18:04:55 +0200 + https://danishbits.org/torrents.php/The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957808 + + https://danishbits.org/torrents.php/The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957808 + http://danishbits.org/torrents.php?id=958866 + + N/A + + <![CDATA[Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> + + Sun, 29 Apr 2018 14:53:03 +0200 + https://danishbits.org/torrents.php/Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957725 + + https://danishbits.org/torrents.php/Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957725 + http://danishbits.org/torrents.php?id=958783 + + N/A + + <![CDATA[Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> + + Sun, 29 Apr 2018 10:28:56 +0200 + https://danishbits.org/torrents.php/Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957570 + + https://danishbits.org/torrents.php/Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957570 + http://danishbits.org/torrents.php?id=958628 + + N/A + + <![CDATA[Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> + + Sun, 29 Apr 2018 08:22:42 +0200 + https://danishbits.org/torrents.php/Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957508 + + https://danishbits.org/torrents.php/Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957508 + http://danishbits.org/torrents.php?id=958566 + + N/A + \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs index 48c9710c5..4ba37f66d 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs @@ -1,7 +1,7 @@ -namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses -{ - public class SabnzbdFullStatusResponse - { - public SabnzbdFullStatus Status { get; set; } - } -} +namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses +{ + public class SabnzbdFullStatusResponse + { + public SabnzbdFullStatus Status { get; set; } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs index 64f7ddacf..e8d90a6d7 100644 --- a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs @@ -1,131 +1,131 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using NLog; -using NzbDrone.Common.Instrumentation.Extensions; -using NzbDrone.Core.Download; -using NzbDrone.Core.Messaging.Commands; -using NzbDrone.Core.Movies; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Queue; -using NzbDrone.Core.DecisionEngine; - -namespace NzbDrone.Core.IndexerSearch -{ - public class MovieSearchService : IExecute, IExecute, IExecute - { - private readonly IMovieService _movieService; - private readonly IMovieCutoffService _movieCutoffService; - private readonly ISearchForNzb _nzbSearchService; - private readonly IProcessDownloadDecisions _processDownloadDecisions; - private readonly IQueueService _queueService; - private readonly Logger _logger; - - public MovieSearchService(IMovieService movieService, - IMovieCutoffService movieCutoffService, - ISearchForNzb nzbSearchService, - IProcessDownloadDecisions processDownloadDecisions, - IQueueService queueService, - Logger logger) - { - _movieService = movieService; - _movieCutoffService = movieCutoffService; - _nzbSearchService = nzbSearchService; - _processDownloadDecisions = processDownloadDecisions; - _queueService = queueService; - _logger = logger; - } - - public void Execute(MoviesSearchCommand message) - { - var downloadedCount = 0; - foreach (var movieId in message.MovieIds) - { - var movies = _movieService.GetMovie(movieId); - - if (!movies.Monitored) - { - _logger.Debug("Movie {0} is not monitored, skipping search", movies.Title); - continue; - } - - var decisions = _nzbSearchService.MovieSearch(movieId, false, false);//_nzbSearchService.SeasonSearch(message.MovieId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual); - downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count; - - } - _logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount); - } - - public void Execute(MissingMoviesSearchCommand message) - { - var pagingSpec = new PagingSpec - { - Page = 1, - PageSize = 100000, - SortDirection = SortDirection.Ascending, - SortKey = "Id" - }; - - pagingSpec.FilterExpressions.Add(v => v.Monitored == true); - List movies = _movieService.MoviesWithoutFiles(pagingSpec).Records.ToList(); - - var queue = _queueService.GetQueue().Select(q => q.Movie.Id); - var missing = movies.Where(e => !queue.Contains(e.Id)).ToList(); - - SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual); - - } - - public void Execute(CutoffUnmetMoviesSearchCommand message) - { - var pagingSpec = new PagingSpec - { - Page = 1, - PageSize = 100000, - SortDirection = SortDirection.Ascending, - SortKey = "Id" - }; - - pagingSpec.FilterExpressions.Add(v => v.Monitored == true); - - List movies = _movieCutoffService.MoviesWhereCutoffUnmet(pagingSpec).Records.ToList(); - - var queue = _queueService.GetQueue().Where(q => q.Movie != null).Select(q => q.Movie.Id); - var missing = movies.Where(e => !queue.Contains(e.Id)).ToList(); - - SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual); - - } - - private void SearchForMissingMovies(List movies, bool userInvokedSearch) - { - _logger.ProgressInfo("Performing missing search for {0} movies", movies.Count); - var downloadedCount = 0; - - foreach (var movieId in movies.GroupBy(e => e.Id)) - { - List decisions; - - try - { - decisions = _nzbSearchService.MovieSearch(movieId.Key, userInvokedSearch, false); - } - catch (Exception ex) - { - var message = String.Format("Unable to search for missing movie {0}", movieId.Key); - _logger.Error(ex, message); - continue; - } - - var processed = _processDownloadDecisions.ProcessDecisions(decisions); - - downloadedCount += processed.Grabbed.Count; - } - - _logger.ProgressInfo("Completed missing search for {0} movies. {1} reports downloaded.", movies.Count, downloadedCount); - } - - - - } -} +using System; +using System.Linq; +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Download; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Queue; +using NzbDrone.Core.DecisionEngine; + +namespace NzbDrone.Core.IndexerSearch +{ + public class MovieSearchService : IExecute, IExecute, IExecute + { + private readonly IMovieService _movieService; + private readonly IMovieCutoffService _movieCutoffService; + private readonly ISearchForNzb _nzbSearchService; + private readonly IProcessDownloadDecisions _processDownloadDecisions; + private readonly IQueueService _queueService; + private readonly Logger _logger; + + public MovieSearchService(IMovieService movieService, + IMovieCutoffService movieCutoffService, + ISearchForNzb nzbSearchService, + IProcessDownloadDecisions processDownloadDecisions, + IQueueService queueService, + Logger logger) + { + _movieService = movieService; + _movieCutoffService = movieCutoffService; + _nzbSearchService = nzbSearchService; + _processDownloadDecisions = processDownloadDecisions; + _queueService = queueService; + _logger = logger; + } + + public void Execute(MoviesSearchCommand message) + { + var downloadedCount = 0; + foreach (var movieId in message.MovieIds) + { + var movies = _movieService.GetMovie(movieId); + + if (!movies.Monitored) + { + _logger.Debug("Movie {0} is not monitored, skipping search", movies.Title); + continue; + } + + var decisions = _nzbSearchService.MovieSearch(movieId, false, false);//_nzbSearchService.SeasonSearch(message.MovieId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual); + downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count; + + } + _logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount); + } + + public void Execute(MissingMoviesSearchCommand message) + { + var pagingSpec = new PagingSpec + { + Page = 1, + PageSize = 100000, + SortDirection = SortDirection.Ascending, + SortKey = "Id" + }; + + pagingSpec.FilterExpressions.Add(v => v.Monitored == true); + List movies = _movieService.MoviesWithoutFiles(pagingSpec).Records.ToList(); + + var queue = _queueService.GetQueue().Select(q => q.Movie.Id); + var missing = movies.Where(e => !queue.Contains(e.Id)).ToList(); + + SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual); + + } + + public void Execute(CutoffUnmetMoviesSearchCommand message) + { + var pagingSpec = new PagingSpec + { + Page = 1, + PageSize = 100000, + SortDirection = SortDirection.Ascending, + SortKey = "Id" + }; + + pagingSpec.FilterExpressions.Add(v => v.Monitored == true); + + List movies = _movieCutoffService.MoviesWhereCutoffUnmet(pagingSpec).Records.ToList(); + + var queue = _queueService.GetQueue().Where(q => q.Movie != null).Select(q => q.Movie.Id); + var missing = movies.Where(e => !queue.Contains(e.Id)).ToList(); + + SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual); + + } + + private void SearchForMissingMovies(List movies, bool userInvokedSearch) + { + _logger.ProgressInfo("Performing missing search for {0} movies", movies.Count); + var downloadedCount = 0; + + foreach (var movieId in movies.GroupBy(e => e.Id)) + { + List decisions; + + try + { + decisions = _nzbSearchService.MovieSearch(movieId.Key, userInvokedSearch, false); + } + catch (Exception ex) + { + var message = String.Format("Unable to search for missing movie {0}", movieId.Key); + _logger.Error(ex, message); + continue; + } + + var processed = _processDownloadDecisions.ProcessDecisions(decisions); + + downloadedCount += processed.Grabbed.Count; + } + + _logger.ProgressInfo("Completed missing search for {0} movies. {1} reports downloaded.", movies.Count, downloadedCount); + } + + + + } +} diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 42dd8cd40..562536001 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -1,562 +1,562 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using NLog; -using NzbDrone.Common.EnsureThat; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.MediaFiles.MediaInfo; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Movies; - -namespace NzbDrone.Core.Organizer -{ - public interface IBuildFileNames - { - string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null); - string BuildFilePath(Movie movie, string fileName, string extension); - string BuildMoviePath(Movie movie, NamingConfig namingConfig = null); - BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); - string GetMovieFolder(Movie movie, NamingConfig namingConfig = null); - } - - public class FileNameBuilder : IBuildFileNames - { - private readonly INamingConfigService _namingConfigService; - private readonly IQualityDefinitionService _qualityDefinitionService; - private readonly IUpdateMediaInfo _mediaInfoUpdater; - private readonly Logger _logger; - - private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._\[(]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._)\]]*)\}", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex TagsRegex = new Regex(@"(?\{tags(?:\:0+)?})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?s?{season(?:\:0+)?}(?[- ._]?[ex])(?{episode(?:\:0+)?}))(?[- ._]+?(?={))?", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?{absolute(?:\:0+)?})(?[- ._]+?(?={))?", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(Clean)?Title\})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex MovieTitleRegex = new Regex(@"(?\{((?:(Movie|Original))(?[- ._])(Clean)?(Title|Filename)(The)?)\})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); - private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled); - - private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - //TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc) - private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' }; - - public FileNameBuilder(INamingConfigService namingConfigService, - IQualityDefinitionService qualityDefinitionService, - IUpdateMediaInfo mediaInfoUpdater, - Logger logger) - { - _namingConfigService = namingConfigService; - _qualityDefinitionService = qualityDefinitionService; - _mediaInfoUpdater = mediaInfoUpdater; - _logger = logger; - } - - public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - if (!namingConfig.RenameEpisodes) - { - return GetOriginalTitle(movieFile); - } - - var pattern = namingConfig.StandardMovieFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - UpdateMediaInfoIfNeeded(pattern, movieFile, movie); - - AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); - AddImdbIdTokens(tokenHandlers, movie.ImdbId); - AddQualityTokens(tokenHandlers, movie, movieFile); - AddMediaInfoTokens(tokenHandlers, movieFile); - AddMovieFileTokens(tokenHandlers, movieFile); - AddTagsTokens(tokenHandlers, movieFile); - - var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); - fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); - fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); - - return fileName; - } - - public string BuildFilePath(Movie movie, string fileName, string extension) - { - Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); - - var path = ""; - - if (movie.PathState > 0) - { - path = movie.Path; - } - else - { - path = BuildMoviePath(movie); - } - - return Path.Combine(path, fileName + extension); - } - - public string BuildMoviePath(Movie movie, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var path = movie.Path; - var directory = new DirectoryInfo(path).Name; - var parentDirectoryPath = new DirectoryInfo(path).Parent.FullName; - - var movieFile = movie.MovieFile; - - var pattern = namingConfig.MovieFolderFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); - AddImdbIdTokens(tokenHandlers, movie.ImdbId); - - if(movie.MovieFile != null) - { - - AddQualityTokens(tokenHandlers, movie, movieFile); - AddMediaInfoTokens(tokenHandlers, movieFile); - AddMovieFileTokens(tokenHandlers, movieFile); - AddTagsTokens(tokenHandlers, movieFile); - } - else - { - AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" }); - } - - - var directoryName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); - directoryName = FileNameCleanupRegex.Replace(directoryName, match => match.Captures[0].Value[0].ToString()); - directoryName = TrimSeparatorsRegex.Replace(directoryName, string.Empty); - - return Path.Combine(parentDirectoryPath, directoryName); - } - - public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) - { - return new BasicNamingConfig(); //For now let's be lazy - - } - - public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var movieFile = movie.MovieFile; - - var pattern = namingConfig.MovieFolderFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); - AddImdbIdTokens(tokenHandlers, movie.ImdbId); - - if (movie.MovieFile != null) - { - AddQualityTokens(tokenHandlers, movie, movieFile); - AddMediaInfoTokens(tokenHandlers, movieFile); - AddMovieFileTokens(tokenHandlers, movieFile); - AddTagsTokens(tokenHandlers, movieFile); - } - else - { - AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}"}); - } - - string name = ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig); - return CleanFolderName(name, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat); - } - - public static string CleanTitle(string title) - { - title = title.Replace("&", "and"); - title = ScenifyReplaceChars.Replace(title, " "); - title = ScenifyRemoveChars.Replace(title, string.Empty); - - return title; - } - - public static string TitleThe(string title) - { - string[] prefixes = { "The ", "An ", "A " }; - - if (title.Length < 5) - { - return title; - } - - foreach (string prefix in prefixes) - { - int prefix_length = prefix.Length; - if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower()) - { - title = title.Substring(prefix_length) + ", " + prefix.Trim(); - break; - } - } - - return title.Trim(); - } - - public static string CleanFileName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete) - { - var colonReplacementFormat = colonReplacement.GetFormatString(); - - string result = name; - string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" }; - string[] goodCharacters = { "+", "+", "", "", "!", "-", colonReplacementFormat, "", "" }; - - for (int i = 0; i < badCharacters.Length; i++) - { - result = result.Replace(badCharacters[i], replace ? goodCharacters[i] : string.Empty); - } - - return result.Trim(); - } - - public static string CleanFolderName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete) - { - name = FileNameCleanupRegex.Replace(name, match => match.Captures[0].Value[0].ToString()); - name = name.Trim(' ', '.'); - - return CleanFileName(name, replace, colonReplacement); - } - - private void AddMovieTokens(Dictionary> tokenHandlers, Movie movie) - { - tokenHandlers["{Movie Title}"] = m => movie.Title; - tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(movie.Title); - tokenHandlers["{Movie Title The}"] = m => TitleThe(movie.Title); - } - - private void AddTagsTokens(Dictionary> tokenHandlers, MovieFile movieFile) - { - if (movieFile.Edition.IsNotNullOrWhiteSpace()) - { - tokenHandlers["{Edition Tags}"] = m => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(movieFile.Edition.ToLower()); - } - } - - private void AddReleaseDateTokens(Dictionary> tokenHandlers, int releaseYear) - { - tokenHandlers["{Release Year}"] = m => string.Format("{0}", releaseYear.ToString()); //Do I need m.CustomFormat? - } - - private void AddImdbIdTokens(Dictionary> tokenHandlers, string imdbId) - { - tokenHandlers["{IMDb Id}"] = m => $"{imdbId}"; - } - - private void AddMovieFileTokens(Dictionary> tokenHandlers, MovieFile movieFile) - { - tokenHandlers["{Original Title}"] = m => GetOriginalTitle(movieFile); - tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(movieFile); - //tokenHandlers["{IMDb Id}"] = m => - tokenHandlers["{Release Group}"] = m => movieFile.ReleaseGroup ?? m.DefaultValue("Radarr"); - } - - private void AddQualityTokens(Dictionary> tokenHandlers, Movie movie, MovieFile movieFile) - { - if (movieFile?.Quality?.Quality == null) - { - tokenHandlers["{Quality Full}"] = m => ""; - tokenHandlers["{Quality Title}"] = m => ""; - tokenHandlers["{Quality Proper}"] = m => ""; - tokenHandlers["{Quality Real}"] = m => ""; - return; - } - - var qualityTitle = _qualityDefinitionService.Get(movieFile.Quality.Quality).Title; - var qualityProper = GetQualityProper(movie, movieFile.Quality); - var qualityReal = GetQualityReal(movie, movieFile.Quality); - - tokenHandlers["{Quality Full}"] = m => String.Format("{0} {1} {2}", qualityTitle, qualityProper, qualityReal); - tokenHandlers["{Quality Title}"] = m => qualityTitle; - tokenHandlers["{Quality Proper}"] = m => qualityProper; - tokenHandlers["{Quality Real}"] = m => qualityReal; - } - - private const string MediaInfoVideoDynamicRangeToken = "{MediaInfo VideoDynamicRange}"; - private static readonly IDictionary MinimumMediaInfoSchemaRevisions = - new Dictionary(FileNameBuilderTokenEqualityComparer.Instance) - { - {MediaInfoVideoDynamicRangeToken, 5} - }; - - private void AddMediaInfoTokens(Dictionary> tokenHandlers, MovieFile movieFile) - { - if (movieFile.MediaInfo == null) - { - _logger.Trace("Media info is unavailable for {0}", movieFile); - - return; - } - - var sceneName = movieFile.GetSceneOrFileName(); - - - var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName); - var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName); - var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo); - var audioLanguages = movieFile.MediaInfo.AudioLanguages ?? string.Empty; - var subtitles = movieFile.MediaInfo.Subtitles ?? string.Empty; - - var mediaInfoAudioLanguages = GetLanguagesToken(audioLanguages); - if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) - { - mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]"; - } - - var mediaInfoAudioLanguagesAll = mediaInfoAudioLanguages; - if (mediaInfoAudioLanguages == "[EN]") - { - mediaInfoAudioLanguages = string.Empty; - } - - var mediaInfoSubtitleLanguages = GetLanguagesToken(subtitles); - if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) - { - mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]"; - } - - var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; - var audioChannelsFormatted = audioChannels > 0 ? - audioChannels.ToString("F1", CultureInfo.InvariantCulture) : - string.Empty; - - var mediaInfo3D = movieFile.MediaInfo.VideoMultiViewCount > 1 ? "3D" : string.Empty; - - tokenHandlers["{MediaInfo Video}"] = m => videoCodec; - tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec; - tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth; - - tokenHandlers["{MediaInfo Audio}"] = m => audioCodec; - tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec; - tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted; - tokenHandlers["{MediaInfo AudioLanguages}"] = m => mediaInfoAudioLanguages; - tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => mediaInfoAudioLanguagesAll; - - tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => mediaInfoSubtitleLanguages; - tokenHandlers["{MediaInfo SubtitleLanguagesAll}"] = m => mediaInfoSubtitleLanguages; - - tokenHandlers["{MediaInfo 3D}"] = m => mediaInfo3D; - - tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}"; - tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}"; - - tokenHandlers[MediaInfoVideoDynamicRangeToken] = - m => MediaInfoFormatter.FormatVideoDynamicRange(movieFile.MediaInfo); - } - - private string GetLanguagesToken(string mediaInfoLanguages) - { - List tokens = new List(); - foreach (var item in mediaInfoLanguages.Split('/')) - { - if (!string.IsNullOrWhiteSpace(item)) - tokens.Add(item.Trim()); - } - - var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); - for (int i = 0; i < tokens.Count; i++) - { - try - { - var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName == tokens[i]); - - if (cultureInfo != null) - tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper(); - } - catch - { - } - } - - return string.Join("+", tokens.Distinct()); - } - - private void UpdateMediaInfoIfNeeded(string pattern, MovieFile movieFile, Movie movie) - { - if (movie.Path.IsNullOrWhiteSpace()) - { - return; - } - - var schemaRevision = movieFile.MediaInfo != null ? movieFile.MediaInfo.SchemaRevision : 0; - var matches = TitleRegex.Matches(pattern); - - var shouldUpdateMediaInfo = matches.Cast() - .Select(m => MinimumMediaInfoSchemaRevisions.GetValueOrDefault(m.Value, -1)) - .Any(r => schemaRevision < r); - - if (shouldUpdateMediaInfo) - { - _mediaInfoUpdater.Update(movieFile, movie); - } - } - - private string ReplaceTokens(string pattern, Dictionary> tokenHandlers, NamingConfig namingConfig) - { - return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig)); - } - - private string ReplaceToken(Match match, Dictionary> tokenHandlers, NamingConfig namingConfig) - { - var tokenMatch = new TokenMatch - { - RegexMatch = match, - Prefix = match.Groups["prefix"].Value, - Separator = match.Groups["separator"].Value, - Suffix = match.Groups["suffix"].Value, - Token = match.Groups["token"].Value, - CustomFormat = match.Groups["customFormat"].Value - }; - - if (tokenMatch.CustomFormat.IsNullOrWhiteSpace()) - { - tokenMatch.CustomFormat = null; - } - - var tokenHandler = tokenHandlers.GetValueOrDefault(tokenMatch.Token, m => string.Empty); - - var replacementText = tokenHandler(tokenMatch).Trim(); - - if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsLower(t))) - { - replacementText = replacementText.ToLower(); - } - else if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsUpper(t))) - { - replacementText = replacementText.ToUpper(); - } - - if (!tokenMatch.Separator.IsNullOrWhiteSpace()) - { - replacementText = replacementText.Replace(" ", tokenMatch.Separator); - } - - replacementText = CleanFileName(replacementText, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat); - - if (!replacementText.IsNullOrWhiteSpace()) - { - replacementText = tokenMatch.Prefix + replacementText + tokenMatch.Suffix; - } - - return replacementText; - } - - private string ReplaceNumberToken(string token, int value) - { - var split = token.Trim('{', '}').Split(':'); - if (split.Length == 1) return value.ToString("0"); - - return value.ToString(split[1]); - } - - private string GetQualityProper(Movie movie, QualityModel quality) - { - if (quality.Revision.Version > 1) - { - return "Proper"; - } - - return String.Empty; - } - - private string GetQualityReal(Movie movie, QualityModel quality) - { - if (quality.Revision.Real > 0) - { - return "REAL"; - } - - return string.Empty; - } - - private string GetOriginalTitle(MovieFile movieFile) - { - if (movieFile.SceneName.IsNullOrWhiteSpace()) - { - return GetOriginalFileName(movieFile); - } - - return movieFile.SceneName; - } - - private string GetOriginalFileName(MovieFile movieFile) - { - if (movieFile.RelativePath.IsNullOrWhiteSpace()) - { - return Path.GetFileNameWithoutExtension(movieFile.Path); - } - - return Path.GetFileNameWithoutExtension(movieFile.RelativePath); - } - } - - internal sealed class TokenMatch - { - public Match RegexMatch { get; set; } - public string Prefix { get; set; } - public string Separator { get; set; } - public string Suffix { get; set; } - public string Token { get; set; } - public string CustomFormat { get; set; } - - public string DefaultValue(string defaultValue) - { - if (string.IsNullOrEmpty(Prefix) && string.IsNullOrEmpty(Suffix)) - { - return defaultValue; - } - else - { - return string.Empty; - } - } - } - - public enum MultiEpisodeStyle - { - Extend = 0, - Duplicate = 1, - Repeat = 2, - Scene = 3, - Range = 4, - PrefixedRange = 5 - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using NLog; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Core.Organizer +{ + public interface IBuildFileNames + { + string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null); + string BuildFilePath(Movie movie, string fileName, string extension); + string BuildMoviePath(Movie movie, NamingConfig namingConfig = null); + BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); + string GetMovieFolder(Movie movie, NamingConfig namingConfig = null); + } + + public class FileNameBuilder : IBuildFileNames + { + private readonly INamingConfigService _namingConfigService; + private readonly IQualityDefinitionService _qualityDefinitionService; + private readonly IUpdateMediaInfo _mediaInfoUpdater; + private readonly Logger _logger; + + private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._\[(]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._)\]]*)\}", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex TagsRegex = new Regex(@"(?\{tags(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?s?{season(?:\:0+)?}(?[- ._]?[ex])(?{episode(?:\:0+)?}))(?[- ._]+?(?={))?", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?{absolute(?:\:0+)?})(?[- ._]+?(?={))?", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(Clean)?Title\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex MovieTitleRegex = new Regex(@"(?\{((?:(Movie|Original))(?[- ._])(Clean)?(Title|Filename)(The)?)\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); + private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled); + + private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + //TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc) + private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' }; + + public FileNameBuilder(INamingConfigService namingConfigService, + IQualityDefinitionService qualityDefinitionService, + IUpdateMediaInfo mediaInfoUpdater, + Logger logger) + { + _namingConfigService = namingConfigService; + _qualityDefinitionService = qualityDefinitionService; + _mediaInfoUpdater = mediaInfoUpdater; + _logger = logger; + } + + public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + if (!namingConfig.RenameEpisodes) + { + return GetOriginalTitle(movieFile); + } + + var pattern = namingConfig.StandardMovieFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + UpdateMediaInfoIfNeeded(pattern, movieFile, movie); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); + + var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); + fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); + fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); + + return fileName; + } + + public string BuildFilePath(Movie movie, string fileName, string extension) + { + Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); + + var path = ""; + + if (movie.PathState > 0) + { + path = movie.Path; + } + else + { + path = BuildMoviePath(movie); + } + + return Path.Combine(path, fileName + extension); + } + + public string BuildMoviePath(Movie movie, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var path = movie.Path; + var directory = new DirectoryInfo(path).Name; + var parentDirectoryPath = new DirectoryInfo(path).Parent.FullName; + + var movieFile = movie.MovieFile; + + var pattern = namingConfig.MovieFolderFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + + if(movie.MovieFile != null) + { + + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); + } + else + { + AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" }); + } + + + var directoryName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); + directoryName = FileNameCleanupRegex.Replace(directoryName, match => match.Captures[0].Value[0].ToString()); + directoryName = TrimSeparatorsRegex.Replace(directoryName, string.Empty); + + return Path.Combine(parentDirectoryPath, directoryName); + } + + public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) + { + return new BasicNamingConfig(); //For now let's be lazy + + } + + public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var movieFile = movie.MovieFile; + + var pattern = namingConfig.MovieFolderFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + + if (movie.MovieFile != null) + { + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); + } + else + { + AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}"}); + } + + string name = ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig); + return CleanFolderName(name, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat); + } + + public static string CleanTitle(string title) + { + title = title.Replace("&", "and"); + title = ScenifyReplaceChars.Replace(title, " "); + title = ScenifyRemoveChars.Replace(title, string.Empty); + + return title; + } + + public static string TitleThe(string title) + { + string[] prefixes = { "The ", "An ", "A " }; + + if (title.Length < 5) + { + return title; + } + + foreach (string prefix in prefixes) + { + int prefix_length = prefix.Length; + if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower()) + { + title = title.Substring(prefix_length) + ", " + prefix.Trim(); + break; + } + } + + return title.Trim(); + } + + public static string CleanFileName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete) + { + var colonReplacementFormat = colonReplacement.GetFormatString(); + + string result = name; + string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" }; + string[] goodCharacters = { "+", "+", "", "", "!", "-", colonReplacementFormat, "", "" }; + + for (int i = 0; i < badCharacters.Length; i++) + { + result = result.Replace(badCharacters[i], replace ? goodCharacters[i] : string.Empty); + } + + return result.Trim(); + } + + public static string CleanFolderName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete) + { + name = FileNameCleanupRegex.Replace(name, match => match.Captures[0].Value[0].ToString()); + name = name.Trim(' ', '.'); + + return CleanFileName(name, replace, colonReplacement); + } + + private void AddMovieTokens(Dictionary> tokenHandlers, Movie movie) + { + tokenHandlers["{Movie Title}"] = m => movie.Title; + tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(movie.Title); + tokenHandlers["{Movie Title The}"] = m => TitleThe(movie.Title); + } + + private void AddTagsTokens(Dictionary> tokenHandlers, MovieFile movieFile) + { + if (movieFile.Edition.IsNotNullOrWhiteSpace()) + { + tokenHandlers["{Edition Tags}"] = m => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(movieFile.Edition.ToLower()); + } + } + + private void AddReleaseDateTokens(Dictionary> tokenHandlers, int releaseYear) + { + tokenHandlers["{Release Year}"] = m => string.Format("{0}", releaseYear.ToString()); //Do I need m.CustomFormat? + } + + private void AddImdbIdTokens(Dictionary> tokenHandlers, string imdbId) + { + tokenHandlers["{IMDb Id}"] = m => $"{imdbId}"; + } + + private void AddMovieFileTokens(Dictionary> tokenHandlers, MovieFile movieFile) + { + tokenHandlers["{Original Title}"] = m => GetOriginalTitle(movieFile); + tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(movieFile); + //tokenHandlers["{IMDb Id}"] = m => + tokenHandlers["{Release Group}"] = m => movieFile.ReleaseGroup ?? m.DefaultValue("Radarr"); + } + + private void AddQualityTokens(Dictionary> tokenHandlers, Movie movie, MovieFile movieFile) + { + if (movieFile?.Quality?.Quality == null) + { + tokenHandlers["{Quality Full}"] = m => ""; + tokenHandlers["{Quality Title}"] = m => ""; + tokenHandlers["{Quality Proper}"] = m => ""; + tokenHandlers["{Quality Real}"] = m => ""; + return; + } + + var qualityTitle = _qualityDefinitionService.Get(movieFile.Quality.Quality).Title; + var qualityProper = GetQualityProper(movie, movieFile.Quality); + var qualityReal = GetQualityReal(movie, movieFile.Quality); + + tokenHandlers["{Quality Full}"] = m => String.Format("{0} {1} {2}", qualityTitle, qualityProper, qualityReal); + tokenHandlers["{Quality Title}"] = m => qualityTitle; + tokenHandlers["{Quality Proper}"] = m => qualityProper; + tokenHandlers["{Quality Real}"] = m => qualityReal; + } + + private const string MediaInfoVideoDynamicRangeToken = "{MediaInfo VideoDynamicRange}"; + private static readonly IDictionary MinimumMediaInfoSchemaRevisions = + new Dictionary(FileNameBuilderTokenEqualityComparer.Instance) + { + {MediaInfoVideoDynamicRangeToken, 5} + }; + + private void AddMediaInfoTokens(Dictionary> tokenHandlers, MovieFile movieFile) + { + if (movieFile.MediaInfo == null) + { + _logger.Trace("Media info is unavailable for {0}", movieFile); + + return; + } + + var sceneName = movieFile.GetSceneOrFileName(); + + + var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName); + var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName); + var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo); + var audioLanguages = movieFile.MediaInfo.AudioLanguages ?? string.Empty; + var subtitles = movieFile.MediaInfo.Subtitles ?? string.Empty; + + var mediaInfoAudioLanguages = GetLanguagesToken(audioLanguages); + if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) + { + mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]"; + } + + var mediaInfoAudioLanguagesAll = mediaInfoAudioLanguages; + if (mediaInfoAudioLanguages == "[EN]") + { + mediaInfoAudioLanguages = string.Empty; + } + + var mediaInfoSubtitleLanguages = GetLanguagesToken(subtitles); + if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) + { + mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]"; + } + + var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; + var audioChannelsFormatted = audioChannels > 0 ? + audioChannels.ToString("F1", CultureInfo.InvariantCulture) : + string.Empty; + + var mediaInfo3D = movieFile.MediaInfo.VideoMultiViewCount > 1 ? "3D" : string.Empty; + + tokenHandlers["{MediaInfo Video}"] = m => videoCodec; + tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec; + tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth; + + tokenHandlers["{MediaInfo Audio}"] = m => audioCodec; + tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec; + tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted; + tokenHandlers["{MediaInfo AudioLanguages}"] = m => mediaInfoAudioLanguages; + tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => mediaInfoAudioLanguagesAll; + + tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => mediaInfoSubtitleLanguages; + tokenHandlers["{MediaInfo SubtitleLanguagesAll}"] = m => mediaInfoSubtitleLanguages; + + tokenHandlers["{MediaInfo 3D}"] = m => mediaInfo3D; + + tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}"; + tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}"; + + tokenHandlers[MediaInfoVideoDynamicRangeToken] = + m => MediaInfoFormatter.FormatVideoDynamicRange(movieFile.MediaInfo); + } + + private string GetLanguagesToken(string mediaInfoLanguages) + { + List tokens = new List(); + foreach (var item in mediaInfoLanguages.Split('/')) + { + if (!string.IsNullOrWhiteSpace(item)) + tokens.Add(item.Trim()); + } + + var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); + for (int i = 0; i < tokens.Count; i++) + { + try + { + var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName == tokens[i]); + + if (cultureInfo != null) + tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper(); + } + catch + { + } + } + + return string.Join("+", tokens.Distinct()); + } + + private void UpdateMediaInfoIfNeeded(string pattern, MovieFile movieFile, Movie movie) + { + if (movie.Path.IsNullOrWhiteSpace()) + { + return; + } + + var schemaRevision = movieFile.MediaInfo != null ? movieFile.MediaInfo.SchemaRevision : 0; + var matches = TitleRegex.Matches(pattern); + + var shouldUpdateMediaInfo = matches.Cast() + .Select(m => MinimumMediaInfoSchemaRevisions.GetValueOrDefault(m.Value, -1)) + .Any(r => schemaRevision < r); + + if (shouldUpdateMediaInfo) + { + _mediaInfoUpdater.Update(movieFile, movie); + } + } + + private string ReplaceTokens(string pattern, Dictionary> tokenHandlers, NamingConfig namingConfig) + { + return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig)); + } + + private string ReplaceToken(Match match, Dictionary> tokenHandlers, NamingConfig namingConfig) + { + var tokenMatch = new TokenMatch + { + RegexMatch = match, + Prefix = match.Groups["prefix"].Value, + Separator = match.Groups["separator"].Value, + Suffix = match.Groups["suffix"].Value, + Token = match.Groups["token"].Value, + CustomFormat = match.Groups["customFormat"].Value + }; + + if (tokenMatch.CustomFormat.IsNullOrWhiteSpace()) + { + tokenMatch.CustomFormat = null; + } + + var tokenHandler = tokenHandlers.GetValueOrDefault(tokenMatch.Token, m => string.Empty); + + var replacementText = tokenHandler(tokenMatch).Trim(); + + if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsLower(t))) + { + replacementText = replacementText.ToLower(); + } + else if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsUpper(t))) + { + replacementText = replacementText.ToUpper(); + } + + if (!tokenMatch.Separator.IsNullOrWhiteSpace()) + { + replacementText = replacementText.Replace(" ", tokenMatch.Separator); + } + + replacementText = CleanFileName(replacementText, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat); + + if (!replacementText.IsNullOrWhiteSpace()) + { + replacementText = tokenMatch.Prefix + replacementText + tokenMatch.Suffix; + } + + return replacementText; + } + + private string ReplaceNumberToken(string token, int value) + { + var split = token.Trim('{', '}').Split(':'); + if (split.Length == 1) return value.ToString("0"); + + return value.ToString(split[1]); + } + + private string GetQualityProper(Movie movie, QualityModel quality) + { + if (quality.Revision.Version > 1) + { + return "Proper"; + } + + return String.Empty; + } + + private string GetQualityReal(Movie movie, QualityModel quality) + { + if (quality.Revision.Real > 0) + { + return "REAL"; + } + + return string.Empty; + } + + private string GetOriginalTitle(MovieFile movieFile) + { + if (movieFile.SceneName.IsNullOrWhiteSpace()) + { + return GetOriginalFileName(movieFile); + } + + return movieFile.SceneName; + } + + private string GetOriginalFileName(MovieFile movieFile) + { + if (movieFile.RelativePath.IsNullOrWhiteSpace()) + { + return Path.GetFileNameWithoutExtension(movieFile.Path); + } + + return Path.GetFileNameWithoutExtension(movieFile.RelativePath); + } + } + + internal sealed class TokenMatch + { + public Match RegexMatch { get; set; } + public string Prefix { get; set; } + public string Separator { get; set; } + public string Suffix { get; set; } + public string Token { get; set; } + public string CustomFormat { get; set; } + + public string DefaultValue(string defaultValue) + { + if (string.IsNullOrEmpty(Prefix) && string.IsNullOrEmpty(Suffix)) + { + return defaultValue; + } + else + { + return string.Empty; + } + } + } + + public enum MultiEpisodeStyle + { + Extend = 0, + Duplicate = 1, + Repeat = 2, + Scene = 3, + Range = 4, + PrefixedRange = 5 + } +} diff --git a/src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs b/src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs index 41690b7cb..ee2d55dca 100644 --- a/src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs +++ b/src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs @@ -1,27 +1,27 @@ -using FluentValidation.Validators; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Movies; - -namespace NzbDrone.Core.Validation.Paths -{ - public class MoviePathValidator : PropertyValidator - { - private readonly IMovieService _moviesService; - - public MoviePathValidator(IMovieService moviesService) - : base("Path is already configured for another movie") - { - _moviesService = moviesService; - } - - protected override bool IsValid(PropertyValidatorContext context) - { - if (context.PropertyValue == null) return true; - - dynamic instance = context.ParentContext.InstanceToValidate; - var instanceId = (int)instance.Id; - - return (!_moviesService.GetAllMovies().Exists(s => s.Path.PathEquals(context.PropertyValue.ToString()) && s.Id != instanceId)); - } - } +using FluentValidation.Validators; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Core.Validation.Paths +{ + public class MoviePathValidator : PropertyValidator + { + private readonly IMovieService _moviesService; + + public MoviePathValidator(IMovieService moviesService) + : base("Path is already configured for another movie") + { + _moviesService = moviesService; + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return true; + + dynamic instance = context.ParentContext.InstanceToValidate; + var instanceId = (int)instance.Id; + + return (!_moviesService.GetAllMovies().Exists(s => s.Path.PathEquals(context.PropertyValue.ToString()) && s.Id != instanceId)); + } + } } \ No newline at end of file diff --git a/src/NzbDrone.Test.Common/App.config b/src/NzbDrone.Test.Common/App.config index 3eb419e7c..69a7d9e36 100644 --- a/src/NzbDrone.Test.Common/App.config +++ b/src/NzbDrone.Test.Common/App.config @@ -1,11 +1,11 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file