New: Release Type (Single/Multi episode and Season Pack) for Custom Formats

Closes #3562
This commit is contained in:
Mark McDowall 2024-02-28 21:38:22 -08:00 committed by Mark McDowall
parent c99d81e79b
commit f8a0751775
23 changed files with 408 additions and 38 deletions

View File

@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class release_typeFixture : MigrationTest<release_type>
{
[Test]
public void should_convert_single_episode_without_folder()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("EpisodeFiles").Row(new
{
SeriesId = 1,
SeasonNumber = 1,
RelativePath = "Season 01/S01E05.mkv",
Size = 125.Megabytes(),
DateAdded = DateTime.UtcNow.AddDays(-5),
OriginalFilePath = "Series.Title.S01E05.720p.HDTV.x265-Sonarr.mkv",
ReleaseGroup = "Sonarr",
Quality = new QualityModel(Quality.HDTV720p).ToJson(),
Languages = "[1]"
});
});
var items = db.Query<EpisodeFile203>("SELECT * FROM \"EpisodeFiles\"");
items.Should().HaveCount(1);
items.First().ReleaseType.Should().Be((int)ReleaseType.SingleEpisode);
}
[Test]
public void should_convert_single_episode_with_folder()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("EpisodeFiles").Row(new
{
SeriesId = 1,
SeasonNumber = 1,
RelativePath = "Season 01/S01E05.mkv",
Size = 125.Megabytes(),
DateAdded = DateTime.UtcNow.AddDays(-5),
OriginalFilePath = "Series.Title.S01E05.720p.HDTV.x265-Sonarr/S01E05.mkv",
ReleaseGroup = "Sonarr",
Quality = new QualityModel(Quality.HDTV720p).ToJson(),
Languages = "[1]"
});
});
var items = db.Query<EpisodeFile203>("SELECT * FROM \"EpisodeFiles\"");
items.Should().HaveCount(1);
items.First().ReleaseType.Should().Be((int)ReleaseType.SingleEpisode);
}
[Test]
public void should_convert_multi_episode_without_folder()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("EpisodeFiles").Row(new
{
SeriesId = 1,
SeasonNumber = 1,
RelativePath = "Season 01/S01E05.mkv",
Size = 125.Megabytes(),
DateAdded = DateTime.UtcNow.AddDays(-5),
OriginalFilePath = "Series.Title.S01E05E06.720p.HDTV.x265-Sonarr.mkv",
ReleaseGroup = "Sonarr",
Quality = new QualityModel(Quality.HDTV720p).ToJson(),
Languages = "[1]"
});
});
var items = db.Query<EpisodeFile203>("SELECT * FROM \"EpisodeFiles\"");
items.Should().HaveCount(1);
items.First().ReleaseType.Should().Be((int)ReleaseType.MultiEpisode);
}
[Test]
public void should_convert_multi_episode_with_folder()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("EpisodeFiles").Row(new
{
SeriesId = 1,
SeasonNumber = 1,
RelativePath = "Season 01/S01E05.mkv",
Size = 125.Megabytes(),
DateAdded = DateTime.UtcNow.AddDays(-5),
OriginalFilePath = "Series.Title.S01E05E06.720p.HDTV.x265-Sonarr/S01E05E06.mkv",
ReleaseGroup = "Sonarr",
Quality = new QualityModel(Quality.HDTV720p).ToJson(),
Languages = "[1]"
});
});
var items = db.Query<EpisodeFile203>("SELECT * FROM \"EpisodeFiles\"");
items.Should().HaveCount(1);
items.First().ReleaseType.Should().Be((int)ReleaseType.MultiEpisode);
}
[Test]
public void should_convert_season_pack_with_folder()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("EpisodeFiles").Row(new
{
SeriesId = 1,
SeasonNumber = 1,
RelativePath = "Season 01/S01E05.mkv",
Size = 125.Megabytes(),
DateAdded = DateTime.UtcNow.AddDays(-5),
OriginalFilePath = "Series.Title.S01.720p.HDTV.x265-Sonarr/S01E05.mkv",
ReleaseGroup = "Sonarr",
Quality = new QualityModel(Quality.HDTV720p).ToJson(),
Languages = "[1]"
});
});
var items = db.Query<EpisodeFile203>("SELECT * FROM \"EpisodeFiles\"");
items.Should().HaveCount(1);
items.First().ReleaseType.Should().Be((int)ReleaseType.SeasonPack);
}
[Test]
public void should_not_convert_episode_without_original_file_path()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("EpisodeFiles").Row(new
{
SeriesId = 1,
SeasonNumber = 1,
RelativePath = "Season 01/S01E05.mkv",
Size = 125.Megabytes(),
DateAdded = DateTime.UtcNow.AddDays(-5),
ReleaseGroup = "Sonarr",
Quality = new QualityModel(Quality.HDTV720p).ToJson(),
Languages = "[1]"
});
});
var items = db.Query<EpisodeFile203>("SELECT * FROM \"EpisodeFiles\"");
items.Should().HaveCount(1);
items.First().ReleaseType.Should().Be((int)ReleaseType.Unknown);
}
public class EpisodeFile203
{
public int Id { get; set; }
public int SeriesId { get; set; }
public int SeasonNumber { get; set; }
public string RelativePath { get; set; }
public long Size { get; set; }
public DateTime DateAdded { get; set; }
public string OriginalFilePath { get; set; }
public string SceneName { get; set; }
public string ReleaseGroup { get; set; }
public QualityModel Quality { get; set; }
public long IndexerFlags { get; set; }
public MediaInfoModel MediaInfo { get; set; }
public List<int> Languages { get; set; }
public long ReleaseType { get; set; }
}
}
}

