mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-18 21:35:27 -04:00
Compare commits
8 Commits
v3
...
3.0.10.1566
| Author | SHA1 | Date | |
|---|---|---|---|
| 67e2fe551a | |||
| 5db5b1dace | |||
| c7919f80de | |||
| 1a1d427c42 | |||
| 9263fc1564 | |||
| 8ab040f612 | |||
| d6dff451e0 | |||
| ac7afc351c |
@@ -45,6 +45,7 @@ function EditIndexerModalContent(props) {
|
|||||||
tags,
|
tags,
|
||||||
fields,
|
fields,
|
||||||
priority,
|
priority,
|
||||||
|
seasonSearchMaximumSingleEpisodeAge,
|
||||||
protocol,
|
protocol,
|
||||||
downloadClientId
|
downloadClientId
|
||||||
} = item;
|
} = item;
|
||||||
@@ -153,6 +154,23 @@ function EditIndexerModalContent(props) {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
>
|
||||||
|
<FormLabel>Maximum Single Episode Age</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.NUMBER}
|
||||||
|
name="seasonSearchMaximumSingleEpisodeAge"
|
||||||
|
helpText="During a full season search only season packs will be allowed when the season's last episode is older than this setting. Standard series only. Use 0 to disable."
|
||||||
|
min={0}
|
||||||
|
unit="days"
|
||||||
|
{...seasonSearchMaximumSingleEpisodeAge}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
|
|||||||
@@ -265,6 +265,11 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
protected virtual void MoveFileInternal(string source, string destination)
|
protected virtual void MoveFileInternal(string source, string destination)
|
||||||
{
|
{
|
||||||
|
if (File.Exists(destination))
|
||||||
|
{
|
||||||
|
throw new FileAlreadyExistsException("File already exists", destination);
|
||||||
|
}
|
||||||
|
|
||||||
File.Move(source, destination);
|
File.Move(source, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Disk
|
||||||
|
{
|
||||||
|
public class FileAlreadyExistsException : Exception
|
||||||
|
{
|
||||||
|
public string Filename { get; set; }
|
||||||
|
|
||||||
|
public FileAlreadyExistsException(string message, string filename) : base(message)
|
||||||
|
{
|
||||||
|
Filename = filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using FluentAssertions;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SingleEpisodeAgeDownloadDecisionFixture : CoreTest<SeasonPackOnlySpecification>
|
||||||
|
{
|
||||||
|
private RemoteEpisode parseResultMulti;
|
||||||
|
private RemoteEpisode parseResultSingle;
|
||||||
|
private Series series;
|
||||||
|
private List<Episode> episodes;
|
||||||
|
private SeasonSearchCriteria multiSearch;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
series = Builder<Series>.CreateNew()
|
||||||
|
.With(s => s.Seasons = Builder<Season>.CreateListOfSize(1).Build().ToList())
|
||||||
|
.With(s => s.SeriesType = SeriesTypes.Standard)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
episodes = new List<Episode>();
|
||||||
|
episodes.Add(CreateEpisodeStub(1, 400));
|
||||||
|
episodes.Add(CreateEpisodeStub(2, 370));
|
||||||
|
episodes.Add(CreateEpisodeStub(3, 340));
|
||||||
|
episodes.Add(CreateEpisodeStub(4, 310));
|
||||||
|
|
||||||
|
multiSearch = new SeasonSearchCriteria();
|
||||||
|
multiSearch.Episodes = episodes.ToList();
|
||||||
|
multiSearch.SeasonNumber = 1;
|
||||||
|
|
||||||
|
parseResultMulti = new RemoteEpisode
|
||||||
|
{
|
||||||
|
Series = series,
|
||||||
|
Release = new ReleaseInfo(),
|
||||||
|
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, new Revision(version: 2)), FullSeason = true },
|
||||||
|
Episodes = episodes.ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
parseResultSingle = new RemoteEpisode
|
||||||
|
{
|
||||||
|
Series = series,
|
||||||
|
Release = new ReleaseInfo(),
|
||||||
|
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, new Revision(version: 2)) },
|
||||||
|
Episodes = new List<Episode>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Episode CreateEpisodeStub(int number, int age)
|
||||||
|
{
|
||||||
|
return new Episode() {
|
||||||
|
SeasonNumber = 1,
|
||||||
|
EpisodeNumber = number,
|
||||||
|
AirDateUtc = DateTime.UtcNow.AddDays(-age)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(1, 200, false)]
|
||||||
|
[TestCase(4, 200, false)]
|
||||||
|
[TestCase(1, 600, true)]
|
||||||
|
[TestCase(1, 365, true)]
|
||||||
|
[TestCase(4, 365, true)]
|
||||||
|
[TestCase(1, 0, true)]
|
||||||
|
public void single_episode_release(int episode, int SeasonSearchMaximumSingleEpisodeAge, bool expectedResult)
|
||||||
|
{
|
||||||
|
parseResultSingle.Release.SeasonSearchMaximumSingleEpisodeAge = SeasonSearchMaximumSingleEpisodeAge;
|
||||||
|
parseResultSingle.Episodes.Clear();
|
||||||
|
parseResultSingle.Episodes.Add(episodes.Find(e => e.EpisodeNumber == episode));
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(parseResultSingle, multiSearch).Accepted.Should().Be(expectedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
// should always accept all season packs
|
||||||
|
[TestCase(200, true)]
|
||||||
|
[TestCase(600, true)]
|
||||||
|
[TestCase(365, true)]
|
||||||
|
[TestCase(0, true)]
|
||||||
|
public void multi_episode_release(int SeasonSearchMaximumSingleEpisodeAge, bool expectedResult)
|
||||||
|
{
|
||||||
|
parseResultMulti.Release.SeasonSearchMaximumSingleEpisodeAge = SeasonSearchMaximumSingleEpisodeAge;
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(parseResultMulti, multiSearch).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,8 +82,8 @@ namespace NzbDrone.Core.Test.Extras.Subtitles
|
|||||||
[TestCase("Series Title - S01E01.srt", "Series Title - S01E01.srt")]
|
[TestCase("Series Title - S01E01.srt", "Series Title - S01E01.srt")]
|
||||||
[TestCase("Series.Title.S01E01.en.srt", "Series Title - S01E01.en.srt")]
|
[TestCase("Series.Title.S01E01.en.srt", "Series Title - S01E01.en.srt")]
|
||||||
[TestCase("Series.Title.S01E01.english.srt", "Series Title - S01E01.en.srt")]
|
[TestCase("Series.Title.S01E01.english.srt", "Series Title - S01E01.en.srt")]
|
||||||
[TestCase("Series-Title-S01E01-fr-cc.srt", "Series Title - S01E01.fr.srt")]
|
[TestCase("Series-Title-S01E01-fr-cc.srt", "Series Title - S01E01.fr.cc.srt")]
|
||||||
[TestCase("Series Title S01E01_en_sdh_forced.srt", "Series Title - S01E01.en.srt")]
|
[TestCase("Series Title S01E01_en_sdh_forced.srt", "Series Title - S01E01.en.sdh.forced.srt")]
|
||||||
[TestCase("Series_Title_S01E01 en.srt", "Series Title - S01E01.en.srt")]
|
[TestCase("Series_Title_S01E01 en.srt", "Series Title - S01E01.en.srt")]
|
||||||
[TestCase(@"Subs\S01E01.en.srt", "Series Title - S01E01.en.srt")]
|
[TestCase(@"Subs\S01E01.en.srt", "Series Title - S01E01.en.srt")]
|
||||||
[TestCase(@"Subs\Series.Title.S01E01\2_en.srt", "Series Title - S01E01.en.srt")]
|
[TestCase(@"Subs\Series.Title.S01E01\2_en.srt", "Series Title - S01E01.en.srt")]
|
||||||
@@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.Extras.Subtitles
|
|||||||
var files = new List<string>
|
var files = new List<string>
|
||||||
{
|
{
|
||||||
Path.Combine(_episodeFolder, "Series.Title.S01E01.en.srt").AsOsAgnostic(),
|
Path.Combine(_episodeFolder, "Series.Title.S01E01.en.srt").AsOsAgnostic(),
|
||||||
Path.Combine(_episodeFolder, "Series.Title.S01E01.english.srt").AsOsAgnostic(),
|
Path.Combine(_episodeFolder, "Series.Title.S01E01.eng.srt").AsOsAgnostic(),
|
||||||
Path.Combine(_episodeFolder, "Subs", "Series_Title_S01E01_en_forced.srt").AsOsAgnostic(),
|
Path.Combine(_episodeFolder, "Subs", "Series_Title_S01E01_en_forced.srt").AsOsAgnostic(),
|
||||||
Path.Combine(_episodeFolder, "Subs", "Series.Title.S01E01", "2_fr.srt").AsOsAgnostic()
|
Path.Combine(_episodeFolder, "Subs", "Series.Title.S01E01", "2_fr.srt").AsOsAgnostic()
|
||||||
};
|
};
|
||||||
@@ -113,7 +113,7 @@ namespace NzbDrone.Core.Test.Extras.Subtitles
|
|||||||
{
|
{
|
||||||
"Series Title - S01E01.1.en.srt",
|
"Series Title - S01E01.1.en.srt",
|
||||||
"Series Title - S01E01.2.en.srt",
|
"Series Title - S01E01.2.en.srt",
|
||||||
"Series Title - S01E01.3.en.srt",
|
"Series Title - S01E01.en.forced.srt",
|
||||||
"Series Title - S01E01.fr.srt",
|
"Series Title - S01E01.fr.srt",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,6 +126,35 @@ namespace NzbDrone.Core.Test.Extras.Subtitles
|
|||||||
results[i].RelativePath.AsOsAgnostic().PathEquals(Path.Combine("Season 1", expectedOutputs[i]).AsOsAgnostic()).Should().Be(true);
|
results[i].RelativePath.AsOsAgnostic().PathEquals(Path.Combine("Season 1", expectedOutputs[i]).AsOsAgnostic()).Should().Be(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_import_multiple_subtitle_files_per_language_with_tags()
|
||||||
|
{
|
||||||
|
var files = new List<string>
|
||||||
|
{
|
||||||
|
Path.Combine(_episodeFolder, "Series.Title.S01E01.en.forced.cc.srt").AsOsAgnostic(),
|
||||||
|
Path.Combine(_episodeFolder, "Series.Title.S01E01.other.en.forced.cc.srt").AsOsAgnostic(),
|
||||||
|
Path.Combine(_episodeFolder, "Series.Title.S01E01.en.forced.sdh.srt").AsOsAgnostic(),
|
||||||
|
Path.Combine(_episodeFolder, "Series.Title.S01E01.en.forced.default.srt").AsOsAgnostic(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedOutputs = new[]
|
||||||
|
{
|
||||||
|
"Series Title - S01E01.1.en.forced.cc.srt",
|
||||||
|
"Series Title - S01E01.2.en.forced.cc.srt",
|
||||||
|
"Series Title - S01E01.en.forced.sdh.srt",
|
||||||
|
"Series Title - S01E01.en.forced.default.srt"
|
||||||
|
};
|
||||||
|
|
||||||
|
var results = Subject.ImportFiles(_localEpisode, _episodeFile, files, true).ToList();
|
||||||
|
|
||||||
|
results.Count().Should().Be(expectedOutputs.Length);
|
||||||
|
|
||||||
|
for (int i = 0; i < expectedOutputs.Length; i++)
|
||||||
|
{
|
||||||
|
results[i].RelativePath.AsOsAgnostic().PathEquals(Path.Combine("Season 1", expectedOutputs[i]).AsOsAgnostic()).Should().Be(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[TestCase("sub.srt", "Series Title - S01E01.srt")]
|
[TestCase("sub.srt", "Series Title - S01E01.srt")]
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
[TestCase("The Series 42 S09E13 1.54 GB WEB-RIP 1080p Dual-Audio 2019 MKV", false)]
|
[TestCase("The Series 42 S09E13 1.54 GB WEB-RIP 1080p Dual-Audio 2019 MKV", false)]
|
||||||
[TestCase("Series.Title.1x04.ITA.1080p.WEBMux.x264-NovaRip", false)]
|
[TestCase("Series.Title.1x04.ITA.1080p.WEBMux.x264-NovaRip", false)]
|
||||||
[TestCase("Series.Title.2019.S02E07.Chapter.15.The.Believer.4Kto1080p.DSNYP.Webrip.x265.10bit.EAC3.5.1.Atmos.GokiTAoE", false)]
|
[TestCase("Series.Title.2019.S02E07.Chapter.15.The.Believer.4Kto1080p.DSNYP.Webrip.x265.10bit.EAC3.5.1.Atmos.GokiTAoE", false)]
|
||||||
|
[TestCase("Series.Title.S01.1080p.AMZN.WEB-Rip.DDP5.1.H.264-Telly", false)]
|
||||||
public void should_parse_webrip1080p_quality(string title, bool proper)
|
public void should_parse_webrip1080p_quality(string title, bool proper)
|
||||||
{
|
{
|
||||||
ParseAndVerifyQuality(title, Quality.WEBRip1080p, proper);
|
ParseAndVerifyQuality(title, Quality.WEBRip1080p, proper);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
removeFailedDownloads = false;
|
removeFailedDownloads = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var updateClientCmd = conn.CreateCommand(tran, $"UPDATE DownloadClients SET RemoveCompletedDownloads = (CASE WHEN Implementation IN (\"RTorrent\", \"Flood\") THEN 0 ELSE ? END), RemoveFailedDownloads = ?"))
|
using (var updateClientCmd = conn.CreateCommand(tran, $"UPDATE DownloadClients SET RemoveCompletedDownloads = (CASE WHEN Implementation IN ('RTorrent', 'Flood') THEN 0 ELSE ? END), RemoveFailedDownloads = ?"))
|
||||||
{
|
{
|
||||||
updateClientCmd.AddParameter(removeCompletedDownloads ? 1 : 0);
|
updateClientCmd.AddParameter(removeCompletedDownloads ? 1 : 0);
|
||||||
updateClientCmd.AddParameter(removeFailedDownloads ? 1 : 0);
|
updateClientCmd.AddParameter(removeFailedDownloads ? 1 : 0);
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(170)]
|
||||||
|
public class add_language_tags_to_subtitle_files : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("SubtitleFiles").AddColumn("LanguageTags").AsString().Nullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(172)]
|
||||||
|
public class add_SeasonSearchMaximumSingleEpisodeAge_to_indexers : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("Indexers").AddColumn("SeasonSearchMaximumSingleEpisodeAge").AsInt32().NotNullable().WithDefaultValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,11 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
|||||||
|
|
||||||
public override bool SupportsTransactions => true;
|
public override bool SupportsTransactions => true;
|
||||||
|
|
||||||
|
public override bool TableExists(string schemaName, string tableName)
|
||||||
|
{
|
||||||
|
return Exists("select count(*) from sqlite_master where name='{0}' and type='table'", tableName);
|
||||||
|
}
|
||||||
|
|
||||||
public override void Process(AlterColumnExpression expression)
|
public override void Process(AlterColumnExpression expression)
|
||||||
{
|
{
|
||||||
var tableDefinition = GetTableSchema(expression.TableName);
|
var tableDefinition = GetTableSchema(expression.TableName);
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
|
{
|
||||||
|
public class SeasonPackOnlySpecification : IDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public SeasonPackOnlySpecification(IConfigService configService, Logger logger)
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||||
|
public RejectionType Type => RejectionType.Permanent;
|
||||||
|
|
||||||
|
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||||
|
{
|
||||||
|
if (searchCriteria == null || searchCriteria.Episodes.Count == 1)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subject.Release.SeasonSearchMaximumSingleEpisodeAge > 0)
|
||||||
|
{
|
||||||
|
if (subject.Series.SeriesType == SeriesTypes.Standard && !subject.ParsedEpisodeInfo.FullSeason && subject.Episodes.Count >= 1)
|
||||||
|
{
|
||||||
|
// test against episodes of the same season in the current search, and make sure they have an air date
|
||||||
|
var subset = searchCriteria.Episodes.Where(e => e.AirDateUtc.HasValue && e.SeasonNumber == subject.Episodes.First().SeasonNumber).ToList();
|
||||||
|
|
||||||
|
if (subset.Count() > 0 && subset.Max(e => e.AirDateUtc).Value.Before(DateTime.UtcNow - TimeSpan.FromDays(subject.Release.SeasonSearchMaximumSingleEpisodeAge)))
|
||||||
|
{
|
||||||
|
_logger.Debug("Release {0}: last episode in this season aired more than {1} days ago, season pack required.", subject.Release.Title, subject.Release.SeasonSearchMaximumSingleEpisodeAge);
|
||||||
|
return Decision.Reject("Last episode in this season aired more than {0} days ago, season pack required.", subject.Release.SeasonSearchMaximumSingleEpisodeAge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using NzbDrone.Core.Extras.Files;
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Extras.Subtitles
|
namespace NzbDrone.Core.Extras.Subtitles
|
||||||
@@ -6,5 +7,11 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
public class SubtitleFile : ExtraFile
|
public class SubtitleFile : ExtraFile
|
||||||
{
|
{
|
||||||
public Language Language { get; set; }
|
public Language Language { get; set; }
|
||||||
|
|
||||||
|
public string AggregateString => Language + LanguageTagsAsString + Extension;
|
||||||
|
|
||||||
|
public List<string> LanguageTags { get; set; }
|
||||||
|
|
||||||
|
private string LanguageTagsAsString => string.Join(".", LanguageTags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
foreach (var episodeFile in episodeFiles)
|
foreach (var episodeFile in episodeFiles)
|
||||||
{
|
{
|
||||||
var groupedExtraFilesForEpisodeFile = subtitleFiles.Where(m => m.EpisodeFileId == episodeFile.Id)
|
var groupedExtraFilesForEpisodeFile = subtitleFiles.Where(m => m.EpisodeFileId == episodeFile.Id)
|
||||||
.GroupBy(s => s.Language + s.Extension).ToList();
|
.GroupBy(s => s.AggregateString).ToList();
|
||||||
|
|
||||||
foreach (var group in groupedExtraFilesForEpisodeFile)
|
foreach (var group in groupedExtraFilesForEpisodeFile)
|
||||||
{
|
{
|
||||||
@@ -81,7 +81,7 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
|
|
||||||
foreach (var subtitleFile in group)
|
foreach (var subtitleFile in group)
|
||||||
{
|
{
|
||||||
var suffix = GetSuffix(subtitleFile.Language, copy, groupCount > 1);
|
var suffix = GetSuffix(subtitleFile.Language, copy, subtitleFile.LanguageTags, groupCount > 1);
|
||||||
movedFiles.AddIfNotNull(MoveFile(series, episodeFile, subtitleFile, suffix));
|
movedFiles.AddIfNotNull(MoveFile(series, episodeFile, subtitleFile, suffix));
|
||||||
|
|
||||||
copy++;
|
copy++;
|
||||||
@@ -116,7 +116,7 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Filename match
|
// Filename match
|
||||||
if (Path.GetFileNameWithoutExtension(file).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase))
|
if (Path.GetFileNameWithoutExtension(file).StartsWithIgnoreCase(sourceFileName))
|
||||||
{
|
{
|
||||||
matchingFiles.Add(file);
|
matchingFiles.Add(file);
|
||||||
continue;
|
continue;
|
||||||
@@ -175,16 +175,24 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subtitleFiles = new List<Tuple<string, Language, string>>();
|
var subtitleFiles = new List<SubtitleFile>();
|
||||||
|
|
||||||
foreach (string file in matchingFiles)
|
foreach (string file in matchingFiles)
|
||||||
{
|
{
|
||||||
var language = LanguageParser.ParseSubtitleLanguage(file);
|
var language = LanguageParser.ParseSubtitleLanguage(file);
|
||||||
var extension = Path.GetExtension(file);
|
var extension = Path.GetExtension(file);
|
||||||
subtitleFiles.Add(new Tuple<string, Language, string>(file, language, extension));
|
var languageTags = LanguageParser.ParseLanguageTags(file);
|
||||||
|
var subFile = new SubtitleFile
|
||||||
|
{
|
||||||
|
Language = language,
|
||||||
|
Extension = extension
|
||||||
|
};
|
||||||
|
subFile.LanguageTags = languageTags.ToList();
|
||||||
|
subFile.RelativePath = PathExtensions.GetRelativePath(sourceFolder, file);
|
||||||
|
subtitleFiles.Add(subFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
var groupedSubtitleFiles = subtitleFiles.GroupBy(s => s.Item2 + s.Item3).ToList();
|
var groupedSubtitleFiles = subtitleFiles.GroupBy(s => s.AggregateString).ToList();
|
||||||
|
|
||||||
foreach (var group in groupedSubtitleFiles)
|
foreach (var group in groupedSubtitleFiles)
|
||||||
{
|
{
|
||||||
@@ -193,14 +201,15 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
|
|
||||||
foreach (var file in group)
|
foreach (var file in group)
|
||||||
{
|
{
|
||||||
|
var path = Path.Combine(sourceFolder, file.RelativePath);
|
||||||
|
var language = file.Language;
|
||||||
|
var extension = file.Extension;
|
||||||
|
var suffix = GetSuffix(language, copy, file.LanguageTags, groupCount > 1);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var path = file.Item1;
|
|
||||||
var language = file.Item2;
|
|
||||||
var extension = file.Item3;
|
|
||||||
var suffix = GetSuffix(language, copy, groupCount > 1);
|
|
||||||
var subtitleFile = ImportFile(localEpisode.Series, episodeFile, path, isReadOnly, extension, suffix);
|
var subtitleFile = ImportFile(localEpisode.Series, episodeFile, path, isReadOnly, extension, suffix);
|
||||||
subtitleFile.Language = language;
|
subtitleFile.Language = language;
|
||||||
|
subtitleFile.LanguageTags = file.LanguageTags;
|
||||||
|
|
||||||
_mediaFileAttributeService.SetFilePermissions(path);
|
_mediaFileAttributeService.SetFilePermissions(path);
|
||||||
_subtitleFileService.Upsert(subtitleFile);
|
_subtitleFileService.Upsert(subtitleFile);
|
||||||
@@ -211,7 +220,7 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Warn(ex, "Failed to import subtitle file: {0}", file.Item1);
|
_logger.Warn(ex, "Failed to import subtitle file: {0}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,7 +228,7 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
return importedFiles;
|
return importedFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSuffix(Language language, int copy, bool multipleCopies = false)
|
private string GetSuffix(Language language, int copy, List<string> languageTags, bool multipleCopies = false)
|
||||||
{
|
{
|
||||||
var suffixBuilder = new StringBuilder();
|
var suffixBuilder = new StringBuilder();
|
||||||
|
|
||||||
@@ -235,6 +244,12 @@ namespace NzbDrone.Core.Extras.Subtitles
|
|||||||
suffixBuilder.Append(IsoLanguages.Get(language).TwoLetterCode);
|
suffixBuilder.Append(IsoLanguages.Get(language).TwoLetterCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (languageTags.Any())
|
||||||
|
{
|
||||||
|
suffixBuilder.Append(".");
|
||||||
|
suffixBuilder.Append(string.Join(".", languageTags));
|
||||||
|
}
|
||||||
|
|
||||||
return suffixBuilder.ToString();
|
return suffixBuilder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Indexers
|
|||||||
public abstract string Name { get; }
|
public abstract string Name { get; }
|
||||||
public abstract DownloadProtocol Protocol { get; }
|
public abstract DownloadProtocol Protocol { get; }
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||||
|
|
||||||
public abstract bool SupportsRss { get; }
|
public abstract bool SupportsRss { get; }
|
||||||
public abstract bool SupportsSearch { get; }
|
public abstract bool SupportsSearch { get; }
|
||||||
@@ -81,6 +82,7 @@ namespace NzbDrone.Core.Indexers
|
|||||||
c.Indexer = Definition.Name;
|
c.Indexer = Definition.Name;
|
||||||
c.DownloadProtocol = Protocol;
|
c.DownloadProtocol = Protocol;
|
||||||
c.IndexerPriority = ((IndexerDefinition)Definition).Priority;
|
c.IndexerPriority = ((IndexerDefinition)Definition).Priority;
|
||||||
|
c.SeasonSearchMaximumSingleEpisodeAge = ((IndexerDefinition)Definition).SeasonSearchMaximumSingleEpisodeAge;
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace NzbDrone.Core.Indexers
|
|||||||
public bool SupportsRss { get; set; }
|
public bool SupportsRss { get; set; }
|
||||||
public bool SupportsSearch { get; set; }
|
public bool SupportsSearch { get; set; }
|
||||||
public int Priority { get; set; } = 25;
|
public int Priority { get; set; } = 25;
|
||||||
|
public int SeasonSearchMaximumSingleEpisodeAge { get; set; } = 0;
|
||||||
|
|
||||||
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;
|
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||||||
|
|
||||||
if (qualityCompare < 0)
|
if (qualityCompare < 0)
|
||||||
{
|
{
|
||||||
_logger.Debug("This file isn't a quality upgrade for all episodes. Skipping {0}", localEpisode.Path);
|
_logger.Debug("This file isn't a quality upgrade for all episodes. New Quality is {0}. Skipping {1}", localEpisode.Quality.Quality, localEpisode.Path);
|
||||||
return Decision.Reject("Not an upgrade for existing episode file(s)");
|
return Decision.Reject("Not an upgrade for existing episode file(s). New Quality is {0}", localEpisode.Quality.Quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same quality, is not a language upgrade, propers/repacks are preferred and it is not a revision update
|
// Same quality, is not a language upgrade, propers/repacks are preferred and it is not a revision update
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
|
|
||||||
_eventAggregator.PublishEvent(new EpisodeFileRenamedEvent(series, episodeFile, previousPath));
|
_eventAggregator.PublishEvent(new EpisodeFileRenamedEvent(series, episodeFile, previousPath));
|
||||||
}
|
}
|
||||||
|
catch (FileAlreadyExistsException ex)
|
||||||
|
{
|
||||||
|
_logger.Warn("File not renamed, there is already a file at the destination: {0}", ex.Filename);
|
||||||
|
}
|
||||||
catch (SameFilenameException ex)
|
catch (SameFilenameException ex)
|
||||||
{
|
{
|
||||||
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
|
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
|
|
||||||
@@ -152,6 +154,25 @@ namespace NzbDrone.Core.Parser
|
|||||||
|
|
||||||
return Language.Unknown;
|
return Language.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> ParseLanguageTags(string fileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var simpleFilename = Path.GetFileNameWithoutExtension(fileName);
|
||||||
|
var match = SubtitleLanguageRegex.Match(simpleFilename);
|
||||||
|
var languageTags = match.Groups["tags"].Captures.Cast<Capture>()
|
||||||
|
.Where(tag => !tag.Value.Empty())
|
||||||
|
.Select(tag => tag.Value.ToLower());
|
||||||
|
return languageTags;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Debug(ex, "Failed parsing language tags from subtitle file: {0}", fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
private static Language RegexLanguage(string title)
|
private static Language RegexLanguage(string title)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Parser.Model
|
|||||||
public int IndexerId { get; set; }
|
public int IndexerId { get; set; }
|
||||||
public string Indexer { get; set; }
|
public string Indexer { get; set; }
|
||||||
public int IndexerPriority { get; set; }
|
public int IndexerPriority { get; set; }
|
||||||
|
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||||
public DownloadProtocol DownloadProtocol { get; set; }
|
public DownloadProtocol DownloadProtocol { get; set; }
|
||||||
public int TvdbId { get; set; }
|
public int TvdbId { get; set; }
|
||||||
public int TvRageId { get; set; }
|
public int TvRageId { get; set; }
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Parser
|
|||||||
|
|
||||||
private static readonly Regex SourceRegex = new Regex(@"\b(?:
|
private static readonly Regex SourceRegex = new Regex(@"\b(?:
|
||||||
(?<bluray>BluRay|Blu-Ray|HD-?DVD|BDMux|BD(?!$))|
|
(?<bluray>BluRay|Blu-Ray|HD-?DVD|BDMux|BD(?!$))|
|
||||||
(?<webdl>WEB[-_. ]DL|WEBDL|AmazonHD|iTunesHD|MaxdomeHD|NetflixU?HD|WebHD|[. ]WEB[. ](?:[xh][ .]?26[45]|DDP?5[. ]1)|[. ](?-i:WEB)$|\d+0p(?:[-. ]AMZN)?[-. ]WEB[-. ]|WEB-DLMux|\b\s\/\sWEB\s\/\s\b|(?:AMZN|NF|DP)[. ]WEB[. ])|
|
(?<webdl>WEB[-_. ]DL(?:mux)?|WEBDL|AmazonHD|iTunesHD|MaxdomeHD|NetflixU?HD|WebHD|[. ]WEB[. ](?:[xh][ .]?26[45]|DDP?5[. ]1)|[. ](?-i:WEB)$|(?:720|1080|2160)p[-. ]WEB[-. ]|[-. ]WEB[-. ](?:720|1080|2160)p|\b\s\/\sWEB\s\/\s\b|(?:AMZN|NF|DP)[. -]WEB[. -](?!Rip))|
|
||||||
(?<webrip>WebRip|Web-Rip|WEBMux)|
|
(?<webrip>WebRip|Web-Rip|WEBMux)|
|
||||||
(?<hdtv>HDTV)|
|
(?<hdtv>HDTV)|
|
||||||
(?<bdrip>BDRip|BDLight)|
|
(?<bdrip>BDRip|BDLight)|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Tv
|
|||||||
_diskProvider.CreateFolder(new DirectoryInfo(destinationPath).Parent.FullName);
|
_diskProvider.CreateFolder(new DirectoryInfo(destinationPath).Parent.FullName);
|
||||||
_diskTransferService.TransferFolder(sourcePath, destinationPath, TransferMode.Move);
|
_diskTransferService.TransferFolder(sourcePath, destinationPath, TransferMode.Move);
|
||||||
|
|
||||||
_logger.ProgressInfo("{0} moved successfully to {1}", series.Title, series.Path);
|
_logger.ProgressInfo("{0} moved successfully to {1}", series.Title, destinationPath);
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new SeriesMovedEvent(series, sourcePath, destinationPath));
|
_eventAggregator.PublishEvent(new SeriesMovedEvent(series, sourcePath, destinationPath));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Sonarr.Api.V3.Indexers
|
|||||||
public bool SupportsSearch { get; set; }
|
public bool SupportsSearch { get; set; }
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||||
public int DownloadClientId { get; set; }
|
public int DownloadClientId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ namespace Sonarr.Api.V3.Indexers
|
|||||||
resource.SupportsSearch = definition.SupportsSearch;
|
resource.SupportsSearch = definition.SupportsSearch;
|
||||||
resource.Protocol = definition.Protocol;
|
resource.Protocol = definition.Protocol;
|
||||||
resource.Priority = definition.Priority;
|
resource.Priority = definition.Priority;
|
||||||
|
resource.SeasonSearchMaximumSingleEpisodeAge = definition.SeasonSearchMaximumSingleEpisodeAge;
|
||||||
resource.DownloadClientId = definition.DownloadClientId;
|
resource.DownloadClientId = definition.DownloadClientId;
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
@@ -44,6 +46,7 @@ namespace Sonarr.Api.V3.Indexers
|
|||||||
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;
|
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;
|
||||||
definition.EnableInteractiveSearch = resource.EnableInteractiveSearch;
|
definition.EnableInteractiveSearch = resource.EnableInteractiveSearch;
|
||||||
definition.Priority = resource.Priority;
|
definition.Priority = resource.Priority;
|
||||||
|
definition.SeasonSearchMaximumSingleEpisodeAge = resource.SeasonSearchMaximumSingleEpisodeAge;
|
||||||
definition.DownloadClientId = resource.DownloadClientId;
|
definition.DownloadClientId = resource.DownloadClientId;
|
||||||
|
|
||||||
return definition;
|
return definition;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
# Increment packageVersion when package scripts change
|
# Increment packageVersion when package scripts change
|
||||||
packageVersion='3.0.9'
|
packageVersion='3.0.10'
|
||||||
|
|
||||||
# For now we keep the build version and package version the same
|
# For now we keep the build version and package version the same
|
||||||
buildVersion=$packageVersion
|
buildVersion=$packageVersion
|
||||||
|
|||||||
Reference in New Issue
Block a user