New: Series file naming tokens

Closes #25
Closes #26
pull/1350/head
ta264 3 years ago
parent 7c5188638f
commit a7c0eabb56

@ -79,6 +79,12 @@ const bookTokens = [
{ token: '{Book Disambiguation}', example: 'Disambiguation' },
{ token: '{Book Series}', example: 'Series Title' },
{ token: '{Book SeriesPosition}', example: '1' },
{ token: '{Book SeriesTitle}', example: 'Series Title #1' },
{ token: '{PartNumber:0}', example: '2' },
{ token: '{PartNumber:00}', example: '02' },
{ token: '{PartCount:0}', example: '9' },

@ -28,10 +28,23 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.With(s => s.Name = "Avenged Sevenfold")
.Build();
var series = Builder<Series>
.CreateNew()
.With(x => x.Title = "Series Title")
.Build();
var seriesLink = Builder<SeriesBookLink>
.CreateListOfSize(1)
.All()
.With(s => s.Position = "1-2")
.With(s => s.Series = series)
.BuildListOfNew();
_book = Builder<Book>
.CreateNew()
.With(s => s.Title = "Hail to the King")
.With(s => s.AuthorMetadata = _author.Metadata.Value)
.With(s => s.SeriesLinks = seriesLink)
.Build();
_edition = Builder<Edition>

@ -35,10 +35,23 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
})
.Build();
var series = Builder<Series>
.CreateNew()
.With(x => x.Title = "Series Title")
.Build();
var seriesLink = Builder<SeriesBookLink>
.CreateListOfSize(1)
.All()
.With(s => s.Position = "1-2")
.With(s => s.Series = series)
.BuildListOfNew();
_book = Builder<Book>
.CreateNew()
.With(s => s.Title = "Hybrid Theory")
.With(s => s.AuthorMetadata = _author.Metadata.Value)
.With(s => s.SeriesLinks = seriesLink)
.Build();
_edition = Builder<Edition>
@ -247,6 +260,33 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be("Hybrid.Theory.2000");
}
[Test]
public void should_set_series()
{
_namingConfig.StandardBookFormat = "{Book Series}";
Subject.BuildBookFileName(_author, _edition, _trackFile)
.Should().Be("Series Title");
}
[Test]
public void should_set_series_number()
{
_namingConfig.StandardBookFormat = "{Book SeriesPosition}";
Subject.BuildBookFileName(_author, _edition, _trackFile)
.Should().Be("1-2");
}
[Test]
public void should_set_series_title()
{
_namingConfig.StandardBookFormat = "{Book SeriesTitle}";
Subject.BuildBookFileName(_author, _edition, _trackFile)
.Should().Be("Series Title #1-2");
}
[Test]
public void should_set_part_number()
{
@ -434,7 +474,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
_namingConfig.StandardBookFormat = "{Author.Name}.{Book.Title}";
Subject.BuildBookFileName(new Author { Name = "In The Woods." }, new Edition { Title = "30 Rock", Book = new Book { AuthorMetadata = new AuthorMetadata { Name = "Author" } } }, _trackFile)
Subject.BuildBookFileName(new Author { Name = "In The Woods." }, new Edition { Title = "30 Rock", Book = new Book { AuthorMetadata = new AuthorMetadata { Name = "Author" }, SeriesLinks = new List<SeriesBookLink>() } }, _trackFile)
.Should().Be("In.The.Woods.30.Rock");
}
@ -443,7 +483,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
_namingConfig.StandardBookFormat = "{Author.Name}.{Book.Title}";
Subject.BuildBookFileName(new Author { Name = "In The Woods..." }, new Edition { Title = "30 Rock", Book = new Book { AuthorMetadata = new AuthorMetadata { Name = "Author" } } }, _trackFile)
Subject.BuildBookFileName(new Author { Name = "In The Woods..." }, new Edition { Title = "30 Rock", Book = new Book { AuthorMetadata = new AuthorMetadata { Name = "Author" }, SeriesLinks = new List<SeriesBookLink>() } }, _trackFile)
.Should().Be("In.The.Woods.30.Rock");
}

@ -28,10 +28,23 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.With(s => s.Name = "Alien Ant Farm")
.Build();
var series = Builder<Series>
.CreateNew()
.With(x => x.Title = "Series Title")
.Build();
var seriesLink = Builder<SeriesBookLink>
.CreateListOfSize(1)
.All()
.With(s => s.Position = "1-2")
.With(s => s.Series = series)
.BuildListOfNew();
_book = Builder<Book>
.CreateNew()
.With(s => s.Title = "Anthology")
.With(s => s.AuthorMetadata = _author.Metadata.Value)
.With(s => s.SeriesLinks = seriesLink)
.Build();
_edition = Builder<Edition>