View File

@ -49,6 +49,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!"))); _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!"))); _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!"))); _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
_rejectedDecisions.ForEach(r => r.LocalEpisode.FileEpisodeInfo = new ParsedEpisodeInfo());
foreach (var episode in episodes) foreach (var episode in episodes)
{ {
@ -59,7 +60,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Episodes = new List<Episode> { episode }, Episodes = new List<Episode> { episode },
Path = Path.Combine(series.Path, "30 Rock - S01E01 - Pilot.avi"), Path = Path.Combine(series.Path, "30 Rock - S01E01 - Pilot.avi"),
Quality = new QualityModel(Quality.Bluray720p), Quality = new QualityModel(Quality.Bluray720p),
ReleaseGroup = "DRONE" ReleaseGroup = "DRONE",
FileEpisodeInfo = new ParsedEpisodeInfo()
})); }));
} }

View File

@ -22,6 +22,7 @@ namespace NzbDrone.Core.Blocklisting
public DownloadProtocol Protocol { get; set; } public DownloadProtocol Protocol { get; set; }
public string Indexer { get; set; } public string Indexer { get; set; }
public IndexerFlags IndexerFlags { get; set; } public IndexerFlags IndexerFlags { get; set; }
public ReleaseType ReleaseType { get; set; }
public string Message { get; set; } public string Message { get; set; }
public string TorrentInfoHash { get; set; } public string TorrentInfoHash { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }

View File

@ -194,6 +194,11 @@ namespace NzbDrone.Core.Blocklisting
blocklist.IndexerFlags = flags; blocklist.IndexerFlags = flags;
} }
if (Enum.TryParse(message.Data.GetValueOrDefault("releaseType"), true, out ReleaseType releaseType))
{
blocklist.ReleaseType = releaseType;
}
_blocklistRepository.Insert(blocklist); _blocklistRepository.Insert(blocklist);
} }

View File

