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

Build for Import album extras #4526

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
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
614 changes: 614 additions & 0 deletions src/NzbDrone.Core.Test/Extras/ExtraServiceFixture.cs

Large diffs are not rendered by default.

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;

namespace NzbDrone.Core.Datastore.Migration
{
[Migration(079)]
public class relax_not_null_constraints_extra_files : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("ExtraFiles").AlterColumn("TrackFileId").AsInt32().Nullable();
}
}
}
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;
}
}
}
Loading
Loading