@ -6,6 +6,7 @@ namespace NzbDrone.Core.Books
public class SeriesBookLink : Entity<SeriesBookLink>
{
public string Position { get; set; }
public int SeriesPosition { get; set; }
public int SeriesId { get; set; }
public int BookId { get; set; }
public bool IsPrimary { get; set; }
@ -18,6 +19,7 @@ namespace NzbDrone.Core.Books
public override void UseMetadataFrom(SeriesBookLink other)
{
Position = other.Position;
SeriesPosition = other.SeriesPosition;
IsPrimary = other.IsPrimary;
}

@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(18)]
public class AddSeriesPosition : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("SeriesBookLink").AddColumn("SeriesPosition").AsInt32().WithDefaultValue(0);
}
}
}

@ -196,7 +196,8 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
Book = bookDict[l.ForeignWorkId.ToString()],
Series = curr,
IsPrimary = l.Primary,
Position = l.PositionInSeries
Position = l.PositionInSeries,
SeriesPosition = l.SeriesPosition
}).ToList();
}
}

@ -245,6 +245,17 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{Book CleanSubtitle}"] = m => CleanTitle(subtitle);
tokenHandlers["{Book SubtitleThe}"] = m => TitleThe(subtitle);
var seriesLinks = edition.Book.Value.SeriesLinks.Value;
if (seriesLinks.Any())
{
var primarySeries = seriesLinks.OrderBy(x => x.SeriesPosition).First();
var seriesTitle = primarySeries.Series?.Value?.Title + (primarySeries.Position.IsNotNullOrWhiteSpace() ? $" #{primarySeries.Position}" : string.Empty);
tokenHandlers["{Book Series}"] = m => primarySeries.Series.Value.Title;
tokenHandlers["{Book SeriesPosition}"] = m => primarySeries.Position;
tokenHandlers["{Book SeriesTitle}"] = m => seriesTitle;
}
if (edition.Disambiguation != null)
{
tokenHandlers["{Book Disambiguation}"] = m => edition.Disambiguation;

@ -37,12 +37,24 @@ namespace NzbDrone.Core.Organizer
}
};
var series = new Series
{
Title = "Series Title"
};
var seriesLink = new SeriesBookLink
{
Position = "1",
Series = series
};
_standardBook = new Book
{
Title = "The Book Title",
ReleaseDate = System.DateTime.Today,
Author = _standardAuthor,
AuthorMetadata = _standardAuthor.Metadata.Value
AuthorMetadata = _standardAuthor.Metadata.Value,
SeriesLinks = new List<SeriesBookLink> { seriesLink }
};
_standardEdition = new Edition

@ -54,6 +54,9 @@ namespace Readarr.Api.V1.Books
var title = selectedEdition?.Title ?? model.Title;
var authorTitle = $"{model.Author?.Value?.Metadata?.Value?.SortNameLastFirst} {title}";
var seriesLinks = model.SeriesLinks?.Value?.OrderBy(x => x.SeriesPosition);
var seriesTitle = seriesLinks?.Select(x => x?.Series?.Value?.Title + (x?.Position.IsNotNullOrWhiteSpace() ?? false ? $" #{x.Position}" : string.Empty)).ConcatToString("; ");
return new BookResource
{
Id = model.Id,
@ -67,7 +70,7 @@ namespace Readarr.Api.V1.Books
Genres = model.Genres,
Title = title,
AuthorTitle = authorTitle,
SeriesTitle = model.SeriesLinks?.Value?.Select(x => x?.Series?.Value?.Title + (x?.Position.IsNotNullOrWhiteSpace() ?? false ? $" #{x.Position}" : string.Empty)).ConcatToString("; "),
SeriesTitle = seriesTitle,
Disambiguation = selectedEdition?.Disambiguation,
Overview = selectedEdition?.Overview,
Images = selectedEdition?.Images ?? new List<MediaCover>(),

@ -8,6 +8,7 @@ namespace Readarr.Api.V1.Series
public class SeriesBookLinkResource : RestResource
{
public string Position { get; set; }
public int SeriesPosition { get; set; }
public int SeriesId { get; set; }
public int BookId { get; set; }
}
@ -20,6 +21,7 @@ namespace Readarr.Api.V1.Series
{
Id = model.Id,
Position = model.Position,
SeriesPosition = model.SeriesPosition,
SeriesId = model.SeriesId,
BookId = model.BookId
};

Loading…
Cancel
Save