@ -41,7 +41,8 @@ namespace NzbDrone.Core.CustomFormats
Series = remoteEpisode.Series, Series = remoteEpisode.Series,
Size = size, Size = size,
Languages = remoteEpisode.Languages, Languages = remoteEpisode.Languages,
IndexerFlags = remoteEpisode.Release?.IndexerFlags ?? 0 IndexerFlags = remoteEpisode.Release?.IndexerFlags ?? 0,
ReleaseType = remoteEpisode.ParsedEpisodeInfo.ReleaseType
}; };
return ParseCustomFormat(input); return ParseCustomFormat(input);
@ -76,7 +77,8 @@ namespace NzbDrone.Core.CustomFormats
Series = series, Series = series,
Size = blocklist.Size ?? 0, Size = blocklist.Size ?? 0,
Languages = blocklist.Languages, Languages = blocklist.Languages,
IndexerFlags = blocklist.IndexerFlags IndexerFlags = blocklist.IndexerFlags,
ReleaseType = blocklist.ReleaseType
}; };
return ParseCustomFormat(input); return ParseCustomFormat(input);
@ -88,6 +90,7 @@ namespace NzbDrone.Core.CustomFormats
long.TryParse(history.Data.GetValueOrDefault("size"), out var size); long.TryParse(history.Data.GetValueOrDefault("size"), out var size);
Enum.TryParse(history.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags indexerFlags); Enum.TryParse(history.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags indexerFlags);
Enum.TryParse(history.Data.GetValueOrDefault("releaseType"), out ReleaseType releaseType);
var episodeInfo = new ParsedEpisodeInfo var episodeInfo = new ParsedEpisodeInfo
{ {
@ -104,7 +107,8 @@ namespace NzbDrone.Core.CustomFormats
Series = series, Series = series,
Size = size, Size = size,
Languages = history.Languages, Languages = history.Languages,
IndexerFlags = indexerFlags IndexerFlags = indexerFlags,
ReleaseType = releaseType
}; };
return ParseCustomFormat(input); return ParseCustomFormat(input);
@ -128,6 +132,7 @@ namespace NzbDrone.Core.CustomFormats
Size = localEpisode.Size, Size = localEpisode.Size,
Languages = localEpisode.Languages, Languages = localEpisode.Languages,
IndexerFlags = localEpisode.IndexerFlags, IndexerFlags = localEpisode.IndexerFlags,
ReleaseType = localEpisode.ReleaseType,
Filename = Path.GetFileName(localEpisode.Path) Filename = Path.GetFileName(localEpisode.Path)
}; };
@ -188,7 +193,7 @@ namespace NzbDrone.Core.CustomFormats
ReleaseTitle = releaseTitle, ReleaseTitle = releaseTitle,
Quality = episodeFile.Quality, Quality = episodeFile.Quality,
Languages = episodeFile.Languages, Languages = episodeFile.Languages,
ReleaseGroup = episodeFile.ReleaseGroup ReleaseGroup = episodeFile.ReleaseGroup,
}; };
var input = new CustomFormatInput var input = new CustomFormatInput
@ -198,7 +203,8 @@ namespace NzbDrone.Core.CustomFormats
Size = episodeFile.Size, Size = episodeFile.Size,
Languages = episodeFile.Languages, Languages = episodeFile.Languages,
IndexerFlags = episodeFile.IndexerFlags, IndexerFlags = episodeFile.IndexerFlags,
Filename = Path.GetFileName(episodeFile.RelativePath) ReleaseType = episodeFile.ReleaseType,
Filename = Path.GetFileName(episodeFile.RelativePath),
}; };
return ParseCustomFormat(input, allCustomFormats); return ParseCustomFormat(input, allCustomFormats);

View File

