Skip to content

Commit

Permalink
Import album extras for manual imports and downloads
Browse files Browse the repository at this point in the history
Co-authored-by: TTY Teapot <ttdev@protonmail.com>
  • Loading branch information
mynameisbogdan and tty418 committed Mar 4, 2024
1 parent c974344 commit 89d5037
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/NzbDrone.Core/Configuration/ConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,14 +208,14 @@ public bool CopyUsingHardlinks

public bool ImportExtraFiles
{
get { return GetValueBoolean("ImportExtraFiles", false); }
get { return GetValueBoolean("ImportExtraFiles", true); }

set { SetValue("ImportExtraFiles", value); }
}

public string ExtraFileExtensions
{
get { return GetValue("ExtraFileExtensions", "srt"); }
get { return GetValue("ExtraFileExtensions", "log, cue, nfo, jpg, jpeg, png"); }

set { SetValue("ExtraFileExtensions", value); }
}
Expand Down
166 changes: 162 additions & 4 deletions src/NzbDrone.Core/Extras/ExtraService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.MediaFiles.TrackImport;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music;
using NzbDrone.Core.Parser.Model;
Expand All @@ -18,6 +21,7 @@ namespace NzbDrone.Core.Extras
public interface IExtraService
{
void ImportTrack(LocalTrack localTrack, TrackFile trackFile, bool isReadOnly);
void ImportAlbumExtras(List<ImportDecision<LocalTrack>> importedTracks);
}

public class ExtraService : IExtraService,
Expand All @@ -32,6 +36,7 @@ public class ExtraService : IExtraService,
private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService;
private readonly List<IManageExtraFiles> _extraFileManagers;
private readonly AlbumExtraFileManager _albumExtraManager;
private readonly Logger _logger;

public ExtraService(IMediaFileService mediaFileService,
Expand All @@ -40,6 +45,7 @@ public class ExtraService : IExtraService,
IDiskProvider diskProvider,
IConfigService configService,
IEnumerable<IManageExtraFiles> extraFileManagers,
AlbumExtraFileManager albumExtraManager,
Logger logger)
{
_mediaFileService = mediaFileService;
Expand All @@ -48,9 +54,104 @@ public class ExtraService : IExtraService,
_diskProvider = diskProvider;
_configService = configService;
_extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList();
_albumExtraManager = albumExtraManager;
_logger = logger;
}

public void ImportAlbumExtras(List<ImportDecision<LocalTrack>> importedTracks)
{
if (!_configService.ImportExtraFiles)
{
return;
}

var trackDestinationDirs = importedTracks.SelectMany(x => x.Item.Tracks.Select(t => t.TrackFile.Value.Path))
.GroupBy(f => _diskProvider.GetParentFolder(f));

var sourceDirs = importedTracks.GroupBy(x => _diskProvider.GetParentFolder(x.Item.Path));
if (!sourceDirs.Any())
{
return;
}

string sourceRoot = null;
string destinationRoot = null;

try
{
sourceRoot = GetCommonParent(sourceDirs.Select(x => x.Key));
destinationRoot = GetCommonParent(trackDestinationDirs.Select(x => x.Key));
}
catch (ArgumentException ex)
{
throw new InvalidOperationException("Common parent dir could not be found, extra files will not be imported", ex);
}

var extraFileImports = new Dictionary<string, AlbumExtraFileImport>();
var trackNames = importedTracks.Select(f => Path.GetFileNameWithoutExtension(f.Item.Path));
var wantedExtensions = ExtraFileExtensionsList();

// extra files in track dirs for multi-CD releases
foreach (var sourceDirImports in sourceDirs)
{
var trackFilePath = sourceDirImports.First()
.Item?.Tracks?.FirstOrDefault()?.TrackFile?.Value?.Path;
if (trackFilePath == null)
{
continue;
}

var targetDir = sourceDirs.Count() == 1
? destinationRoot
: _diskProvider.GetParentFolder(trackFilePath);

var trackDirFiles = _diskProvider.GetFiles(sourceDirImports.Key, false);
var trackDirExtraFiles = FilterAlbumExtraFiles(trackDirFiles, trackNames, wantedExtensions);
foreach (var trackDirExtra in trackDirExtraFiles)
{
var import = AlbumExtraFileImport.AtDestinationDir(trackDirExtra, targetDir);
extraFileImports.Add(trackDirExtra, import);
}

// nested files under track dirs:
var subdirFiles = _diskProvider.GetFiles(sourceDirImports.Key, true);
subdirFiles = FilterAlbumExtraFiles(subdirFiles, trackNames, wantedExtensions);

foreach (var subdirExtra in subdirFiles.Where(x => !extraFileImports.ContainsKey(x)))
{
var extraFileDirectory = _diskProvider.GetParentFolder(subdirExtra);
var relative = sourceDirImports.Key.GetRelativePath(extraFileDirectory);
var dest = Path.Combine(targetDir, relative);
var import = AlbumExtraFileImport.AtDestinationDir(subdirExtra, dest);
extraFileImports.Add(subdirExtra, import);
}
}

if (sourceDirs.Count() > 1)
{
// look for common parent dir
var parentDirs = sourceDirs.GroupBy(x => _diskProvider.GetParentFolder(x.Key));

if (parentDirs.Count() == 1)
{
var albumDirFiles = _diskProvider.GetFiles(parentDirs.Single().Key, true);
var albumExtras = FilterAlbumExtraFiles(albumDirFiles, trackNames, wantedExtensions);

foreach (var albumExtraFile in albumExtras.Where(x => !extraFileImports.ContainsKey(x)))
{
var newImport = AlbumExtraFileImport.AtRelativePathFromSource(albumExtraFile, sourceRoot, destinationRoot);
extraFileImports.Add(albumExtraFile, newImport);
}
}
}

var firstTrack = importedTracks.First();
var artist = firstTrack.Item.Artist;
var albumId = firstTrack.Item.Album.Id;

_albumExtraManager.ImportAlbumExtras(artist, albumId, extraFileImports.Values);
}

