Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow artist grouping into folders (ex. ABC, DEF, etc.) #4419

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/src/Settings/MediaManagement/Naming/NamingModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const artistTokens = [

{ token: '{Artist NameFirstCharacter}', example: 'A' },

{ token: '{Artist NameFirstCharacterGroup}', example: 'ABC' },

{ token: '{Artist CleanName}', example: 'Artist Name' },

{ token: '{Artist Disambiguation}', example: 'Disambiguation' },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;

namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class ArtistNameFirstCharacterGroupFixture : CoreTest<FileNameBuilder>
{
private Artist _artist;
private NamingConfig _namingConfig;

[SetUp]
public void Setup()
{
_artist = Builder<Artist>
.CreateNew()
.Build();

_namingConfig = NamingConfig.Default;
_namingConfig.RenameTracks = true;

Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);

Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
}

[TestCase("The Mist", "MNO", "The Mist")]
[TestCase("A", "ABC", "A")]
[TestCase("30 Rock", "0-9", "30 Rock")]
[TestCase("The '80s Greatest", "0-9", "The '80s Greatest")]
[TestCase("좀비버스", "좀", "좀비버스")]
[TestCase("¡Mucha Lucha!", "MNO", "¡Mucha Lucha!")]
[TestCase(".hack", "GHI", "hack")]
[TestCase("Ütopya", "STU", "Ütopya")]
[TestCase("Æon Flux", "ABC", "Æon Flux")]
[TestCase("Yabbadabbadoo", "YZ", "Yabbadabbadoo")]
public void should_get_expected_folder_name_back(string title, string parent, string child)
{
_artist.Name = title;
_namingConfig.ArtistFolderFormat = "{Artist NameFirstCharacterGroup}\\{Artist Name}";

Subject.GetArtistFolder(_artist).Should().Be(Path.Combine(parent, child));
}

[Test]
public void should_be_able_to_use_lower_case_first_character()
{
_artist.Name = "Westworld";
_namingConfig.ArtistFolderFormat = "{Artist NameFirstCharacterGroup}\\{artist name}";

Subject.GetArtistFolder(_artist).Should().Be(Path.Combine("VWX", "westworld"));
}
}
}
26 changes: 26 additions & 0 deletions src/NzbDrone.Core/Organizer/FileNameBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ public class FileNameBuilder : IBuildFileNames

private static readonly Regex ReservedDeviceNamesRegex = new Regex(@"^(?:aux|com[1-9]|con|lpt[1-9]|nul|prn)\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);

private static readonly List<(string Range, string Group)> ArtistNameGroups = new List<(string Range, string Group)>
{
("0-9", "0-9"), ("A-C", "ABC"), ("D-F", "DEF"), ("G-I", "GHI"),
("J-L", "JKL"), ("M-O", "MNO"), ("P-R", "PQR"), ("S-U", "STU"),
("V-X", "VWX"), ("Y-Z", "YZ")
};

public FileNameBuilder(INamingConfigService namingConfigService,
IQualityDefinitionService qualityDefinitionService,
ICacheManager cacheManager,
Expand Down Expand Up @@ -282,6 +289,24 @@ public static string TitleFirstCharacter(string title)
return "_";
}

public static string TitleFirstCharacterGroup(string title)
{
var firstChar = TitleFirstCharacter(title);
if (firstChar == "_")
{
return firstChar;
}

var group = ArtistNameGroups.FirstOrDefault(g => firstChar[0] >= g.Range[0] && firstChar[0] <= g.Range[2]);
if (group == default)
{
// If it turns out not to be a Latin character, fall back to the single character
return firstChar;
}

return group.Group;
}

public static string CleanFileName(string name)
{
return CleanFileName(name, NamingConfig.Default);
Expand All @@ -301,6 +326,7 @@ private void AddArtistTokens(Dictionary<string, Func<TokenMatch, string>> tokenH
tokenHandlers["{Artist NameThe}"] = m => TitleThe(artist.Name);
tokenHandlers["{Artist Genre}"] = m => artist.Metadata.Value.Genres?.FirstOrDefault() ?? string.Empty;
tokenHandlers["{Artist NameFirstCharacter}"] = m => TitleFirstCharacter(TitleThe(artist.Name));
tokenHandlers["{Artist NameFirstCharacterGroup"] = m => TitleFirstCharacterGroup(TitleThe(artist.Name));
tokenHandlers["{Artist MbId}"] = m => artist.ForeignArtistId ?? string.Empty;

if (artist.Metadata.Value.Disambiguation != null)
Expand Down
Loading