@ -13,6 +13,7 @@ namespace NzbDrone.Core.CustomFormats
public IndexerFlags IndexerFlags { get; set; } public IndexerFlags IndexerFlags { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public string Filename { get; set; } public string Filename { get; set; }
public ReleaseType ReleaseType { get; set; }
public CustomFormatInput() public CustomFormatInput()
{ {

View File

@ -11,11 +11,11 @@ namespace NzbDrone.Core.CustomFormats
public IndexerFlagSpecificationValidator() public IndexerFlagSpecificationValidator()
{ {
RuleFor(c => c.Value).NotEmpty(); RuleFor(c => c.Value).NotEmpty();
RuleFor(c => c.Value).Custom((qualityValue, context) => RuleFor(c => c.Value).Custom((flag, context) =>
{ {
if (!Enum.IsDefined(typeof(IndexerFlags), qualityValue)) if (!Enum.IsDefined(typeof(IndexerFlags), flag))
{ {
context.AddFailure($"Invalid indexer flag condition value: {qualityValue}"); context.AddFailure($"Invalid indexer flag condition value: {flag}");
} }
}); });
} }

View File

@ -0,0 +1,43 @@
using System;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class SeasonPackSpecificationValidator : AbstractValidator<SeasonPackSpecification>
{
public SeasonPackSpecificationValidator()
{
RuleFor(c => c.Value).Custom((releaseType, context) =>
{
if (!Enum.IsDefined(typeof(ReleaseType), releaseType))
{
context.AddFailure($"Invalid release type condition value: {releaseType}");
}
});
}
}
public class SeasonPackSpecification : CustomFormatSpecificationBase
{
private static readonly SeasonPackSpecificationValidator Validator = new ();
public override int Order => 10;
public override string ImplementationName => "Release Type";
[FieldDefinition(1, Label = "ReleaseType", Type = FieldType.Select, SelectOptions = typeof(ReleaseType))]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
return input.ReleaseType == (ReleaseType)Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Data;
using System.IO;
using Dapper;
using FluentMigrator;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(203)]
public class release_type : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Blocklist").AddColumn("ReleaseType").AsInt32().WithDefaultValue(0);
Alter.Table("EpisodeFiles").AddColumn("ReleaseType").AsInt32().WithDefaultValue(0);
Execute.WithConnection(UpdateEpisodeFiles);
}
private void UpdateEpisodeFiles(IDbConnection conn, IDbTransaction tran)
{
var updates = new List<object>();
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT \"Id\", \"OriginalFilePath\" FROM \"EpisodeFiles\" WHERE \"OriginalFilePath\" IS NOT NULL";
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var id = reader.GetInt32(0);
var originalFilePath = reader.GetString(1);
var folderName = Path.GetDirectoryName(originalFilePath);
var fileName = Path.GetFileNameWithoutExtension(originalFilePath);
var title = folderName.IsNullOrWhiteSpace() ? fileName : folderName;
var parsedEpisodeInfo = Parser.Parser.ParseTitle(title);
if (parsedEpisodeInfo != null && parsedEpisodeInfo.ReleaseType != ReleaseType.Unknown)
{
updates.Add(new
{
Id = id,
ReleaseType = (int)parsedEpisodeInfo.ReleaseType
});
}
}
}
var updateEpisodeFilesSql = "UPDATE \"EpisodeFiles\" SET \"ReleaseType\" = @ReleaseType WHERE \"Id\" = @Id";
conn.Execute(updateEpisodeFilesSql, updates, transaction: tran);
}
}
}

View File