public void ImportTrack(LocalTrack localTrack, TrackFile trackFile, bool isReadOnly)
{
ImportExtraFiles(localTrack, trackFile, isReadOnly);
Expand All @@ -69,10 +170,7 @@ public void ImportExtraFiles(LocalTrack localTrack, TrackFile trackFile, bool is
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var files = _diskProvider.GetFiles(sourceFolder, false);

var wantedExtensions = _configService.ExtraFileExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(e => e.Trim(' ', '.'))
.ToList();
var wantedExtensions = ExtraFileExtensionsList();

var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase)).ToList();
var filteredFilenames = new List<string>();
Expand Down Expand Up @@ -176,6 +274,18 @@ public void Handle(ArtistRenamedEvent message)
{
extraFileManager.MoveFilesAfterRename(artist, trackFiles);
}

_ = _albumExtraManager.MoveFilesAfterRename(artist, message.RenamedFiles);
}

private static IEnumerable<string> FilterAlbumExtraFiles(IEnumerable<string> files,
IEnumerable<string> trackFileNames,
IEnumerable<string> wantedExtensions)
{
return files
.Where(x =>
wantedExtensions.Any(ext => x.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase))
&& !trackFileNames.Any(t => t.Equals(Path.GetFileNameWithoutExtension(x), StringComparison.OrdinalIgnoreCase)));
}

private List<TrackFile> GetTrackFiles(int artistId)
Expand All @@ -191,5 +301,53 @@ private List<TrackFile> GetTrackFiles(int artistId)

return trackFiles;
}

private List<string> ExtraFileExtensionsList()
{
return _configService.ExtraFileExtensions
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(e => e.Trim(' ', '.'))
.ToList();
}

private string GetCommonParent(IEnumerable<string> paths)
{
if (paths.Count() == 1)
{
return paths.Single();
}

var parentDirs = paths.GroupBy(p => _diskProvider.GetParentFolder(p));
if (parentDirs.Count() == 1)
{
return parentDirs.Single().Key;
}

// search depth limited to 1+1, parent of parent:
var parentOfParent = parentDirs.Select(d => _diskProvider.GetParentFolder(d.Key)).GroupBy(i => i);
if (parentOfParent.Count() == 1)
{
return parentOfParent.Single().Key;
}

// Look for shortest path and check if this is the parent dir:
var ordered = parentDirs.OrderBy(x => x.Key.Length);

var commonParent = ordered.First().Key;
foreach (var childDir in ordered.Skip(1))
{
try
{
_ = commonParent.GetRelativePath(childDir.Key);
}
catch (NotParentException ex)
{
throw new ArgumentException(
$"Unable to find common parent: child path not under parent candidate '{commonParent}'", nameof(paths), ex);
}
}

return commonParent;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@ public List<ImportResult> Import(List<ImportDecision<LocalTrack>> decisions, boo
var album = _albumService.GetAlbum(albumImport.First().ImportDecision.Item.Album.Id);
var artist = albumImport.First().ImportDecision.Item.Artist;

if (album != null)
{
var albumTrackFiles = filesToAdd.Where(x => x.AlbumId == album.Id);
_extraService.ImportAlbumExtras(albumImport.Select(x => x.ImportDecision).ToList());
}

if (albumImport.Where(e => e.Errors.Count == 0).ToList().Count > 0 && artist != null && album != null)
{
_eventAggregator.PublishEvent(new AlbumImportedEvent(
Expand Down
2 changes: 1 addition & 1 deletion src/NzbDrone.Test.Common/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ protected string TempFolder
[SetUp]
public void TestBaseSetup()
{
GetType().IsPublic.Should().BeTrue("All Test fixtures should be public to work in mono.");
GetType().Should().Match(t => t.IsPublic || t.IsNestedPublic, "All Test fixtures should be public to work in mono.");

LogManager.ReconfigExistingLoggers();

Expand Down

0 comments on commit 89d5037

Please sign in to comment.