@ -170,6 +170,7 @@ namespace NzbDrone.Core.History
history.Data.Add("SeriesMatchType", message.Episode.SeriesMatchType.ToString()); history.Data.Add("SeriesMatchType", message.Episode.SeriesMatchType.ToString());
history.Data.Add("ReleaseSource", message.Episode.ReleaseSource.ToString()); history.Data.Add("ReleaseSource", message.Episode.ReleaseSource.ToString());
history.Data.Add("IndexerFlags", message.Episode.Release.IndexerFlags.ToString()); history.Data.Add("IndexerFlags", message.Episode.Release.IndexerFlags.ToString());
history.Data.Add("ReleaseType", message.Episode.ParsedEpisodeInfo.ReleaseType.ToString());
if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace()) if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace())
{ {
@ -222,6 +223,7 @@ namespace NzbDrone.Core.History
history.Data.Add("CustomFormatScore", message.EpisodeInfo.CustomFormatScore.ToString()); history.Data.Add("CustomFormatScore", message.EpisodeInfo.CustomFormatScore.ToString());
history.Data.Add("Size", message.EpisodeInfo.Size.ToString()); history.Data.Add("Size", message.EpisodeInfo.Size.ToString());
history.Data.Add("IndexerFlags", message.ImportedEpisode.IndexerFlags.ToString()); history.Data.Add("IndexerFlags", message.ImportedEpisode.IndexerFlags.ToString());
history.Data.Add("ReleaseType", message.ImportedEpisode.ReleaseType.ToString());
_historyRepository.Insert(history); _historyRepository.Insert(history);
} }
@ -283,6 +285,7 @@ namespace NzbDrone.Core.History
history.Data.Add("ReleaseGroup", message.EpisodeFile.ReleaseGroup); history.Data.Add("ReleaseGroup", message.EpisodeFile.ReleaseGroup);
history.Data.Add("Size", message.EpisodeFile.Size.ToString()); history.Data.Add("Size", message.EpisodeFile.Size.ToString());
history.Data.Add("IndexerFlags", message.EpisodeFile.IndexerFlags.ToString()); history.Data.Add("IndexerFlags", message.EpisodeFile.IndexerFlags.ToString());
history.Data.Add("ReleaseType", message.EpisodeFile.ReleaseType.ToString());
_historyRepository.Insert(history); _historyRepository.Insert(history);
} }
@ -315,6 +318,7 @@ namespace NzbDrone.Core.History
history.Data.Add("ReleaseGroup", message.EpisodeFile.ReleaseGroup); history.Data.Add("ReleaseGroup", message.EpisodeFile.ReleaseGroup);
history.Data.Add("Size", message.EpisodeFile.Size.ToString()); history.Data.Add("Size", message.EpisodeFile.Size.ToString());
history.Data.Add("IndexerFlags", message.EpisodeFile.IndexerFlags.ToString()); history.Data.Add("IndexerFlags", message.EpisodeFile.IndexerFlags.ToString());
history.Data.Add("ReleaseType", message.EpisodeFile.ReleaseType.ToString());
_historyRepository.Insert(history); _historyRepository.Insert(history);
} }
@ -343,6 +347,7 @@ namespace NzbDrone.Core.History
history.Data.Add("Message", message.Message); history.Data.Add("Message", message.Message);
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteEpisode?.ParsedEpisodeInfo?.ReleaseGroup); history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteEpisode?.ParsedEpisodeInfo?.ReleaseGroup);
history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString()); history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString());
history.Data.Add("ReleaseType", message.TrackedDownload?.RemoteEpisode?.ParsedEpisodeInfo?.ReleaseType.ToString());
historyToAdd.Add(history); historyToAdd.Add(history);
} }

View File

@ -1597,6 +1597,7 @@
"ReleaseSceneIndicatorUnknownMessage": "Numbering varies for this episode and release does not match any known mappings.", "ReleaseSceneIndicatorUnknownMessage": "Numbering varies for this episode and release does not match any known mappings.",
"ReleaseSceneIndicatorUnknownSeries": "Unknown episode or series.", "ReleaseSceneIndicatorUnknownSeries": "Unknown episode or series.",
"ReleaseTitle": "Release Title", "ReleaseTitle": "Release Title",
"ReleaseType": "Release Type",
"Reload": "Reload", "Reload": "Reload",
"RemotePath": "Remote Path", "RemotePath": "Remote Path",
"RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", "RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.",

View File

@ -27,6 +27,7 @@ namespace NzbDrone.Core.MediaFiles
public LazyLoaded<List<Episode>> Episodes { get; set; } public LazyLoaded<List<Episode>> Episodes { get; set; }
public LazyLoaded<Series> Series { get; set; } public LazyLoaded<Series> Series { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public ReleaseType ReleaseType { get; set; }
public override string ToString() public override string ToString()
{ {

View File

@ -97,6 +97,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
episodeFile.ReleaseGroup = localEpisode.ReleaseGroup; episodeFile.ReleaseGroup = localEpisode.ReleaseGroup;
episodeFile.Languages = localEpisode.Languages; episodeFile.Languages = localEpisode.Languages;
// Prefer the release type from the download client, folder and finally the file so we have the most accurate information.
episodeFile.ReleaseType = localEpisode.DownloadClientEpisodeInfo?.ReleaseType ??
localEpisode.FolderEpisodeInfo?.ReleaseType ??
localEpisode.FileEpisodeInfo.ReleaseType;
if (downloadClientItem?.DownloadId.IsNotNullOrWhiteSpace() == true) if (downloadClientItem?.DownloadId.IsNotNullOrWhiteSpace() == true)
{ {
var grabHistory = _historyService.FindByDownloadId(downloadClientItem.DownloadId) var grabHistory = _historyService.FindByDownloadId(downloadClientItem.DownloadId)
@ -107,12 +112,27 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
{ {
episodeFile.IndexerFlags = flags; episodeFile.IndexerFlags = flags;
} }
// Prefer the release type from the grabbed history
if (Enum.TryParse(grabHistory?.Data.GetValueOrDefault("releaseType"), true, out ReleaseType releaseType))
{
episodeFile.ReleaseType = releaseType;
}
} }
else else
{ {
episodeFile.IndexerFlags = localEpisode.IndexerFlags; episodeFile.IndexerFlags = localEpisode.IndexerFlags;
} }
// Fall back to parsed information if history is unavailable or missing
if (episodeFile.ReleaseType == ReleaseType.Unknown)
{
// Prefer the release type from the download client, folder and finally the file so we have the most accurate information.
episodeFile.ReleaseType = localEpisode.DownloadClientEpisodeInfo?.ReleaseType ??
localEpisode.FolderEpisodeInfo?.ReleaseType ??
localEpisode.FileEpisodeInfo.ReleaseType;
}
bool copyOnly; bool copyOnly;
switch (importMode) switch (importMode)
{ {

View File

@ -119,6 +119,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
localEpisode.FileEpisodeInfo = fileEpisodeInfo; localEpisode.FileEpisodeInfo = fileEpisodeInfo;
localEpisode.Size = _diskProvider.GetFileSize(localEpisode.Path); localEpisode.Size = _diskProvider.GetFileSize(localEpisode.Path);
localEpisode.ReleaseType = localEpisode.DownloadClientEpisodeInfo?.ReleaseType ??
localEpisode.FolderEpisodeInfo?.ReleaseType ??
localEpisode.FileEpisodeInfo?.ReleaseType ??
ReleaseType.Unknown;
try try
{ {

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
@ -17,6 +18,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public int IndexerFlags { get; set; } public int IndexerFlags { get; set; }
public ReleaseType ReleaseType { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public bool Equals(ManualImportFile other) public bool Equals(ManualImportFile other)

View File

@ -2,6 +2,7 @@ using System.Collections.Generic;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -25,6 +26,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public List<CustomFormat> CustomFormats { get; set; } public List<CustomFormat> CustomFormats { get; set; }
public int CustomFormatScore { get; set; } public int CustomFormatScore { get; set; }
public int IndexerFlags { get; set; } public int IndexerFlags { get; set; }
public ReleaseType ReleaseType { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
public ManualImportItem() public ManualImportItem()

View File

@ -425,6 +425,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path); item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
item.Rejections = decision.Rejections; item.Rejections = decision.Rejections;
item.IndexerFlags = (int)decision.LocalEpisode.IndexerFlags; item.IndexerFlags = (int)decision.LocalEpisode.IndexerFlags;
item.ReleaseType = decision.LocalEpisode.ReleaseType;
return item; return item;
} }
@ -444,6 +445,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
item.Quality = episodeFile.Quality; item.Quality = episodeFile.Quality;
item.Languages = episodeFile.Languages; item.Languages = episodeFile.Languages;
item.IndexerFlags = (int)episodeFile.IndexerFlags; item.IndexerFlags = (int)episodeFile.IndexerFlags;
item.ReleaseType = episodeFile.ReleaseType;
item.Size = _diskProvider.GetFileSize(item.Path); item.Size = _diskProvider.GetFileSize(item.Path);
item.Rejections = Enumerable.Empty<Rejection>(); item.Rejections = Enumerable.Empty<Rejection>();
item.EpisodeFileId = episodeFile.Id; item.EpisodeFileId = episodeFile.Id;
@ -481,6 +483,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
Quality = file.Quality, Quality = file.Quality,
Languages = file.Languages, Languages = file.Languages,
IndexerFlags = (IndexerFlags)file.IndexerFlags, IndexerFlags = (IndexerFlags)file.IndexerFlags,
ReleaseType = file.ReleaseType,
Series = series, Series = series,
Size = 0 Size = 0
}; };
@ -510,6 +513,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
localEpisode.Quality = file.Quality; localEpisode.Quality = file.Quality;
localEpisode.Languages = file.Languages; localEpisode.Languages = file.Languages;
localEpisode.IndexerFlags = (IndexerFlags)file.IndexerFlags; localEpisode.IndexerFlags = (IndexerFlags)file.IndexerFlags;
localEpisode.ReleaseType = file.ReleaseType;
// TODO: Cleanup non-tracked downloads // TODO: Cleanup non-tracked downloads

View File

@ -32,6 +32,7 @@ namespace NzbDrone.Core.Parser.Model
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public IndexerFlags IndexerFlags { get; set; } public IndexerFlags IndexerFlags { get; set; }
public ReleaseType ReleaseType { get; set; }
public MediaInfoModel MediaInfo { get; set; } public MediaInfoModel MediaInfo { get; set; }
public bool ExistingFile { get; set; } public bool ExistingFile { get; set; }
public bool SceneSource { get; set; } public bool SceneSource { get; set; }

View File

@ -90,6 +90,29 @@ namespace NzbDrone.Core.Parser.Model
} }
} }
public ReleaseType ReleaseType
{
get
{
if (EpisodeNumbers.Length > 1 || AbsoluteEpisodeNumbers.Length > 1)
{
return Model.ReleaseType.MultiEpisode;
}
if (EpisodeNumbers.Length == 1 || AbsoluteEpisodeNumbers.Length == 1)
{
return Model.ReleaseType.SingleEpisode;
}
if (FullSeason)
{
return Model.ReleaseType.SeasonPack;
}
return Model.ReleaseType.Unknown;
}
}
public override string ToString() public override string ToString()
{ {
var episodeString = "[Unknown Episode]"; var episodeString = "[Unknown Episode]";

View File

@ -0,0 +1,18 @@
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Parser.Model
{
public enum ReleaseType
{
Unknown = 0,
[FieldOption(label: "Single Episode")]
SingleEpisode = 1,
[FieldOption(label: "Multi-Episode")]
MultiEpisode = 2,
[FieldOption(label: "Season Pack")]
SeasonPack = 3
}
}

View File

@ -209,6 +209,11 @@ namespace Sonarr.Api.V3.EpisodeFiles
{ {
episodeFile.IndexerFlags = (IndexerFlags)resourceEpisodeFile.IndexerFlags; episodeFile.IndexerFlags = (IndexerFlags)resourceEpisodeFile.IndexerFlags;
} }
if (resourceEpisodeFile.ReleaseType != null)
{
episodeFile.ReleaseType = (ReleaseType)resourceEpisodeFile.ReleaseType;
}
} }
_mediaFileService.Update(episodeFiles); _mediaFileService.Update(episodeFiles);

View File

@ -26,6 +26,7 @@ namespace Sonarr.Api.V3.EpisodeFiles
public List<CustomFormatResource> CustomFormats { get; set; } public List<CustomFormatResource> CustomFormats { get; set; }
public int CustomFormatScore { get; set; } public int CustomFormatScore { get; set; }
public int? IndexerFlags { get; set; } public int? IndexerFlags { get; set; }
public int? ReleaseType { get; set; }
public MediaInfoResource MediaInfo { get; set; } public MediaInfoResource MediaInfo { get; set; }
public bool QualityCutoffNotMet { get; set; } public bool QualityCutoffNotMet { get; set; }
@ -33,34 +34,6 @@ namespace Sonarr.Api.V3.EpisodeFiles
public static class EpisodeFileResourceMapper public static class EpisodeFileResourceMapper
{ {
private static EpisodeFileResource ToResource(this EpisodeFile model)
{
if (model == null)
{
return null;
}
return new EpisodeFileResource
{
Id = model.Id,
SeriesId = model.SeriesId,
SeasonNumber = model.SeasonNumber,
RelativePath = model.RelativePath,
// Path
Size = model.Size,
DateAdded = model.DateAdded,
SceneName = model.SceneName,
ReleaseGroup = model.ReleaseGroup,
Languages = model.Languages,
Quality = model.Quality,
MediaInfo = model.MediaInfo.ToResource(model.SceneName)
// QualityCutoffNotMet
};
}
public static EpisodeFileResource ToResource(this EpisodeFile model, NzbDrone.Core.Tv.Series series, IUpgradableSpecification upgradableSpecification, ICustomFormatCalculationService formatCalculationService) public static EpisodeFileResource ToResource(this EpisodeFile model, NzbDrone.Core.Tv.Series series, IUpgradableSpecification upgradableSpecification, ICustomFormatCalculationService formatCalculationService)
{ {
if (model == null) if (model == null)
@ -90,7 +63,8 @@ namespace Sonarr.Api.V3.EpisodeFiles
QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(series.QualityProfile.Value, model.Quality), QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(series.QualityProfile.Value, model.Quality),
CustomFormats = customFormats.ToResource(false), CustomFormats = customFormats.ToResource(false),
CustomFormatScore = customFormatScore, CustomFormatScore = customFormatScore,
IndexerFlags = (int)model.IndexerFlags IndexerFlags = (int)model.IndexerFlags,
ReleaseType = (int)model.ReleaseType,
}; };
} }
} }

View File

@ -4,6 +4,7 @@ using NzbDrone.Common.Crypto;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual; using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using Sonarr.Api.V3.CustomFormats; using Sonarr.Api.V3.CustomFormats;
using Sonarr.Api.V3.Episodes; using Sonarr.Api.V3.Episodes;
@ -31,6 +32,7 @@ namespace Sonarr.Api.V3.ManualImport
public List<CustomFormatResource> CustomFormats { get; set; } public List<CustomFormatResource> CustomFormats { get; set; }
public int CustomFormatScore { get; set; } public int CustomFormatScore { get; set; }
public int IndexerFlags { get; set; } public int IndexerFlags { get; set; }
public ReleaseType ReleaseType { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
} }
@ -67,6 +69,7 @@ namespace Sonarr.Api.V3.ManualImport
// QualityWeight // QualityWeight
DownloadId = model.DownloadId, DownloadId = model.DownloadId,
IndexerFlags = model.IndexerFlags, IndexerFlags = model.IndexerFlags,
ReleaseType = model.ReleaseType,
Rejections = model.Rejections Rejections = model.Rejections
}; };
} }