mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-14 02:37:08 -07:00
New: Don't require artist mapping
This commit is contained in:
parent
1cc434a498
commit
be4e748977
159 changed files with 2934 additions and 4208 deletions
|
@ -39,7 +39,7 @@ namespace Lidarr.Api.V1.Albums
|
|||
IMapCoversToLocal coverMapper,
|
||||
IUpgradableSpecification upgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster,
|
||||
ProfileExistsValidator profileExistsValidator,
|
||||
QualityProfileExistsValidator qualityProfileExistsValidator,
|
||||
MetadataProfileExistsValidator metadataProfileExistsValidator)
|
||||
|
||||
: base(albumService, artistStatisticsService, coverMapper, upgradableSpecification, signalRBroadcaster)
|
||||
|
@ -54,7 +54,7 @@ namespace Lidarr.Api.V1.Albums
|
|||
Put("/monitor", x => SetAlbumsMonitored());
|
||||
|
||||
PostValidator.RuleFor(s => s.ForeignAlbumId).NotEmpty();
|
||||
PostValidator.RuleFor(s => s.Artist.QualityProfileId).SetValidator(profileExistsValidator);
|
||||
PostValidator.RuleFor(s => s.Artist.QualityProfileId).SetValidator(qualityProfileExistsValidator);
|
||||
PostValidator.RuleFor(s => s.Artist.MetadataProfileId).SetValidator(metadataProfileExistsValidator);
|
||||
PostValidator.RuleFor(s => s.Artist.RootFolderPath).IsValidPath().When(s => s.Artist.Path.IsNullOrWhiteSpace());
|
||||
PostValidator.RuleFor(s => s.Artist.ForeignArtistId).NotEmpty();
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace Lidarr.Api.V1.Artist
|
|||
ArtistExistsValidator artistExistsValidator,
|
||||
ArtistAncestorValidator artistAncestorValidator,
|
||||
SystemFolderValidator systemFolderValidator,
|
||||
ProfileExistsValidator profileExistsValidator,
|
||||
QualityProfileExistsValidator qualityProfileExistsValidator,
|
||||
MetadataProfileExistsValidator metadataProfileExistsValidator)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
|
@ -85,7 +85,7 @@ namespace Lidarr.Api.V1.Artist
|
|||
.SetValidator(systemFolderValidator)
|
||||
.When(s => !s.Path.IsNullOrWhiteSpace());
|
||||
|
||||
SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(profileExistsValidator);
|
||||
SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(qualityProfileExistsValidator);
|
||||
SharedValidator.RuleFor(s => s.MetadataProfileId).SetValidator(metadataProfileExistsValidator);
|
||||
|
||||
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
|
||||
|
|
|
@ -62,7 +62,6 @@ namespace Lidarr.Api.V1.FileSystem
|
|||
return _diskScanService.GetAudioFiles(path).Select(f => new
|
||||
{
|
||||
Path = f.FullName,
|
||||
RelativePath = path.GetRelativePath(f.FullName),
|
||||
Name = f.Name
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Lidarr.Api.V1.ImportLists
|
|||
public static readonly ImportListResourceMapper ResourceMapper = new ImportListResourceMapper();
|
||||
|
||||
public ImportListModule(ImportListFactory importListFactory,
|
||||
ProfileExistsValidator profileExistsValidator,
|
||||
QualityProfileExistsValidator qualityProfileExistsValidator,
|
||||
MetadataProfileExistsValidator metadataProfileExistsValidator)
|
||||
: base(importListFactory, "importlist", ResourceMapper)
|
||||
{
|
||||
|
@ -17,7 +17,7 @@ namespace Lidarr.Api.V1.ImportLists
|
|||
Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.MetadataProfileId));
|
||||
|
||||
SharedValidator.RuleFor(c => c.RootFolderPath).IsValidPath();
|
||||
SharedValidator.RuleFor(c => c.QualityProfileId).SetValidator(profileExistsValidator);
|
||||
SharedValidator.RuleFor(c => c.QualityProfileId).SetValidator(qualityProfileExistsValidator);
|
||||
SharedValidator.RuleFor(c => c.MetadataProfileId).SetValidator(metadataProfileExistsValidator);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,6 @@ namespace Lidarr.Api.V1.ManualImport
|
|||
{
|
||||
Id = resource.Id,
|
||||
Path = resource.Path,
|
||||
RelativePath = resource.RelativePath,
|
||||
Name = resource.Name,
|
||||
Size = resource.Size,
|
||||
Artist = resource.Artist == null ? null : _artistService.GetArtist(resource.Artist.Id),
|
||||
|
|
|
@ -14,7 +14,6 @@ namespace Lidarr.Api.V1.ManualImport
|
|||
public class ManualImportResource : RestResource
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string Name { get; set; }
|
||||
public long Size { get; set; }
|
||||
public ArtistResource Artist { get; set; }
|
||||
|
@ -44,7 +43,6 @@ namespace Lidarr.Api.V1.ManualImport
|
|||
{
|
||||
Id = model.Id,
|
||||
Path = model.Path,
|
||||
RelativePath = model.RelativePath,
|
||||
Name = model.Name,
|
||||
Size = model.Size,
|
||||
Artist = model.Artist.ToResource(),
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using Lidarr.Http;
|
||||
using Lidarr.Http.REST;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
|
@ -18,7 +20,9 @@ namespace Lidarr.Api.V1.RootFolders
|
|||
MappedNetworkDriveValidator mappedNetworkDriveValidator,
|
||||
StartupFolderValidator startupFolderValidator,
|
||||
SystemFolderValidator systemFolderValidator,
|
||||
FolderWritableValidator folderWritableValidator)
|
||||
FolderWritableValidator folderWritableValidator,
|
||||
QualityProfileExistsValidator qualityProfileExistsValidator,
|
||||
MetadataProfileExistsValidator metadataProfileExistsValidator)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_rootFolderService = rootFolderService;
|
||||
|
@ -26,17 +30,29 @@ namespace Lidarr.Api.V1.RootFolders
|
|||
GetResourceAll = GetRootFolders;
|
||||
GetResourceById = GetRootFolder;
|
||||
CreateResource = CreateRootFolder;
|
||||
UpdateResource = UpdateRootFolder;
|
||||
DeleteResource = DeleteFolder;
|
||||
|
||||
SharedValidator.RuleFor(c => c.Path)
|
||||
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||
.IsValidPath()
|
||||
.SetValidator(rootFolderValidator)
|
||||
.SetValidator(mappedNetworkDriveValidator)
|
||||
.SetValidator(startupFolderValidator)
|
||||
.SetValidator(pathExistsValidator)
|
||||
.SetValidator(systemFolderValidator)
|
||||
.SetValidator(folderWritableValidator);
|
||||
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||
.IsValidPath()
|
||||
.SetValidator(mappedNetworkDriveValidator)
|
||||
.SetValidator(startupFolderValidator)
|
||||
.SetValidator(pathExistsValidator)
|
||||
.SetValidator(systemFolderValidator)
|
||||
.SetValidator(folderWritableValidator);
|
||||
|
||||
PostValidator.RuleFor(c => c.Path)
|
||||
.SetValidator(rootFolderValidator);
|
||||
|
||||
SharedValidator.RuleFor(c => c.Name)
|
||||
.NotEmpty();
|
||||
|
||||
SharedValidator.RuleFor(c => c.DefaultMetadataProfileId)
|
||||
.SetValidator(metadataProfileExistsValidator);
|
||||
|
||||
SharedValidator.RuleFor(c => c.DefaultQualityProfileId)
|
||||
.SetValidator(qualityProfileExistsValidator);
|
||||
}
|
||||
|
||||
private RootFolderResource GetRootFolder(int id)
|
||||
|
@ -51,9 +67,21 @@ namespace Lidarr.Api.V1.RootFolders
|
|||
return _rootFolderService.Add(model).Id;
|
||||
}
|
||||
|
||||
private void UpdateRootFolder(RootFolderResource rootFolderResource)
|
||||
{
|
||||
var model = rootFolderResource.ToModel();
|
||||
|
||||
if (model.Path != rootFolderResource.Path)
|
||||
{
|
||||
throw new BadRequestException("Cannot edit root folder path");
|
||||
}
|
||||
|
||||
_rootFolderService.Update(model);
|
||||
}
|
||||
|
||||
private List<RootFolderResource> GetRootFolders()
|
||||
{
|
||||
return _rootFolderService.AllWithUnmappedFolders().ToResource();
|
||||
return _rootFolderService.AllWithSpaceStats().ToResource();
|
||||
}
|
||||
|
||||
private void DeleteFolder(int id)
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Lidarr.Http.REST;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace Lidarr.Api.V1.RootFolders
|
||||
{
|
||||
public class RootFolderResource : RestResource
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public int DefaultMetadataProfileId { get; set; }
|
||||
public int DefaultQualityProfileId { get; set; }
|
||||
public MonitorTypes DefaultMonitorOption { get; set; }
|
||||
public HashSet<int> DefaultTags { get; set; }
|
||||
|
||||
public bool Accessible { get; set; }
|
||||
public long? FreeSpace { get; set; }
|
||||
public long? TotalSpace { get; set; }
|
||||
|
||||
public List<UnmappedFolder> UnmappedFolders { get; set; }
|
||||
}
|
||||
|
||||
public static class RootFolderResourceMapper
|
||||
|
@ -28,11 +33,16 @@ namespace Lidarr.Api.V1.RootFolders
|
|||
{
|
||||
Id = model.Id,
|
||||
|
||||
Name = model.Name,
|
||||
Path = model.Path,
|
||||
DefaultMetadataProfileId = model.DefaultMetadataProfileId,
|
||||
DefaultQualityProfileId = model.DefaultQualityProfileId,
|
||||
DefaultMonitorOption = model.DefaultMonitorOption,
|
||||
DefaultTags = model.DefaultTags,
|
||||
|
||||
Accessible = model.Accessible,
|
||||
FreeSpace = model.FreeSpace,
|
||||
TotalSpace = model.TotalSpace,
|
||||
UnmappedFolders = model.UnmappedFolders
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -46,12 +56,13 @@ namespace Lidarr.Api.V1.RootFolders
|
|||
return new RootFolder
|
||||
{
|
||||
Id = resource.Id,
|
||||
|
||||
Name = resource.Name,
|
||||
Path = resource.Path,
|
||||
|
||||
//Accessible
|
||||
//FreeSpace
|
||||
//UnmappedFolders
|
||||
DefaultMetadataProfileId = resource.DefaultMetadataProfileId,
|
||||
DefaultQualityProfileId = resource.DefaultQualityProfileId,
|
||||
DefaultMonitorOption = resource.DefaultMonitorOption,
|
||||
DefaultTags = resource.DefaultTags
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
{
|
||||
public int ArtistId { get; set; }
|
||||
public int AlbumId { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string Path { get; set; }
|
||||
public long Size { get; set; }
|
||||
public DateTime DateAdded { get; set; }
|
||||
|
@ -74,7 +73,6 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
ArtistId = artist.Id,
|
||||
AlbumId = model.AlbumId,
|
||||
Path = model.Path,
|
||||
RelativePath = artist.Path.GetRelativePath(model.Path),
|
||||
Size = model.Size,
|
||||
DateAdded = model.DateAdded,
|
||||
Quality = model.Quality,
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace Lidarr.Api.V1.Tracks
|
|||
public int AlbumId { get; set; }
|
||||
public List<int> TrackNumbers { get; set; }
|
||||
public int TrackFileId { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string Path { get; set; }
|
||||
public List<TagDifference> Changes { get; set; }
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace Lidarr.Api.V1.Tracks
|
|||
AlbumId = model.AlbumId,
|
||||
TrackNumbers = model.TrackNumbers.ToList(),
|
||||
TrackFileId = model.TrackFileId,
|
||||
RelativePath = model.RelativePath,
|
||||
Path = model.Path,
|
||||
Changes = model.Changes.Select(x => new TagDifference
|
||||
{
|
||||
Field = x.Key,
|
||||
|
|
|
@ -153,5 +153,15 @@ namespace NzbDrone.Common.Extensions
|
|||
{
|
||||
return string.Join(separator, source.Select(predicate));
|
||||
}
|
||||
|
||||
public static TSource MostCommon<TSource>(this IEnumerable<TSource> items)
|
||||
{
|
||||
return items.GroupBy(x => x).OrderByDescending(x => x.Count()).First().Key;
|
||||
}
|
||||
|
||||
public static TResult MostCommon<TSource, TResult>(this IEnumerable<TSource> items, Func<TSource, TResult> predicate)
|
||||
{
|
||||
return items.Select(predicate).GroupBy(x => x).OrderByDescending(x => x.Count()).First().Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
|
@ -6,6 +7,7 @@ using NUnit.Framework;
|
|||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.DiskSpace;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
|
@ -14,14 +16,20 @@ namespace NzbDrone.Core.Test.DiskSpace
|
|||
[TestFixture]
|
||||
public class DiskSpaceServiceFixture : CoreTest<DiskSpaceService>
|
||||
{
|
||||
private string _artistFolder;
|
||||
private string _artostFolder2;
|
||||
private RootFolder _rootDir;
|
||||
private string _artistFolder1;
|
||||
private string _artistFolder2;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_artistFolder = @"G:\fasdlfsdf\artist".AsOsAgnostic();
|
||||
_artostFolder2 = @"G:\fasdlfsdf\artist2".AsOsAgnostic();
|
||||
_rootDir = new RootFolder { Path = @"G:\fasdlfsdf".AsOsAgnostic() };
|
||||
_artistFolder1 = Path.Combine(_rootDir.Path, "artist1");
|
||||
_artistFolder2 = Path.Combine(_rootDir.Path, "artist2");
|
||||
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(x => x.All())
|
||||
.Returns(new List<RootFolder>() { _rootDir });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetMounts())
|
||||
|
@ -59,9 +67,9 @@ namespace NzbDrone.Core.Test.DiskSpace
|
|||
[Test]
|
||||
public void should_check_diskspace_for_artist_folders()
|
||||
{
|
||||
GivenArtist(new Artist { Path = _artistFolder });
|
||||
GivenArtist(new Artist { Path = _artistFolder1 });
|
||||
|
||||
GivenExistingFolder(_artistFolder);
|
||||
GivenExistingFolder(_artistFolder1);
|
||||
|
||||
var freeSpace = Subject.GetFreeSpace();
|
||||
|
||||
|
@ -71,10 +79,10 @@ namespace NzbDrone.Core.Test.DiskSpace
|
|||
[Test]
|
||||
public void should_check_diskspace_for_same_root_folder_only_once()
|
||||
{
|
||||
GivenArtist(new Artist { Path = _artistFolder }, new Artist { Path = _artostFolder2 });
|
||||
GivenArtist(new Artist { Path = _artistFolder1 }, new Artist { Path = _artistFolder2 });
|
||||
|
||||
GivenExistingFolder(_artistFolder);
|
||||
GivenExistingFolder(_artostFolder2);
|
||||
GivenExistingFolder(_artistFolder1);
|
||||
GivenExistingFolder(_artistFolder2);
|
||||
|
||||
var freeSpace = Subject.GetFreeSpace();
|
||||
|
||||
|
@ -84,19 +92,6 @@ namespace NzbDrone.Core.Test.DiskSpace
|
|||
.Verify(v => v.GetAvailableSpace(It.IsAny<string>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_check_diskspace_for_missing_artist_folders()
|
||||
{
|
||||
GivenArtist(new Artist { Path = _artistFolder });
|
||||
|
||||
var freeSpace = Subject.GetFreeSpace();
|
||||
|
||||
freeSpace.Should().BeEmpty();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.GetAvailableSpace(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[TestCase("/boot")]
|
||||
[TestCase("/var/lib/rancher")]
|
||||
[TestCase("/var/lib/rancher/volumes")]
|
||||
|
@ -114,6 +109,10 @@ namespace NzbDrone.Core.Test.DiskSpace
|
|||
.Setup(v => v.GetMounts())
|
||||
.Returns(new List<IMount> { mount.Object });
|
||||
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(x => x.All())
|
||||
.Returns(new List<RootFolder>());
|
||||
|
||||
var freeSpace = Subject.GetFreeSpace();
|
||||
|
||||
freeSpace.Should().BeEmpty();
|
||||
|
|
|
@ -9,7 +9,6 @@ using FluentAssertions;
|
|||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
@ -41,11 +40,15 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
.Build();
|
||||
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
|
||||
.Returns(_rootFolder);
|
||||
.Setup(s => s.GetBestRootFolder(It.IsAny<string>()))
|
||||
.Returns(new RootFolder { Path = _rootFolder });
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(s => s.GetArtists(It.IsAny<List<int>>()))
|
||||
.Returns(new List<Artist>());
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>(), It.IsAny<bool>()))
|
||||
.Setup(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns(new List<ImportDecision<LocalTrack>>());
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
|
@ -57,8 +60,8 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
.Returns(new List<TrackFile>());
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>()))
|
||||
.Returns((List<IFileInfo> files, Artist artist, FilterFilesType filter) => files);
|
||||
.Setup(v => v.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<FilterFilesType>()))
|
||||
.Returns((List<IFileInfo> files, FilterFilesType filter) => files);
|
||||
}
|
||||
|
||||
private void GivenRootFolder(params string[] subfolders)
|
||||
|
@ -112,7 +115,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
[Test]
|
||||
public void should_not_scan_if_root_folder_does_not_exist()
|
||||
{
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
|
||||
|
@ -120,15 +123,18 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
.Verify(v => v.FolderExists(_artist.Path), Times.Never());
|
||||
|
||||
Mocker.GetMock<IMediaFileTableCleanupService>()
|
||||
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Never());
|
||||
.Verify(v => v.Clean(It.IsAny<string>(), It.IsAny<List<string>>()), Times.Never());
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_scan_if_artist_root_folder_is_empty()
|
||||
public void should_not_scan_if_root_folder_is_empty()
|
||||
{
|
||||
GivenRootFolder();
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
|
||||
|
@ -136,72 +142,23 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
.Verify(v => v.FolderExists(_artist.Path), Times.Never());
|
||||
|
||||
Mocker.GetMock<IMediaFileTableCleanupService>()
|
||||
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Never());
|
||||
.Verify(v => v.Clean(It.IsAny<string>(), It.IsAny<List<string>>()), Times.Never());
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), _artist, FilterFilesType.Known, true), Times.Never());
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_create_if_artist_folder_does_not_exist_but_create_folder_enabled()
|
||||
public void should_clean_if_folder_does_not_exist()
|
||||
{
|
||||
GivenRootFolder(_otherArtistFolder);
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.CreateEmptyArtistFolders)
|
||||
.Returns(true);
|
||||
|
||||
Subject.Scan(_artist);
|
||||
|
||||
DiskProvider.FolderExists(_artist.Path).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_create_if_artist_folder_does_not_exist_and_create_folder_disabled()
|
||||
{
|
||||
GivenRootFolder(_otherArtistFolder);
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.CreateEmptyArtistFolders)
|
||||
.Returns(false);
|
||||
|
||||
Subject.Scan(_artist);
|
||||
|
||||
DiskProvider.FolderExists(_artist.Path).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_clean_but_not_import_if_artist_folder_does_not_exist()
|
||||
{
|
||||
GivenRootFolder(_otherArtistFolder);
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
DiskProvider.FolderExists(_artist.Path).Should().BeFalse();
|
||||
|
||||
Mocker.GetMock<IMediaFileTableCleanupService>()
|
||||
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Once());
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), _artist, FilterFilesType.Known, true), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_clean_but_not_import_if_artist_folder_does_not_exist_and_create_folder_enabled()
|
||||
{
|
||||
GivenRootFolder(_otherArtistFolder);
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.CreateEmptyArtistFolders)
|
||||
.Returns(true);
|
||||
|
||||
Subject.Scan(_artist);
|
||||
|
||||
Mocker.GetMock<IMediaFileTableCleanupService>()
|
||||
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Once());
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), _artist, FilterFilesType.Known, true), Times.Never());
|
||||
.Verify(v => v.Clean(It.IsAny<string>(), It.IsAny<List<string>>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -215,10 +172,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "s01e01.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 2), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 2), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -235,10 +192,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -253,10 +210,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -276,10 +233,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Season 2", "s02e02.flac"),
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 4), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 4), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -292,10 +249,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Album 1", ".t01.mp3")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -311,10 +268,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -331,10 +288,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -348,10 +305,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -365,10 +322,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -384,10 +341,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "Season 1", "s01e01.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 2), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 2), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -402,20 +359,20 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
Path.Combine(_artist.Path, "24 The Status Quo Combustion.flac")
|
||||
});
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), _artist, FilterFilesType.Known, true), Times.Once());
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<IFileInfo>>(l => l.Count == 1), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()), Times.Once());
|
||||
}
|
||||
|
||||
private void GivenRejections()
|
||||
{
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(x => x.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>(), It.IsAny<bool>()))
|
||||
.Returns((List<IFileInfo> fileList, Artist artist, FilterFilesType filter, bool includeExisting) =>
|
||||
.Setup(x => x.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns((List<IFileInfo> fileList, IdentificationOverrides idOverrides, ImportDecisionMakerInfo idInfo, ImportDecisionMakerConfig idConfig) =>
|
||||
fileList.Select(x => new LocalTrack
|
||||
{
|
||||
Artist = artist,
|
||||
Artist = _artist,
|
||||
Path = x.FullName,
|
||||
Modified = x.LastWriteTimeUtc,
|
||||
FileTrackInfo = new ParsedTrackInfo()
|
||||
|
@ -437,7 +394,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
GivenKnownFiles(new List<string>());
|
||||
GivenRejections();
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(x => x.AddMany(It.Is<List<TrackFile>>(l => l.Select(t => t.Path).SequenceEqual(files))),
|
||||
|
@ -457,7 +414,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
GivenKnownFiles(files.GetRange(1, 1));
|
||||
GivenRejections();
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(x => x.AddMany(It.Is<List<TrackFile>>(l => l.Select(t => t.Path).SequenceEqual(files.GetRange(0, 1)))),
|
||||
|
@ -477,7 +434,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
GivenKnownFiles(files);
|
||||
GivenRejections();
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(x => x.AddMany(It.Is<List<TrackFile>>(l => l.Count == 0)),
|
||||
|
@ -501,7 +458,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
GivenKnownFiles(files);
|
||||
GivenRejections();
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(x => x.Update(It.Is<List<TrackFile>>(l => l.Count == 0)),
|
||||
|
@ -525,7 +482,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
GivenKnownFiles(files);
|
||||
GivenRejections();
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(x => x.Update(It.Is<List<TrackFile>>(l => l.Count == 2)),
|
||||
|
@ -556,14 +513,14 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
|||
.Build();
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(x => x.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>(), It.IsAny<bool>()))
|
||||
.Setup(x => x.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns(new List<ImportDecision<LocalTrack>> { new ImportDecision<LocalTrack>(localTrack, new Rejection("Reject")) });
|
||||
|
||||
Subject.Scan(_artist);
|
||||
Subject.Scan(new List<string> { _artist.Path });
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(x => x.Update(It.Is<List<TrackFile>>(
|
||||
l => l.Count == 1 &&
|
||||
l => l.Count == 1 &&
|
||||
l[0].Path == localTrack.Path &&
|
||||
l[0].Modified == localTrack.Modified &&
|
||||
l[0].Size == localTrack.Size &&
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
imported.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>(), null))
|
||||
.Setup(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
|
@ -130,8 +130,8 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
Subject.ProcessRootFolder(DiskProvider.GetDirectoryInfo(_droneFactory));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(c => c.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>(), It.IsAny<ParsedTrackInfo>()),
|
||||
Times.Never());
|
||||
.Verify(c => c.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()),
|
||||
Times.Never());
|
||||
|
||||
VerifyNoImport();
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
imported.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>(), null))
|
||||
.Setup(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
|
@ -238,7 +238,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
imported.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>(), null))
|
||||
.Setup(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
|
@ -278,7 +278,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
imported.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>(), null))
|
||||
.Setup(v => v.GetImportDecisions(It.IsAny<List<IFileInfo>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerInfo>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedTracks>()
|
||||
|
|
|
@ -123,7 +123,6 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
{
|
||||
VerifyData();
|
||||
var firstReleaseFiles = Subject.GetFilesWithBasePath(dir.AsOsAgnostic());
|
||||
VerifyEagerLoaded(firstReleaseFiles);
|
||||
|
||||
firstReleaseFiles.Should().HaveCount(2);
|
||||
}
|
||||
|
|
|
@ -17,19 +17,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
[TestFixture]
|
||||
public class FilterFixture : FileSystemTest<MediaFileService>
|
||||
{
|
||||
private Artist _artist;
|
||||
private DateTime _lastWrite = new DateTime(2019, 1, 1);
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_artist = new Artist
|
||||
{
|
||||
Id = 10,
|
||||
Path = @"C:\".AsOsAgnostic()
|
||||
};
|
||||
}
|
||||
|
||||
private List<IFileInfo> GivenFiles(string[] files)
|
||||
{
|
||||
foreach (var file in files)
|
||||
|
@ -52,10 +41,10 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
});
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().BeEquivalentTo(files);
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().BeEquivalentTo(files);
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Known)]
|
||||
|
@ -70,14 +59,14 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
});
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(files.Select(f => new TrackFile
|
||||
{
|
||||
Path = f.FullName,
|
||||
Modified = _lastWrite
|
||||
}).ToList());
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().BeEmpty();
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Known)]
|
||||
|
@ -92,7 +81,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
});
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>
|
||||
{
|
||||
new TrackFile
|
||||
|
@ -102,8 +91,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
}
|
||||
});
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(2);
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().HaveCount(2);
|
||||
Subject.FilterUnchangedFiles(files, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Known)]
|
||||
|
@ -120,7 +109,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
});
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>
|
||||
{
|
||||
new TrackFile
|
||||
|
@ -130,8 +119,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
}
|
||||
});
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(2);
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().HaveCount(2);
|
||||
Subject.FilterUnchangedFiles(files, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Known)]
|
||||
|
@ -148,7 +137,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
});
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>
|
||||
{
|
||||
new TrackFile
|
||||
|
@ -158,7 +147,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
}
|
||||
});
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(3);
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Known)]
|
||||
|
@ -171,12 +160,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
});
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(1);
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain(files.First().FullName.ToLower());
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().Contain(files.First());
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().HaveCount(1);
|
||||
Subject.FilterUnchangedFiles(files, filter).Select(x => x.FullName).Should().NotContain(files.First().FullName.ToLower());
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().Contain(files.First());
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Known)]
|
||||
|
@ -190,7 +179,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
var files = FileSystem.AllFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>
|
||||
{
|
||||
new TrackFile
|
||||
|
@ -201,8 +190,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
}
|
||||
});
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(2);
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().HaveCount(2);
|
||||
Subject.FilterUnchangedFiles(files, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Matched)]
|
||||
|
@ -215,7 +204,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
var files = FileSystem.AllFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>
|
||||
{
|
||||
new TrackFile
|
||||
|
@ -227,8 +216,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
}
|
||||
});
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(3);
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().Contain("C:\\file2.avi".AsOsAgnostic());
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().HaveCount(3);
|
||||
Subject.FilterUnchangedFiles(files, filter).Select(x => x.FullName).Should().Contain("C:\\file2.avi".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Matched)]
|
||||
|
@ -241,7 +230,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
var files = FileSystem.AllFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>
|
||||
{
|
||||
new TrackFile
|
||||
|
@ -253,8 +242,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
}
|
||||
});
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(2);
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().HaveCount(2);
|
||||
Subject.FilterUnchangedFiles(files, filter).Select(x => x.FullName).Should().NotContain("C:\\file2.avi".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[TestCase(FilterFilesType.Known)]
|
||||
|
@ -268,7 +257,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
var files = FileSystem.AllFiles.Select(x => DiskProvider.GetFileInfo(x)).ToList();
|
||||
|
||||
Mocker.GetMock<IMediaFileRepository>()
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Setup(c => c.GetFileWithPath(It.IsAny<List<string>>()))
|
||||
.Returns(new List<TrackFile>
|
||||
{
|
||||
new TrackFile
|
||||
|
@ -279,8 +268,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
|||
}
|
||||
});
|
||||
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Should().HaveCount(3);
|
||||
Subject.FilterUnchangedFiles(files, _artist, filter).Select(x => x.FullName).Should().Contain("C:\\file2.avi".AsOsAgnostic());
|
||||
Subject.FilterUnchangedFiles(files, filter).Should().HaveCount(3);
|
||||
Subject.FilterUnchangedFiles(files, filter).Select(x => x.FullName).Should().Contain("C:\\file2.avi".AsOsAgnostic());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
{
|
||||
public class MediaFileTableCleanupServiceFixture : CoreTest<MediaFileTableCleanupService>
|
||||
{
|
||||
private readonly string _deletedPath = @"c:\ANY FILE STARTING WITH THIS PATH IS CONSIDERED DELETED!".AsOsAgnostic();
|
||||
private readonly string _DELETED_PATH = @"c:\ANY FILE STARTING WITH THIS PATH IS CONSIDERED DELETED!".AsOsAgnostic();
|
||||
private List<Track> _tracks;
|
||||
private Artist _artist;
|
||||
|
||||
|
@ -62,7 +62,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
|
||||
GivenTrackFiles(trackFiles);
|
||||
|
||||
Subject.Clean(_artist, FilesOnDisk(trackFiles));
|
||||
Subject.Clean(_artist.Path, FilesOnDisk(trackFiles));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(c => c.DeleteMany(It.Is<List<TrackFile>>(x => x.Count == 0), DeleteMediaFileReason.MissingFromDisk), Times.Once());
|
||||
|
@ -75,15 +75,15 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
.All()
|
||||
.With(x => x.Path = Path.Combine(@"c:\test".AsOsAgnostic(), Path.GetRandomFileName()))
|
||||
.Random(2)
|
||||
.With(c => c.Path = Path.Combine(_deletedPath, Path.GetRandomFileName()))
|
||||
.With(c => c.Path = Path.Combine(_DELETED_PATH, Path.GetRandomFileName()))
|
||||
.Build();
|
||||
|
||||
GivenTrackFiles(trackFiles);
|
||||
|
||||
Subject.Clean(_artist, FilesOnDisk(trackFiles.Where(e => !e.Path.StartsWith(_deletedPath))));
|
||||
Subject.Clean(_artist.Path, FilesOnDisk(trackFiles.Where(e => !e.Path.StartsWith(_DELETED_PATH))));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(c => c.DeleteMany(It.Is<List<TrackFile>>(e => e.Count == 2 && e.All(y => y.Path.StartsWith(_deletedPath))), DeleteMediaFileReason.MissingFromDisk), Times.Once());
|
||||
.Verify(c => c.DeleteMany(It.Is<List<TrackFile>>(e => e.Count == 2 && e.All(y => y.Path.StartsWith(_DELETED_PATH))), DeleteMediaFileReason.MissingFromDisk), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
|
||||
GivenTrackFiles(trackFiles);
|
||||
|
||||
Subject.Clean(_artist, new List<string>());
|
||||
Subject.Clean(_artist.Path, new List<string>());
|
||||
|
||||
Mocker.GetMock<ITrackService>()
|
||||
.Verify(c => c.SetFileIds(It.Is<List<Track>>(e => e.Count == 10 && e.All(y => y.TrackFileId == 0))), Times.Once());
|
||||
|
@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
|
||||
GivenTrackFiles(trackFiles);
|
||||
|
||||
Subject.Clean(_artist, FilesOnDisk(trackFiles));
|
||||
Subject.Clean(_artist.Path, FilesOnDisk(trackFiles));
|
||||
|
||||
Mocker.GetMock<ITrackService>().Verify(c => c.SetFileIds(It.Is<List<Track>>(x => x.Count == 0)), Times.Once());
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ using NzbDrone.Core.Test.Framework;
|
|||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlbumDistanceFixture : CoreTest<IdentificationService>
|
||||
public class AlbumDistanceFixture : CoreTest
|
||||
{
|
||||
private ArtistMetadata _artist;
|
||||
|
||||
|
@ -28,13 +28,13 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
|
||||
private List<Track> GivenTracks(int count)
|
||||
{
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = _artist)
|
||||
.With(x => x.MediumNumber = 1)
|
||||
.Build()
|
||||
.ToList();
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = _artist)
|
||||
.With(x => x.MediumNumber = 1)
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private LocalTrack GivenLocalTrack(Track track, AlbumRelease release)
|
||||
|
@ -102,7 +102,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
private TrackMapping GivenMapping(List<LocalTrack> local, List<Track> remote)
|
||||
{
|
||||
var mapping = new TrackMapping();
|
||||
var distances = local.Zip(remote, (l, r) => Tuple.Create(r, Subject.TrackDistance(l, r, Subject.GetTotalTrackNumber(r, remote))));
|
||||
var distances = local.Zip(remote, (l, r) => Tuple.Create(r, DistanceCalculator.TrackDistance(l, r, DistanceCalculator.GetTotalTrackNumber(r, remote))));
|
||||
mapping.Mapping = local.Zip(distances, (l, r) => new { l, r }).ToDictionary(x => x.l, x => x.r);
|
||||
mapping.LocalExtra = local.Except(mapping.Mapping.Keys).ToList();
|
||||
mapping.MBExtra = remote.Except(mapping.Mapping.Values.Select(x => x.Item1)).ToList();
|
||||
|
@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -130,7 +130,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
localTracks.RemoveAt(1);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
var dist = Subject.AlbumReleaseDistance(localTracks, release, mapping);
|
||||
var dist = DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping);
|
||||
dist.NormalizedDistance().Should().NotBe(0.0);
|
||||
dist.NormalizedDistance().Should().BeLessThan(0.2);
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
.With(x => x.Name = "different artist")
|
||||
.Build();
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().NotBe(0.0);
|
||||
DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
.With(x => x.ForeignArtistId = "89ad4ac3-39f7-470e-963a-56509c546377")
|
||||
.Build();
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
// TODO: there are a couple more VA tests in beets but we don't support VA yet anyway
|
||||
|
@ -178,7 +178,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
localTracks = new[] { 1, 3, 2 }.Select(x => localTracks[x - 1]).ToList();
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
var dist = Subject.AlbumReleaseDistance(localTracks, release, mapping);
|
||||
var dist = DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping);
|
||||
dist.NormalizedDistance().Should().NotBe(0.0);
|
||||
dist.NormalizedDistance().Should().BeLessThan(0.2);
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -209,7 +209,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
private static DateTime?[] dates = new DateTime?[] { null, new DateTime(2007, 1, 1), DateTime.Now };
|
||||
|
@ -225,7 +225,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
release.Album.Value.ReleaseDate = null;
|
||||
release.ReleaseDate = releaseDate;
|
||||
|
||||
var result = Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance();
|
||||
var result = DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance();
|
||||
|
||||
if (!releaseDate.HasValue || (localTracks[0].FileTrackInfo.Year == (releaseDate?.Year ?? 0)))
|
||||
{
|
||||
|
@ -248,7 +248,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
release.Album.Value.ReleaseDate = albumDate;
|
||||
release.ReleaseDate = null;
|
||||
|
||||
var result = Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance();
|
||||
var result = DistanceCalculator.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance();
|
||||
|
||||
if (!albumDate.HasValue || (localTracks[0].FileTrackInfo.Year == (albumDate?.Year ?? 0)))
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using FizzWare.NBuilder;
|
|||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
@ -13,7 +14,7 @@ using NzbDrone.Core.Test.Framework;
|
|||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class GetCandidatesFixture : CoreTest<IdentificationService>
|
||||
public class GetCandidatesFixture : CoreTest<CandidateService>
|
||||
{
|
||||
private ArtistMetadata _artist;
|
||||
|
||||
|
@ -28,12 +29,12 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
|
||||
private List<Track> GivenTracks(int count)
|
||||
{
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = _artist)
|
||||
.Build()
|
||||
.ToList();
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = _artist)
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private ParsedTrackInfo GivenParsedTrackInfo(Track track, AlbumRelease release)
|
||||
|
@ -108,12 +109,12 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
Mocker.GetMock<IFingerprintingService>()
|
||||
.Setup(x => x.Lookup(It.IsAny<List<LocalTrack>>(), It.IsAny<double>()))
|
||||
.Callback((List<LocalTrack> x, double thres) =>
|
||||
{
|
||||
foreach (var track in x)
|
||||
{
|
||||
track.AcoustIdResults = null;
|
||||
}
|
||||
});
|
||||
foreach (var track in x)
|
||||
{
|
||||
track.AcoustIdResults = null;
|
||||
}
|
||||
});
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Setup(x => x.GetReleasesByRecordingIds(It.IsAny<List<string>>()))
|
||||
|
@ -121,7 +122,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
|
||||
var local = GivenLocalAlbumRelease();
|
||||
|
||||
Subject.GetCandidatesFromFingerprint(local, null, null, null, false).Should().BeEquivalentTo(new List<CandidateAlbumRelease>());
|
||||
Subject.GetDbCandidatesFromFingerprint(local, null, false).Should().BeEquivalentTo(new List<CandidateAlbumRelease>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -131,8 +132,12 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var localAlbumRelease = new LocalAlbumRelease(localTracks);
|
||||
var idOverrides = new IdentificationOverrides
|
||||
{
|
||||
AlbumRelease = release
|
||||
};
|
||||
|
||||
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, release, false).Should().BeEquivalentTo(
|
||||
Subject.GetDbCandidatesFromTags(localAlbumRelease, idOverrides, false).Should().BeEquivalentTo(
|
||||
new List<CandidateAlbumRelease> { new CandidateAlbumRelease(release) });
|
||||
}
|
||||
|
||||
|
@ -149,7 +154,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
.Setup(x => x.GetReleaseByForeignReleaseId("xxx", true))
|
||||
.Returns(release);
|
||||
|
||||
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, null, false).Should().BeEquivalentTo(
|
||||
Subject.GetDbCandidatesFromTags(localAlbumRelease, null, false).Should().BeEquivalentTo(
|
||||
new List<CandidateAlbumRelease> { new CandidateAlbumRelease(release) });
|
||||
}
|
||||
}
|
|
@ -9,6 +9,9 @@ using Newtonsoft.Json;
|
|||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.ImportLists.Exclusions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation.Aggregators;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
|
@ -32,7 +35,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
private AddArtistService _addArtistService;
|
||||
private RefreshArtistService _refreshArtistService;
|
||||
|
||||
private IdentificationService _subject;
|
||||
private IdentificationService _Subject;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
|
@ -45,6 +48,8 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
Mocker.SetConstant<IAlbumRepository>(Mocker.Resolve<AlbumRepository>());
|
||||
Mocker.SetConstant<IReleaseRepository>(Mocker.Resolve<ReleaseRepository>());
|
||||
Mocker.SetConstant<ITrackRepository>(Mocker.Resolve<TrackRepository>());
|
||||
Mocker.SetConstant<IImportListExclusionRepository>(Mocker.Resolve<ImportListExclusionRepository>());
|
||||
Mocker.SetConstant<IMediaFileRepository>(Mocker.Resolve<MediaFileRepository>());
|
||||
|
||||
Mocker.GetMock<IMetadataProfileService>().Setup(x => x.Exists(It.IsAny<int>())).Returns(true);
|
||||
|
||||
|
@ -54,6 +59,8 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
Mocker.SetConstant<IAlbumService>(Mocker.Resolve<AlbumService>());
|
||||
Mocker.SetConstant<IReleaseService>(Mocker.Resolve<ReleaseService>());
|
||||
Mocker.SetConstant<ITrackService>(Mocker.Resolve<TrackService>());
|
||||
Mocker.SetConstant<IImportListExclusionService>(Mocker.Resolve<ImportListExclusionService>());
|
||||
Mocker.SetConstant<IMediaFileService>(Mocker.Resolve<MediaFileService>());
|
||||
|
||||
Mocker.SetConstant<IConfigService>(Mocker.Resolve<IConfigService>());
|
||||
Mocker.SetConstant<IProvideArtistInfo>(Mocker.Resolve<SkyHookProxy>());
|
||||
|
@ -69,6 +76,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
Mocker.GetMock<IAddArtistValidator>().Setup(x => x.Validate(It.IsAny<Artist>())).Returns(new ValidationResult());
|
||||
|
||||
Mocker.SetConstant<ITrackGroupingService>(Mocker.Resolve<TrackGroupingService>());
|
||||
Mocker.SetConstant<ICandidateService>(Mocker.Resolve<CandidateService>());
|
||||
|
||||
// set up the augmenters
|
||||
List<IAggregate<LocalAlbumRelease>> aggregators = new List<IAggregate<LocalAlbumRelease>>
|
||||
|
@ -78,7 +86,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
Mocker.SetConstant<IEnumerable<IAggregate<LocalAlbumRelease>>>(aggregators);
|
||||
Mocker.SetConstant<IAugmentingService>(Mocker.Resolve<AugmentingService>());
|
||||
|
||||
_subject = Mocker.Resolve<IdentificationService>();
|
||||
_Subject = Mocker.Resolve<IdentificationService>();
|
||||
}
|
||||
|
||||
private void GivenMetadataProfile(MetadataProfile profile)
|
||||
|
@ -131,9 +139,9 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
Mocker.GetMock<IFingerprintingService>()
|
||||
.Setup(x => x.Lookup(It.IsAny<List<LocalTrack>>(), It.IsAny<double>()))
|
||||
.Callback((List<LocalTrack> track, double thres) =>
|
||||
{
|
||||
track.ForEach(x => x.AcoustIdResults = fingerprints.SingleOrDefault(f => f.Path == x.Path).AcoustIdResults);
|
||||
});
|
||||
{
|
||||
track.ForEach(x => x.AcoustIdResults = fingerprints.SingleOrDefault(f => f.Path == x.Path).AcoustIdResults);
|
||||
});
|
||||
}
|
||||
|
||||
public static class IdTestCaseFactory
|
||||
|
@ -164,7 +172,6 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
|
||||
// these are slow to run so only do so manually
|
||||
[Explicit]
|
||||
[Test]
|
||||
[TestCaseSource(typeof(IdTestCaseFactory), "TestCases")]
|
||||
public void should_match_tracks(string file)
|
||||
{
|
||||
|
@ -173,6 +180,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
|
||||
var artists = GivenArtists(testcase.LibraryArtists);
|
||||
var specifiedArtist = artists.SingleOrDefault(x => x.Metadata.Value.ForeignArtistId == testcase.Artist);
|
||||
var idOverrides = new IdentificationOverrides { Artist = specifiedArtist };
|
||||
|
||||
var tracks = testcase.Tracks.Select(x => new LocalTrack
|
||||
{
|
||||
|
@ -185,7 +193,14 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
GivenFingerprints(testcase.Fingerprints);
|
||||
}
|
||||
|
||||
var result = _subject.Identify(tracks, specifiedArtist, null, null, testcase.NewDownload, testcase.SingleRelease, false);
|
||||
var config = new ImportDecisionMakerConfig
|
||||
{
|
||||
NewDownload = testcase.NewDownload,
|
||||
SingleRelease = testcase.SingleRelease,
|
||||
IncludeExisting = false
|
||||
};
|
||||
|
||||
var result = _Subject.Identify(tracks, idOverrides, config);
|
||||
|
||||
TestLogger.Debug($"Found releases:\n{result.Where(x => x.AlbumRelease != null).Select(x => x.AlbumRelease?.ForeignReleaseId).ToJson()}");
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ using NzbDrone.Core.Test.Framework;
|
|||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class TrackDistanceFixture : CoreTest<IdentificationService>
|
||||
public class TrackDistanceFixture : CoreTest
|
||||
{
|
||||
private Track GivenTrack(string title)
|
||||
{
|
||||
|
@ -53,7 +53,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
DistanceCalculator.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.Title = "one (feat. two)";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
DistanceCalculator.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -73,7 +73,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.CleanTitle = "foo";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().NotBe(0.0);
|
||||
DistanceCalculator.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.ArtistTitle = "foo";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().NotBe(0.0);
|
||||
DistanceCalculator.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -93,7 +93,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.ArtistTitle = "Various Artists";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
DistanceCalculator.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,13 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
private List<IFileInfo> _fileInfos;
|
||||
private LocalTrack _localTrack;
|
||||
private Artist _artist;
|
||||
private Album _album;
|
||||
private AlbumRelease _albumRelease;
|
||||
private QualityModel _quality;
|
||||
|
||||
private IdentificationOverrides _idOverrides;
|
||||
private ImportDecisionMakerConfig _idConfig;
|
||||
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass3;
|
||||
|
@ -82,10 +86,16 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail3"));
|
||||
|
||||
_artist = Builder<Artist>.CreateNew()
|
||||
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
|
||||
.Build();
|
||||
.With(e => e.QualityProfileId = 1)
|
||||
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
|
||||
.Build();
|
||||
|
||||
_album = Builder<Album>.CreateNew()
|
||||
.With(x => x.Artist = _artist)
|
||||
.Build();
|
||||
|
||||
_albumRelease = Builder<AlbumRelease>.CreateNew()
|
||||
.With(x => x.Album = _album)
|
||||
.Build();
|
||||
|
||||
_quality = new QualityModel(Quality.MP3_256);
|
||||
|
@ -98,11 +108,18 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic()
|
||||
};
|
||||
|
||||
_idOverrides = new IdentificationOverrides
|
||||
{
|
||||
Artist = _artist
|
||||
};
|
||||
|
||||
_idConfig = new ImportDecisionMakerConfig();
|
||||
|
||||
GivenAudioFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() });
|
||||
|
||||
Mocker.GetMock<IIdentificationService>()
|
||||
.Setup(s => s.Identify(It.IsAny<List<LocalTrack>>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<AlbumRelease>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<bool>()))
|
||||
.Returns((List<LocalTrack> tracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease, bool includeExisting) =>
|
||||
.Setup(s => s.Identify(It.IsAny<List<LocalTrack>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns((List<LocalTrack> tracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) =>
|
||||
{
|
||||
var ret = new LocalAlbumRelease(tracks);
|
||||
ret.AlbumRelease = _albumRelease;
|
||||
|
@ -110,8 +127,8 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
});
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(c => c.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<Artist>(), It.IsAny<FilterFilesType>()))
|
||||
.Returns((List<IFileInfo> files, Artist artist, FilterFilesType filter) => files);
|
||||
.Setup(c => c.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<FilterFilesType>()))
|
||||
.Returns((List<IFileInfo> files, FilterFilesType filter) => files);
|
||||
|
||||
GivenSpecifications(_albumpass1);
|
||||
}
|
||||
|
@ -145,10 +162,12 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
public void should_call_all_album_specifications()
|
||||
{
|
||||
var downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||
var itemInfo = new ImportDecisionMakerInfo { DownloadClientItem = downloadClientItem };
|
||||
|
||||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3, _albumfail1, _albumfail2, _albumfail3);
|
||||
|
||||
Subject.GetImportDecisions(_fileInfos, new Artist(), null, null, downloadClientItem, null, FilterFilesType.None, false, false, false);
|
||||
Subject.GetImportDecisions(_fileInfos, null, itemInfo, _idConfig);
|
||||
|
||||
_albumfail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumfail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
|
@ -162,10 +181,12 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
public void should_call_all_track_specifications_if_album_accepted()
|
||||
{
|
||||
var downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||
var itemInfo = new ImportDecisionMakerInfo { DownloadClientItem = downloadClientItem };
|
||||
|
||||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||
|
||||
Subject.GetImportDecisions(_fileInfos, new Artist(), null, null, downloadClientItem, null, FilterFilesType.None, false, false, false);
|
||||
Subject.GetImportDecisions(_fileInfos, null, itemInfo, _idConfig);
|
||||
|
||||
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
|
@ -179,11 +200,13 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
public void should_call_no_track_specifications_if_album_rejected()
|
||||
{
|
||||
var downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||
var itemInfo = new ImportDecisionMakerInfo { DownloadClientItem = downloadClientItem };
|
||||
|
||||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3, _albumfail1, _albumfail2, _albumfail3);
|
||||
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||
|
||||
Subject.GetImportDecisions(_fileInfos, new Artist(), null, null, downloadClientItem, null, FilterFilesType.None, false, false, false);
|
||||
Subject.GetImportDecisions(_fileInfos, null, itemInfo, _idConfig);
|
||||
|
||||
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
||||
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
||||
|
@ -199,7 +222,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
GivenSpecifications(_albumfail1);
|
||||
GivenSpecifications(_pass1);
|
||||
|
||||
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
|
||||
var result = Subject.GetImportDecisions(_fileInfos, null, null, _idConfig);
|
||||
|
||||
result.Single().Approved.Should().BeFalse();
|
||||
}
|
||||
|
@ -210,7 +233,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
GivenSpecifications(_albumpass1);
|
||||
GivenSpecifications(_fail1);
|
||||
|
||||
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
|
||||
var result = Subject.GetImportDecisions(_fileInfos, null, null, _idConfig);
|
||||
|
||||
result.Single().Approved.Should().BeFalse();
|
||||
}
|
||||
|
@ -221,7 +244,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
GivenSpecifications(_albumpass1, _albumfail1, _albumpass2, _albumpass3);
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
|
||||
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
|
||||
var result = Subject.GetImportDecisions(_fileInfos, null, null, _idConfig);
|
||||
|
||||
result.Single().Approved.Should().BeFalse();
|
||||
}
|
||||
|
@ -232,7 +255,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3);
|
||||
GivenSpecifications(_pass1, _fail1, _pass2, _pass3);
|
||||
|
||||
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
|
||||
var result = Subject.GetImportDecisions(_fileInfos, null, null, _idConfig);
|
||||
|
||||
result.Single().Approved.Should().BeFalse();
|
||||
}
|
||||
|
@ -244,7 +267,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3);
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
|
||||
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
|
||||
var result = Subject.GetImportDecisions(_fileInfos, null, null, _idConfig);
|
||||
|
||||
result.Single().Approved.Should().BeTrue();
|
||||
}
|
||||
|
@ -255,7 +278,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
GivenAugmentationSuccess();
|
||||
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||
|
||||
var result = Subject.GetImportDecisions(_fileInfos, new Artist(), FilterFilesType.None, false);
|
||||
var result = Subject.GetImportDecisions(_fileInfos, null, null, _idConfig);
|
||||
result.Single().Rejections.Should().HaveCount(3);
|
||||
}
|
||||
|
||||
|
@ -275,7 +298,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
|
||||
});
|
||||
|
||||
Subject.GetImportDecisions(_fileInfos, _artist, FilterFilesType.None, false);
|
||||
var decisions = Subject.GetImportDecisions(_fileInfos, _idOverrides, null, _idConfig);
|
||||
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_fileInfos.Count));
|
||||
|
@ -296,13 +319,13 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
});
|
||||
|
||||
Mocker.GetMock<IIdentificationService>()
|
||||
.Setup(s => s.Identify(It.IsAny<List<LocalTrack>>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<AlbumRelease>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<bool>()))
|
||||
.Returns((List<LocalTrack> tracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease, bool includeExisting) =>
|
||||
{
|
||||
return new List<LocalAlbumRelease> { new LocalAlbumRelease(tracks) };
|
||||
});
|
||||
.Setup(s => s.Identify(It.IsAny<List<LocalTrack>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns((List<LocalTrack> tracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) =>
|
||||
{
|
||||
return new List<LocalAlbumRelease> { new LocalAlbumRelease(tracks) };
|
||||
});
|
||||
|
||||
var decisions = Subject.GetImportDecisions(_fileInfos, _artist, FilterFilesType.None, false);
|
||||
var decisions = Subject.GetImportDecisions(_fileInfos, _idOverrides, null, _idConfig);
|
||||
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_fileInfos.Count));
|
||||
|
@ -323,7 +346,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
|
||||
});
|
||||
|
||||
var decisions = Subject.GetImportDecisions(_fileInfos, _artist, FilterFilesType.None, false);
|
||||
var decisions = Subject.GetImportDecisions(_fileInfos, _idOverrides, null, _idConfig);
|
||||
|
||||
Mocker.GetMock<IAugmentingService>()
|
||||
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_fileInfos.Count));
|
||||
|
@ -344,7 +367,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
|||
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
|
||||
});
|
||||
|
||||
Subject.GetImportDecisions(_fileInfos, _artist, FilterFilesType.None, false).Should().HaveCount(1);
|
||||
Subject.GetImportDecisions(_fileInfos, _idOverrides, null, _idConfig).Should().HaveCount(1);
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
|
|
@ -127,5 +127,91 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_disambiguate_if_artist_folder_exists()
|
||||
{
|
||||
var newArtist = new Artist
|
||||
{
|
||||
ForeignArtistId = "ce09ea31-3d4a-4487-a797-e315175457a0",
|
||||
Path = @"C:\Test\Music\Name1",
|
||||
};
|
||||
|
||||
_fakeArtist.Metadata = Builder<ArtistMetadata>.CreateNew().With(x => x.Disambiguation = "Disambiguation").Build();
|
||||
|
||||
GivenValidArtist(newArtist.ForeignArtistId);
|
||||
GivenValidPath();
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.ArtistPathExists(newArtist.Path))
|
||||
.Returns(true);
|
||||
|
||||
var artist = Subject.AddArtist(newArtist);
|
||||
artist.Path.Should().Be(newArtist.Path + " (Disambiguation)");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_disambiguate_with_numbers_if_artist_folder_still_exists()
|
||||
{
|
||||
var newArtist = new Artist
|
||||
{
|
||||
ForeignArtistId = "ce09ea31-3d4a-4487-a797-e315175457a0",
|
||||
Path = @"C:\Test\Music\Name1",
|
||||
};
|
||||
|
||||
_fakeArtist.Metadata = Builder<ArtistMetadata>.CreateNew().With(x => x.Disambiguation = "Disambiguation").Build();
|
||||
|
||||
GivenValidArtist(newArtist.ForeignArtistId);
|
||||
GivenValidPath();
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.ArtistPathExists(newArtist.Path))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.ArtistPathExists(newArtist.Path + " (Disambiguation)"))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.ArtistPathExists(newArtist.Path + " (Disambiguation) (1)"))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.ArtistPathExists(newArtist.Path + " (Disambiguation) (2)"))
|
||||
.Returns(true);
|
||||
|
||||
var artist = Subject.AddArtist(newArtist);
|
||||
artist.Path.Should().Be(newArtist.Path + " (Disambiguation) (3)");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_disambiguate_with_numbers_if_artist_folder_exists_and_no_disambiguation()
|
||||
{
|
||||
var newArtist = new Artist
|
||||
{
|
||||
ForeignArtistId = "ce09ea31-3d4a-4487-a797-e315175457a0",
|
||||
Path = @"C:\Test\Music\Name1",
|
||||
};
|
||||
|
||||
_fakeArtist.Metadata = Builder<ArtistMetadata>.CreateNew().With(x => x.Disambiguation = string.Empty).Build();
|
||||
|
||||
GivenValidArtist(newArtist.ForeignArtistId);
|
||||
GivenValidPath();
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.ArtistPathExists(newArtist.Path))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.ArtistPathExists(newArtist.Path + " (1)"))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.ArtistPathExists(newArtist.Path + " (2)"))
|
||||
.Returns(true);
|
||||
|
||||
var artist = Subject.AddArtist(newArtist);
|
||||
artist.Path.Should().Be(newArtist.Path + " (3)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ using NzbDrone.Core.MetadataSource;
|
|||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Commands;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
|
@ -48,8 +49,8 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
.Build();
|
||||
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.Setup(s => s.GetArtist(_artist.Id))
|
||||
.Returns(_artist);
|
||||
.Setup(s => s.GetArtists(new List<int> { _artist.Id }))
|
||||
.Returns(new List<Artist> { _artist });
|
||||
|
||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||
.Setup(s => s.InsertMany(It.IsAny<List<Album>>()));
|
||||
|
@ -69,6 +70,10 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
Mocker.GetMock<IImportListExclusionService>()
|
||||
.Setup(x => x.FindByForeignId(It.IsAny<List<string>>()))
|
||||
.Returns(new List<ImportListExclusion>());
|
||||
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(x => x.All())
|
||||
.Returns(new List<RootFolder>());
|
||||
}
|
||||
|
||||
private void GivenNewArtistInfo(Artist artist)
|
||||
|
|
|
@ -8,6 +8,7 @@ using NzbDrone.Core.ImportLists;
|
|||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Profiles.Metadata;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Profiles.Metadata
|
||||
|
@ -122,8 +123,14 @@ namespace NzbDrone.Core.Test.Profiles.Metadata
|
|||
.With(c => c.MetadataProfileId = 1)
|
||||
.Build().ToList();
|
||||
|
||||
var rootFolders = Builder<RootFolder>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(f => f.DefaultMetadataProfileId = 1)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IArtistService>().Setup(c => c.GetAllArtists()).Returns(artistList);
|
||||
Mocker.GetMock<IImportListFactory>().Setup(c => c.All()).Returns(importLists);
|
||||
Mocker.GetMock<IRootFolderService>().Setup(c => c.All()).Returns(rootFolders);
|
||||
Mocker.GetMock<IMetadataProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile);
|
||||
|
||||
Assert.Throws<MetadataProfileInUseException>(() => Subject.Delete(profile.Id));
|
||||
|
@ -148,8 +155,14 @@ namespace NzbDrone.Core.Test.Profiles.Metadata
|
|||
.With(c => c.MetadataProfileId = profile.Id)
|
||||
.Build().ToList();
|
||||
|
||||
var rootFolders = Builder<RootFolder>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(f => f.DefaultMetadataProfileId = 1)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IArtistService>().Setup(c => c.GetAllArtists()).Returns(artistList);
|
||||
Mocker.GetMock<IImportListFactory>().Setup(c => c.All()).Returns(importLists);
|
||||
Mocker.GetMock<IRootFolderService>().Setup(c => c.All()).Returns(rootFolders);
|
||||
Mocker.GetMock<IMetadataProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile);
|
||||
|
||||
Assert.Throws<MetadataProfileInUseException>(() => Subject.Delete(profile.Id));
|
||||
|
@ -158,7 +171,39 @@ namespace NzbDrone.Core.Test.Profiles.Metadata
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_profile_if_not_assigned_to_artist_or_import_list()
|
||||
public void should_not_be_able_to_delete_profile_if_assigned_to_root_folder()
|
||||
{
|
||||
var profile = Builder<MetadataProfile>.CreateNew()
|
||||
.With(p => p.Id = 2)
|
||||
.Build();
|
||||
|
||||
var artistList = Builder<Artist>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(c => c.MetadataProfileId = 1)
|
||||
.Build().ToList();
|
||||
|
||||
var importLists = Builder<ImportListDefinition>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(c => c.MetadataProfileId = 1)
|
||||
.Build().ToList();
|
||||
|
||||
var rootFolders = Builder<RootFolder>.CreateListOfSize(2)
|
||||
.Random(1)
|
||||
.With(f => f.DefaultMetadataProfileId = profile.Id)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IArtistService>().Setup(c => c.GetAllArtists()).Returns(artistList);
|
||||
Mocker.GetMock<IImportListFactory>().Setup(c => c.All()).Returns(importLists);
|
||||
Mocker.GetMock<IRootFolderService>().Setup(c => c.All()).Returns(rootFolders);
|
||||
Mocker.GetMock<IMetadataProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile);
|
||||
|
||||
Assert.Throws<MetadataProfileInUseException>(() => Subject.Delete(profile.Id));
|
||||
|
||||
Mocker.GetMock<IMetadataProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_profile_if_not_assigned_to_artist_import_list_or_root_folder()
|
||||
{
|
||||
var profile = Builder<MetadataProfile>.CreateNew()
|
||||
.With(p => p.Id = 1)
|
||||
|
@ -174,8 +219,14 @@ namespace NzbDrone.Core.Test.Profiles.Metadata
|
|||
.With(c => c.MetadataProfileId = 2)
|
||||
.Build().ToList();
|
||||
|
||||
var rootFolders = Builder<RootFolder>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(f => f.DefaultMetadataProfileId = 2)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IArtistService>().Setup(c => c.GetAllArtists()).Returns(artistList);
|
||||
Mocker.GetMock<IImportListFactory>().Setup(c => c.All()).Returns(importLists);
|
||||
Mocker.GetMock<IRootFolderService>().Setup(c => c.All()).Returns(rootFolders);
|
||||
Mocker.GetMock<IMetadataProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile);
|
||||
|
||||
Subject.Delete(1);
|
||||
|
|
|
@ -6,6 +6,7 @@ using NzbDrone.Core.ImportLists;
|
|||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Profiles.Qualities;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Profiles
|
||||
|
@ -56,8 +57,14 @@ namespace NzbDrone.Core.Test.Profiles
|
|||
.With(c => c.ProfileId = 1)
|
||||
.Build().ToList();
|
||||
|
||||
var rootFolders = Builder<RootFolder>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(f => f.DefaultQualityProfileId = 1)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IArtistService>().Setup(c => c.GetAllArtists()).Returns(artistList);
|
||||
Mocker.GetMock<IImportListFactory>().Setup(c => c.All()).Returns(importLists);
|
||||
Mocker.GetMock<IRootFolderService>().Setup(c => c.All()).Returns(rootFolders);
|
||||
Mocker.GetMock<IProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile);
|
||||
|
||||
Assert.Throws<QualityProfileInUseException>(() => Subject.Delete(profile.Id));
|
||||
|
@ -82,8 +89,14 @@ namespace NzbDrone.Core.Test.Profiles
|
|||
.With(c => c.ProfileId = profile.Id)
|
||||
.Build().ToList();
|
||||
|
||||
var rootFolders = Builder<RootFolder>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(f => f.DefaultQualityProfileId = 1)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IArtistService>().Setup(c => c.GetAllArtists()).Returns(artistList);
|
||||
Mocker.GetMock<IImportListFactory>().Setup(c => c.All()).Returns(importLists);
|
||||
Mocker.GetMock<IRootFolderService>().Setup(c => c.All()).Returns(rootFolders);
|
||||
Mocker.GetMock<IProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile);
|
||||
|
||||
Assert.Throws<QualityProfileInUseException>(() => Subject.Delete(profile.Id));
|
||||
|
@ -92,7 +105,39 @@ namespace NzbDrone.Core.Test.Profiles
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_profile_if_not_assigned_to_artist_or_import_list()
|
||||
public void should_not_be_able_to_delete_profile_if_assigned_to_root_folder()
|
||||
{
|
||||
var profile = Builder<QualityProfile>.CreateNew()
|
||||
.With(p => p.Id = 2)
|
||||
.Build();
|
||||
|
||||
var artistList = Builder<Artist>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(c => c.QualityProfileId = 1)
|
||||
.Build().ToList();
|
||||
|
||||
var importLists = Builder<ImportListDefinition>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(c => c.ProfileId = 1)
|
||||
.Build().ToList();
|
||||
|
||||
var rootFolders = Builder<RootFolder>.CreateListOfSize(2)
|
||||
.Random(1)
|
||||
.With(f => f.DefaultQualityProfileId = profile.Id)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IArtistService>().Setup(c => c.GetAllArtists()).Returns(artistList);
|
||||
Mocker.GetMock<IImportListFactory>().Setup(c => c.All()).Returns(importLists);
|
||||
Mocker.GetMock<IRootFolderService>().Setup(c => c.All()).Returns(rootFolders);
|
||||
Mocker.GetMock<IProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile);
|
||||
|
||||
Assert.Throws<QualityProfileInUseException>(() => Subject.Delete(profile.Id));
|
||||
|
||||
Mocker.GetMock<IProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_profile_if_not_assigned_to_artist_import_list_or_root_folder()
|
||||
{
|
||||
var artistList = Builder<Artist>.CreateListOfSize(3)
|
||||
.All()
|
||||
|
@ -104,8 +149,14 @@ namespace NzbDrone.Core.Test.Profiles
|
|||
.With(c => c.ProfileId = 2)
|
||||
.Build().ToList();
|
||||
|
||||
var rootFolders = Builder<RootFolder>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(f => f.DefaultQualityProfileId = 2)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IArtistService>().Setup(c => c.GetAllArtists()).Returns(artistList);
|
||||
Mocker.GetMock<IImportListFactory>().Setup(c => c.All()).Returns(importLists);
|
||||
Mocker.GetMock<IRootFolderService>().Setup(c => c.All()).Returns(rootFolders);
|
||||
|
||||
Subject.Delete(1);
|
||||
|
||||
|
|
|
@ -93,49 +93,5 @@ namespace NzbDrone.Core.Test.RootFolderTests
|
|||
|
||||
Assert.Throws<UnauthorizedAccessException>(() => Subject.Add(new RootFolder { Path = @"C:\Music".AsOsAgnostic() }));
|
||||
}
|
||||
|
||||
[TestCase("$recycle.bin")]
|
||||
[TestCase("system volume information")]
|
||||
[TestCase("recycler")]
|
||||
[TestCase("lost+found")]
|
||||
[TestCase(".appledb")]
|
||||
[TestCase(".appledesktop")]
|
||||
[TestCase(".appledouble")]
|
||||
[TestCase("@eadir")]
|
||||
[TestCase(".grab")]
|
||||
public void should_get_root_folder_with_subfolders_excluding_special_sub_folders(string subFolder)
|
||||
{
|
||||
var rootFolderPath = @"C:\Test\Music".AsOsAgnostic();
|
||||
var rootFolder = Builder<RootFolder>.CreateNew()
|
||||
.With(r => r.Path = rootFolderPath)
|
||||
.Build();
|
||||
|
||||
var subFolders = new[]
|
||||
{
|
||||
"Artist1",
|
||||
"Artist2",
|
||||
"Artist3",
|
||||
subFolder
|
||||
};
|
||||
|
||||
var folders = subFolders.Select(f => Path.Combine(rootFolderPath, f)).ToArray();
|
||||
|
||||
Mocker.GetMock<IRootFolderRepository>()
|
||||
.Setup(s => s.Get(It.IsAny<int>()))
|
||||
.Returns(rootFolder);
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(s => s.GetAllArtists())
|
||||
.Returns(new List<Artist>());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetDirectories(rootFolder.Path))
|
||||
.Returns(folders);
|
||||
|
||||
var unmappedFolders = Subject.Get(rootFolder.Id).UnmappedFolders;
|
||||
|
||||
unmappedFolders.Count.Should().BeGreaterThan(0);
|
||||
unmappedFolders.Should().NotContain(u => u.Name == subFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(39)]
|
||||
public class add_root_folder_add_defaults : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("RootFolders").AddColumn("Name").AsString().Nullable();
|
||||
Alter.Table("RootFolders").AddColumn("DefaultMetadataProfileId").AsInt32().WithDefaultValue(0);
|
||||
Alter.Table("RootFolders").AddColumn("DefaultQualityProfileId").AsInt32().WithDefaultValue(0);
|
||||
Alter.Table("RootFolders").AddColumn("DefaultMonitorOption").AsInt32().WithDefaultValue(0);
|
||||
Alter.Table("RootFolders").AddColumn("DefaultTags").AsString().Nullable();
|
||||
|
||||
Execute.WithConnection(SetDefaultOptions);
|
||||
}
|
||||
|
||||
private void SetDefaultOptions(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
int metadataId = GetMinProfileId(conn, tran, "MetadataProfiles");
|
||||
int qualityId = GetMinProfileId(conn, tran, "QualityProfiles");
|
||||
|
||||
if (metadataId == 0 || qualityId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = $"SELECT Id, Path FROM RootFolders";
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var rootFolderId = reader.GetInt32(0);
|
||||
var path = reader.GetString(1);
|
||||
|
||||
using (var updateCmd = conn.CreateCommand())
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
updateCmd.CommandText = "UPDATE RootFolders SET Name = ?, DefaultMetadataProfileId = ?, DefaultQualityProfileId = ?, DefaultTags = ? WHERE Id = ?";
|
||||
updateCmd.AddParameter(path);
|
||||
updateCmd.AddParameter(metadataId);
|
||||
updateCmd.AddParameter(qualityId);
|
||||
updateCmd.AddParameter("[]");
|
||||
updateCmd.AddParameter(rootFolderId);
|
||||
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GetMinProfileId(IDbConnection conn, IDbTransaction tran, string table)
|
||||
{
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
|
||||
// A plain min(id) will return an empty row if table is empty which is a pain to deal with
|
||||
cmd.CommandText = $"SELECT COALESCE(MIN(Id), 0) FROM {table}";
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
return reader.GetInt32(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
|||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.DiskSpace
|
||||
{
|
||||
|
@ -16,22 +16,24 @@ namespace NzbDrone.Core.DiskSpace
|
|||
|
||||
public class DiskSpaceService : IDiskSpaceService
|
||||
{
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled);
|
||||
|
||||
public DiskSpaceService(IArtistService artistService, IDiskProvider diskProvider, Logger logger)
|
||||
public DiskSpaceService(IDiskProvider diskProvider,
|
||||
IRootFolderService rootFolderService,
|
||||
Logger logger)
|
||||
{
|
||||
_artistService = artistService;
|
||||
_diskProvider = diskProvider;
|
||||
_rootFolderService = rootFolderService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<DiskSpace> GetFreeSpace()
|
||||
{
|
||||
var importantRootFolders = GetArtistRootPaths().Distinct().ToList();
|
||||
var importantRootFolders = _rootFolderService.All().Select(x => x.Path).ToList();
|
||||
|
||||
var optionalRootFolders = GetFixedDisksRootPaths().Except(importantRootFolders).Distinct().ToList();
|
||||
|
||||
|
@ -40,14 +42,6 @@ namespace NzbDrone.Core.DiskSpace
|
|||
return diskSpace;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetArtistRootPaths()
|
||||
{
|
||||
return _artistService.GetAllArtists()
|
||||
.Where(s => _diskProvider.FolderExists(s.Path))
|
||||
.Select(s => _diskProvider.GetPathRoot(s.Path))
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetFixedDisksRootPaths()
|
||||
{
|
||||
return _diskProvider.GetMounts()
|
||||
|
|
|
@ -313,7 +313,6 @@ namespace NzbDrone.Core.History
|
|||
public void Handle(TrackFileRenamedEvent message)
|
||||
{
|
||||
var sourcePath = message.OriginalPath;
|
||||
var sourceRelativePath = message.Artist.Path.GetRelativePath(message.OriginalPath);
|
||||
var path = message.TrackFile.Path;
|
||||
|
||||
foreach (var track in message.TrackFile.Tracks.Value)
|
||||
|
@ -330,7 +329,6 @@ namespace NzbDrone.Core.History
|
|||
};
|
||||
|
||||
history.Data.Add("SourcePath", sourcePath);
|
||||
history.Data.Add("SourceRelativePath", sourceRelativePath);
|
||||
history.Data.Add("Path", path);
|
||||
|
||||
_historyRepository.Insert(history);
|
||||
|
|
|
@ -26,6 +26,12 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
|||
{
|
||||
Ensure.That(title, () => title).IsNotNullOrWhiteSpace();
|
||||
|
||||
// Most VA albums are listed as VA, not Various Artists
|
||||
if (title == "Various Artists")
|
||||
{
|
||||
title = "VA";
|
||||
}
|
||||
|
||||
var cleanTitle = BeginningThe.Replace(title, string.Empty);
|
||||
|
||||
cleanTitle = cleanTitle.Replace(" & ", " ");
|
||||
|
|
|
@ -355,7 +355,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
AlbumId = file.Album.Value.Id,
|
||||
TrackNumbers = file.Tracks.Value.Select(e => e.AbsoluteTrackNumber).ToList(),
|
||||
TrackFileId = file.Id,
|
||||
RelativePath = file.Artist.Value.Path.GetRelativePath(file.Path),
|
||||
Path = file.Path,
|
||||
Changes = diff
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Commands
|
||||
{
|
||||
public class RescanArtistCommand : Command
|
||||
{
|
||||
public int? ArtistId { get; set; }
|
||||
public FilterFilesType Filter { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
|
||||
public RescanArtistCommand(FilterFilesType filter = FilterFilesType.Known)
|
||||
{
|
||||
Filter = filter;
|
||||
}
|
||||
|
||||
public RescanArtistCommand(int artistId, FilterFilesType filter = FilterFilesType.Known)
|
||||
{
|
||||
ArtistId = artistId;
|
||||
Filter = filter;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Commands
|
||||
{
|
||||
public class RescanFoldersCommand : Command
|
||||
{
|
||||
public RescanFoldersCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public RescanFoldersCommand(List<string> folders, FilterFilesType filter, List<int> artistIds)
|
||||
{
|
||||
Folders = folders;
|
||||
Filter = filter;
|
||||
ArtistIds = artistIds;
|
||||
}
|
||||
|
||||
public List<string> Folders { get; set; }
|
||||
public FilterFilesType Filter { get; set; }
|
||||
public List<int> ArtistIds { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
public override bool RequiresDiskAccess => true;
|
||||
}
|
||||
}
|
|
@ -10,20 +10,20 @@ using NzbDrone.Common;
|
|||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public interface IDiskScanService
|
||||
{
|
||||
void Scan(Artist artist, FilterFilesType filter = FilterFilesType.Known);
|
||||
void Scan(List<string> folders = null, FilterFilesType filter = FilterFilesType.Known, List<int> artistIds = null);
|
||||
IFileInfo[] GetAudioFiles(string path, bool allDirectories = true);
|
||||
string[] GetNonAudioFiles(string path, bool allDirectories = true);
|
||||
List<IFileInfo> FilterFiles(string basePath, IEnumerable<IFileInfo> files);
|
||||
|
@ -32,13 +32,12 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
public class DiskScanService :
|
||||
IDiskScanService,
|
||||
IExecute<RescanArtistCommand>
|
||||
IExecute<RescanFoldersCommand>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly IImportApprovedTracks _importApprovedTracks;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
|
@ -49,7 +48,6 @@ namespace NzbDrone.Core.MediaFiles
|
|||
IMediaFileService mediaFileService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
IImportApprovedTracks importApprovedTracks,
|
||||
IConfigService configService,
|
||||
IArtistService artistService,
|
||||
IRootFolderService rootFolderService,
|
||||
IMediaFileTableCleanupService mediaFileTableCleanupService,
|
||||
|
@ -60,7 +58,6 @@ namespace NzbDrone.Core.MediaFiles
|
|||
_mediaFileService = mediaFileService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_importApprovedTracks = importApprovedTracks;
|
||||
_configService = configService;
|
||||
_artistService = artistService;
|
||||
_mediaFileTableCleanupService = mediaFileTableCleanupService;
|
||||
_rootFolderService = rootFolderService;
|
||||
|
@ -71,56 +68,90 @@ namespace NzbDrone.Core.MediaFiles
|
|||
private static readonly Regex ExcludedSubFoldersRegex = new Regex(@"(?:\\|\/|^)(?:extras|@eadir|extrafanart|plex versions|\.[^\\/]+)(?:\\|\/)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex ExcludedFilesRegex = new Regex(@"^\._|^Thumbs\.db$|^\.DS_store$|\.partial~$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public void Scan(Artist artist, FilterFilesType filter = FilterFilesType.Known)
|
||||
public void Scan(List<string> folders = null, FilterFilesType filter = FilterFilesType.Known, List<int> artistIds = null)
|
||||
{
|
||||
var rootFolder = _rootFolderService.GetBestRootFolderPath(artist.Path);
|
||||
|
||||
if (!_diskProvider.FolderExists(rootFolder))
|
||||
if (folders == null)
|
||||
{
|
||||
_logger.Warn("Artist' root folder ({0}) doesn't exist.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new ArtistScanSkippedEvent(artist, ArtistScanSkippedReason.RootFolderDoesNotExist));
|
||||
return;
|
||||
folders = _rootFolderService.All().Select(x => x.Path).ToList();
|
||||
}
|
||||
|
||||
if (_diskProvider.GetDirectories(rootFolder).Empty())
|
||||
if (artistIds == null)
|
||||
{
|
||||
_logger.Warn("Artist' root folder ({0}) is empty.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new ArtistScanSkippedEvent(artist, ArtistScanSkippedReason.RootFolderIsEmpty));
|
||||
return;
|
||||
artistIds = new List<int>();
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Scanning {0}", artist.Name);
|
||||
|
||||
if (!_diskProvider.FolderExists(artist.Path))
|
||||
{
|
||||
if (_configService.CreateEmptyArtistFolders)
|
||||
{
|
||||
_logger.Debug("Creating missing artist folder: {0}", artist.Path);
|
||||
_diskProvider.CreateFolder(artist.Path);
|
||||
SetPermissions(artist.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Artist folder doesn't exist: {0}", artist.Path);
|
||||
}
|
||||
|
||||
CleanMediaFiles(artist, new List<string>());
|
||||
CompletedScanning(artist);
|
||||
|
||||
return;
|
||||
}
|
||||
var mediaFileList = new List<IFileInfo>();
|
||||
|
||||
var musicFilesStopwatch = Stopwatch.StartNew();
|
||||
var mediaFileList = FilterFiles(artist.Path, GetAudioFiles(artist.Path)).ToList();
|
||||
musicFilesStopwatch.Stop();
|
||||
_logger.Trace("Finished getting track files for: {0} [{1}]", artist, musicFilesStopwatch.Elapsed);
|
||||
|
||||
CleanMediaFiles(artist, mediaFileList.Select(x => x.FullName).ToList());
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
// We could be scanning a root folder or a subset of a root folder. If it's a subset,
|
||||
// check if the root folder exists before cleaning.
|
||||
var rootFolder = _rootFolderService.GetBestRootFolder(folder);
|
||||
|
||||
if (rootFolder == null)
|
||||
{
|
||||
_logger.Error("Not scanning {0}, it's not a subdirectory of a defined root folder", folder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderExists(rootFolder.Path))
|
||||
{
|
||||
_logger.Warn("Root folder {0} doesn't exist.", rootFolder.Path);
|
||||
|
||||
var skippedArtists = _artistService.GetArtists(artistIds);
|
||||
skippedArtists.ForEach(x => _eventAggregator.PublishEvent(new ArtistScanSkippedEvent(x, ArtistScanSkippedReason.RootFolderDoesNotExist)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_diskProvider.GetDirectories(rootFolder.Path).Empty())
|
||||
{
|
||||
_logger.Warn("Root folder {0} is empty.", rootFolder.Path);
|
||||
|
||||
var skippedArtists = _artistService.GetArtists(artistIds);
|
||||
skippedArtists.ForEach(x => _eventAggregator.PublishEvent(new ArtistScanSkippedEvent(x, ArtistScanSkippedReason.RootFolderIsEmpty)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderExists(folder))
|
||||
{
|
||||
_logger.Debug("Specified scan folder ({0}) doesn't exist.", folder);
|
||||
|
||||
CleanMediaFiles(folder, new List<string>());
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Scanning {0}", folder);
|
||||
|
||||
var files = FilterFiles(folder, GetAudioFiles(folder));
|
||||
|
||||
if (!files.Any())
|
||||
{
|
||||
_logger.Warn("Scan folder {0} is empty.", folder);
|
||||
continue;
|
||||
}
|
||||
|
||||
CleanMediaFiles(folder, files.Select(x => x.FullName).ToList());
|
||||
mediaFileList.AddRange(files);
|
||||
}
|
||||
|
||||
musicFilesStopwatch.Stop();
|
||||
_logger.Trace("Finished getting track files for:\n{0} [{1}]", folders.ConcatToString("\n"), musicFilesStopwatch.Elapsed);
|
||||
|
||||
var decisionsStopwatch = Stopwatch.StartNew();
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, artist, filter, true);
|
||||
|
||||
var config = new ImportDecisionMakerConfig
|
||||
{
|
||||
Filter = filter,
|
||||
IncludeExisting = true,
|
||||
AddNewArtists = true
|
||||
};
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, null, null, config);
|
||||
|
||||
decisionsStopwatch.Stop();
|
||||
_logger.Debug("Import decisions complete for: {0} [{1}]", artist, decisionsStopwatch.Elapsed);
|
||||
_logger.Debug("Import decisions complete [{0}]", decisionsStopwatch.Elapsed);
|
||||
|
||||
var importStopwatch = Stopwatch.StartNew();
|
||||
_importApprovedTracks.Import(decisions, false);
|
||||
|
@ -128,7 +159,8 @@ namespace NzbDrone.Core.MediaFiles
|
|||
// decisions may have been filtered to just new files. Anything new and approved will have been inserted.
|
||||
// Now we need to make sure anything new but not approved gets inserted
|
||||
// Note that knownFiles will include anything imported just now
|
||||
var knownFiles = _mediaFileService.GetFilesWithBasePath(artist.Path);
|
||||
var knownFiles = new List<TrackFile>();
|
||||
folders.ForEach(x => knownFiles.AddRange(_mediaFileService.GetFilesWithBasePath(x)));
|
||||
|
||||
var newFiles = decisions
|
||||
.ExceptBy(x => x.Item.Path, knownFiles, x => x.Path, PathEqualityComparer.Instance)
|
||||
|
@ -173,17 +205,20 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
_logger.Debug($"Updated info for {updatedFiles.Count} known files");
|
||||
|
||||
RemoveEmptyArtistFolder(artist.Path);
|
||||
var artists = _artistService.GetArtists(artistIds);
|
||||
foreach (var artist in artists)
|
||||
{
|
||||
CompletedScanning(artist);
|
||||
}
|
||||
|
||||
CompletedScanning(artist);
|
||||
importStopwatch.Stop();
|
||||
_logger.Debug("Track import complete for: {0} [{1}]", artist, importStopwatch.Elapsed);
|
||||
_logger.Debug("Track import complete for:\n{0} [{1}]", folders.ConcatToString("\n"), importStopwatch.Elapsed);
|
||||
}
|
||||
|
||||
private void CleanMediaFiles(Artist artist, List<string> mediaFileList)
|
||||
private void CleanMediaFiles(string folder, List<string> mediaFileList)
|
||||
{
|
||||
_logger.Debug("{0} Cleaning up media files in DB", artist);
|
||||
_mediaFileTableCleanupService.Clean(artist, mediaFileList);
|
||||
_logger.Debug($"Cleaning up media files in DB [{folder}]");
|
||||
_mediaFileTableCleanupService.Clean(folder, mediaFileList);
|
||||
}
|
||||
|
||||
private void CompletedScanning(Artist artist)
|
||||
|
@ -238,56 +273,9 @@ namespace NzbDrone.Core.MediaFiles
|
|||
.ToList();
|
||||
}
|
||||
|
||||
private void SetPermissions(string path)
|
||||
public void Execute(RescanFoldersCommand message)
|
||||
{
|
||||
if (!_configService.SetPermissionsLinux)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var permissions = _configService.FolderChmod;
|
||||
_diskProvider.SetPermissions(path, permissions, _configService.ChownUser, _configService.ChownGroup);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Unable to apply permissions to: " + path);
|
||||
_logger.Debug(ex, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveEmptyArtistFolder(string path)
|
||||
{
|
||||
if (_configService.DeleteEmptyFolders)
|
||||
{
|
||||
if (_diskProvider.GetFiles(path, SearchOption.AllDirectories).Empty())
|
||||
{
|
||||
_diskProvider.DeleteFolder(path, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_diskProvider.RemoveEmptySubfolders(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(RescanArtistCommand message)
|
||||
{
|
||||
if (message.ArtistId.HasValue)
|
||||
{
|
||||
var artist = _artistService.GetArtist(message.ArtistId.Value);
|
||||
Scan(artist, message.Filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
var allArtists = _artistService.GetAllArtists();
|
||||
|
||||
foreach (var artist in allArtists)
|
||||
{
|
||||
Scan(artist, message.Filter);
|
||||
}
|
||||
}
|
||||
Scan(message.Folders, message.Filter, message.ArtistIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,7 +200,25 @@ namespace NzbDrone.Core.MediaFiles
|
|||
}
|
||||
}
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(audioFiles, artist, downloadClientItem, trackInfo);
|
||||
var idOverrides = new IdentificationOverrides
|
||||
{
|
||||
Artist = artist
|
||||
};
|
||||
var idInfo = new ImportDecisionMakerInfo
|
||||
{
|
||||
DownloadClientItem = downloadClientItem,
|
||||
ParsedTrackInfo = trackInfo
|
||||
};
|
||||
var idConfig = new ImportDecisionMakerConfig
|
||||
{
|
||||
Filter = FilterFilesType.None,
|
||||
NewDownload = true,
|
||||
SingleRelease = false,
|
||||
IncludeExisting = false,
|
||||
AddNewArtists = false
|
||||
};
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(audioFiles, idOverrides, idInfo, idConfig);
|
||||
var importResults = _importApprovedTracks.Import(decisions, true, downloadClientItem, importMode);
|
||||
|
||||
if (importMode == ImportMode.Auto)
|
||||
|
@ -259,7 +277,24 @@ namespace NzbDrone.Core.MediaFiles
|
|||
}
|
||||
}
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(new List<IFileInfo>() { fileInfo }, artist, downloadClientItem, null);
|
||||
var idOverrides = new IdentificationOverrides
|
||||
{
|
||||
Artist = artist
|
||||
};
|
||||
var idInfo = new ImportDecisionMakerInfo
|
||||
{
|
||||
DownloadClientItem = downloadClientItem
|
||||
};
|
||||
var idConfig = new ImportDecisionMakerConfig
|
||||
{
|
||||
Filter = FilterFilesType.None,
|
||||
NewDownload = true,
|
||||
SingleRelease = false,
|
||||
IncludeExisting = false,
|
||||
AddNewArtists = false
|
||||
};
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(new List<IFileInfo>() { fileInfo }, idOverrides, idInfo, idConfig);
|
||||
|
||||
return _importApprovedTracks.Import(decisions, true, downloadClientItem, importMode);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
|
@ -15,6 +17,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
List<TrackFile> GetFilesByRelease(int releaseId);
|
||||
List<TrackFile> GetUnmappedFiles();
|
||||
List<TrackFile> GetFilesWithBasePath(string path);
|
||||
List<TrackFile> GetFileWithPath(List<string> paths);
|
||||
TrackFile GetFileWithPath(string path);
|
||||
void DeleteFilesByAlbum(int albumId);
|
||||
void UnlinkFilesByAlbum(int albumId);
|
||||
|
@ -88,7 +91,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
{
|
||||
// ensure path ends with a single trailing path separator to avoid matching partial paths
|
||||
var safePath = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
return Query
|
||||
return DataMapper.Query<TrackFile>()
|
||||
.Where(x => x.Path.StartsWith(safePath))
|
||||
.ToList();
|
||||
}
|
||||
|
@ -97,5 +100,15 @@ namespace NzbDrone.Core.MediaFiles
|
|||
{
|
||||
return Query.Where(x => x.Path == path).SingleOrDefault();
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFileWithPath(List<string> paths)
|
||||
{
|
||||
// use more limited join for speed
|
||||
var all = DataMapper.Query<TrackFile>()
|
||||
.Join<TrackFile, Track>(JoinType.Left, t => t.Tracks, (t, x) => t.Id == x.TrackFileId)
|
||||
.ToList();
|
||||
var joined = all.Join(paths, x => x.Path, x => x, (file, path) => file, PathEqualityComparer.Instance).ToList();
|
||||
return joined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ using System.Linq;
|
|||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
|
@ -24,15 +25,18 @@ namespace NzbDrone.Core.MediaFiles
|
|||
List<TrackFile> GetFilesByAlbum(int albumId);
|
||||
List<TrackFile> GetFilesByRelease(int releaseId);
|
||||
List<TrackFile> GetUnmappedFiles();
|
||||
List<IFileInfo> FilterUnchangedFiles(List<IFileInfo> files, Artist artist, FilterFilesType filter);
|
||||
List<IFileInfo> FilterUnchangedFiles(List<IFileInfo> files, FilterFilesType filter);
|
||||
TrackFile Get(int id);
|
||||
List<TrackFile> Get(IEnumerable<int> ids);
|
||||
List<TrackFile> GetFilesWithBasePath(string path);
|
||||
List<TrackFile> GetFileWithPath(List<string> path);
|
||||
TrackFile GetFileWithPath(string path);
|
||||
void UpdateMediaInfo(List<TrackFile> trackFiles);
|
||||
}
|
||||
|
||||
public class MediaFileService : IMediaFileService, IHandleAsync<AlbumDeletedEvent>
|
||||
public class MediaFileService : IMediaFileService,
|
||||
IHandleAsync<AlbumDeletedEvent>,
|
||||
IHandleAsync<ModelEvent<RootFolder>>
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMediaFileRepository _mediaFileRepository;
|
||||
|
@ -93,11 +97,16 @@ namespace NzbDrone.Core.MediaFiles
|
|||
}
|
||||
}
|
||||
|
||||
public List<IFileInfo> FilterUnchangedFiles(List<IFileInfo> files, Artist artist, FilterFilesType filter)
|
||||
public List<IFileInfo> FilterUnchangedFiles(List<IFileInfo> files, FilterFilesType filter)
|
||||
{
|
||||
if (filter == FilterFilesType.None)
|
||||
{
|
||||
return files;
|
||||
}
|
||||
|
||||
_logger.Debug($"Filtering {files.Count} files for unchanged files");
|
||||
|
||||
var knownFiles = GetFilesWithBasePath(artist.Path);
|
||||
var knownFiles = GetFileWithPath(files.Select(x => x.FullName).ToList());
|
||||
_logger.Trace($"Got {knownFiles.Count} existing files");
|
||||
|
||||
if (!knownFiles.Any())
|
||||
|
@ -156,21 +165,14 @@ namespace NzbDrone.Core.MediaFiles
|
|||
return _mediaFileRepository.GetFilesWithBasePath(path);
|
||||
}
|
||||
|
||||
public TrackFile GetFileWithPath(string path)
|
||||
public List<TrackFile> GetFileWithPath(List<string> path)
|
||||
{
|
||||
return _mediaFileRepository.GetFileWithPath(path);
|
||||
}
|
||||
|
||||
public void HandleAsync(AlbumDeletedEvent message)
|
||||
public TrackFile GetFileWithPath(string path)
|
||||
{
|
||||
if (message.DeleteFiles)
|
||||
{
|
||||
_mediaFileRepository.DeleteFilesByAlbum(message.Album.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mediaFileRepository.UnlinkFilesByAlbum(message.Album.Id);
|
||||
}
|
||||
return _mediaFileRepository.GetFileWithPath(path);
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesByArtist(int artistId)
|
||||
|
@ -197,5 +199,26 @@ namespace NzbDrone.Core.MediaFiles
|
|||
{
|
||||
_mediaFileRepository.SetFields(trackFiles, t => t.MediaInfo);
|
||||
}
|
||||
|
||||
public void HandleAsync(AlbumDeletedEvent message)
|
||||
{
|
||||
if (message.DeleteFiles)
|
||||
{
|
||||
_mediaFileRepository.DeleteFilesByAlbum(message.Album.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mediaFileRepository.UnlinkFilesByAlbum(message.Album.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleAsync(ModelEvent<RootFolder> message)
|
||||
{
|
||||
if (message.Action == ModelAction.Deleted)
|
||||
{
|
||||
var files = GetFilesWithBasePath(message.Model.Path);
|
||||
DeleteMany(files, DeleteMediaFileReason.Manual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
{
|
||||
public interface IMediaFileTableCleanupService
|
||||
{
|
||||
void Clean(Artist artist, List<string> filesOnDisk);
|
||||
void Clean(string folder, List<string> filesOnDisk);
|
||||
}
|
||||
|
||||
public class MediaFileTableCleanupService : IMediaFileTableCleanupService
|
||||
|
@ -27,9 +27,9 @@ namespace NzbDrone.Core.MediaFiles
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Clean(Artist artist, List<string> filesOnDisk)
|
||||
public void Clean(string folder, List<string> filesOnDisk)
|
||||
{
|
||||
var dbFiles = _mediaFileService.GetFilesWithBasePath(artist.Path);
|
||||
var dbFiles = _mediaFileService.GetFilesWithBasePath(folder);
|
||||
|
||||
// get files in database that are missing on disk and remove from database
|
||||
var missingFiles = dbFiles.ExceptBy(x => x.Path, filesOnDisk, x => x, PathEqualityComparer.Instance).ToList();
|
||||
|
|
|
@ -103,8 +103,8 @@ namespace NzbDrone.Core.MediaFiles
|
|||
AlbumId = album.Id,
|
||||
TrackNumbers = tracksInFile.Select(e => e.AbsoluteTrackNumber).ToList(),
|
||||
TrackFileId = file.Id,
|
||||
ExistingPath = artist.Path.GetRelativePath(file.Path),
|
||||
NewPath = artist.Path.GetRelativePath(newPath)
|
||||
ExistingPath = file.Path,
|
||||
NewPath = newPath
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
public int AlbumId { get; set; }
|
||||
public List<int> TrackNumbers { get; set; }
|
||||
public int TrackFileId { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string Path { get; set; }
|
||||
public Dictionary<string, Tuple<string, string>> Changes { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.MetadataSource.SkyHook;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
public interface ICandidateService
|
||||
{
|
||||
List<CandidateAlbumRelease> GetDbCandidatesFromTags(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, bool includeExisting);
|
||||
List<CandidateAlbumRelease> GetDbCandidatesFromFingerprint(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, bool includeExisting);
|
||||
List<CandidateAlbumRelease> GetRemoteCandidates(LocalAlbumRelease localAlbumRelease);
|
||||
}
|
||||
|
||||
public class CandidateService : ICandidateService
|
||||
{
|
||||
private readonly ISearchForNewAlbum _albumSearchService;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IReleaseService _releaseService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public CandidateService(ISearchForNewAlbum albumSearchService,
|
||||
IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
IReleaseService releaseService,
|
||||
IMediaFileService mediaFileService,
|
||||
Logger logger)
|
||||
{
|
||||
_albumSearchService = albumSearchService;
|
||||
_artistService = artistService;
|
||||
_albumService = albumService;
|
||||
_releaseService = releaseService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<CandidateAlbumRelease> GetDbCandidatesFromTags(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, bool includeExisting)
|
||||
{
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
// Generally artist, album and release are null. But if they're not then limit candidates appropriately.
|
||||
// We've tried to make sure that tracks are all for a single release.
|
||||
List<CandidateAlbumRelease> candidateReleases;
|
||||
|
||||
// if we have a release ID, use that
|
||||
AlbumRelease tagMbidRelease = null;
|
||||
List<CandidateAlbumRelease> tagCandidate = null;
|
||||
|
||||
var releaseIds = localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.ReleaseMBId).Distinct().ToList();
|
||||
if (releaseIds.Count == 1 && releaseIds[0].IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Debug("Selecting release from consensus ForeignReleaseId [{0}]", releaseIds[0]);
|
||||
tagMbidRelease = _releaseService.GetReleaseByForeignReleaseId(releaseIds[0], true);
|
||||
|
||||
if (tagMbidRelease != null)
|
||||
{
|
||||
tagCandidate = GetDbCandidatesByRelease(new List<AlbumRelease> { tagMbidRelease }, includeExisting);
|
||||
}
|
||||
}
|
||||
|
||||
if (idOverrides?.AlbumRelease != null)
|
||||
{
|
||||
// this case overrides the release picked up from the file tags
|
||||
var release = idOverrides.AlbumRelease;
|
||||
_logger.Debug("Release {0} [{1} tracks] was forced", release, release.TrackCount);
|
||||
candidateReleases = GetDbCandidatesByRelease(new List<AlbumRelease> { release }, includeExisting);
|
||||
}
|
||||
else if (idOverrides?.Album != null)
|
||||
{
|
||||
// use the release from file tags if it exists and agrees with the specified album
|
||||
if (tagMbidRelease?.AlbumId == idOverrides.Album.Id)
|
||||
{
|
||||
candidateReleases = tagCandidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateReleases = GetDbCandidatesByAlbum(localAlbumRelease, idOverrides.Album, includeExisting);
|
||||
}
|
||||
}
|
||||
else if (idOverrides?.Artist != null)
|
||||
{
|
||||
// use the release from file tags if it exists and agrees with the specified album
|
||||
if (tagMbidRelease?.Album.Value.ArtistMetadataId == idOverrides.Artist.ArtistMetadataId)
|
||||
{
|
||||
candidateReleases = tagCandidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateReleases = GetDbCandidatesByArtist(localAlbumRelease, idOverrides.Artist, includeExisting);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tagMbidRelease != null)
|
||||
{
|
||||
candidateReleases = tagCandidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateReleases = GetDbCandidates(localAlbumRelease, includeExisting);
|
||||
}
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
_logger.Debug($"Getting candidates from tags for {localAlbumRelease.LocalTracks.Count} tracks took {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
// if we haven't got any candidates then try fingerprinting
|
||||
return candidateReleases;
|
||||
}
|
||||
|
||||
private List<CandidateAlbumRelease> GetDbCandidatesByRelease(List<AlbumRelease> releases, bool includeExisting)
|
||||
{
|
||||
// get the local tracks on disk for each album
|
||||
var albumTracks = releases.Select(x => x.AlbumId)
|
||||
.Distinct()
|
||||
.ToDictionary(id => id, id => includeExisting ? _mediaFileService.GetFilesByAlbum(id) : new List<TrackFile>());
|
||||
|
||||
return releases.Select(x => new CandidateAlbumRelease
|
||||
{
|
||||
AlbumRelease = x,
|
||||
ExistingTracks = albumTracks[x.AlbumId]
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private List<CandidateAlbumRelease> GetDbCandidatesByAlbum(LocalAlbumRelease localAlbumRelease, Album album, bool includeExisting)
|
||||
{
|
||||
// sort candidate releases by closest track count so that we stand a chance of
|
||||
// getting a perfect match early on
|
||||
return GetDbCandidatesByRelease(_releaseService.GetReleasesByAlbum(album.Id)
|
||||
.OrderBy(x => Math.Abs(localAlbumRelease.TrackCount - x.TrackCount))
|
||||
.ToList(), includeExisting);
|
||||
}
|
||||
|
||||
private List<CandidateAlbumRelease> GetDbCandidatesByArtist(LocalAlbumRelease localAlbumRelease, Artist artist, bool includeExisting)
|
||||
{
|
||||
_logger.Trace("Getting candidates for {0}", artist);
|
||||
var candidateReleases = new List<CandidateAlbumRelease>();
|
||||
|
||||
var albumTag = localAlbumRelease.LocalTracks.MostCommon(x => x.FileTrackInfo.AlbumTitle) ?? "";
|
||||
if (albumTag.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var possibleAlbums = _albumService.GetCandidates(artist.ArtistMetadataId, albumTag);
|
||||
foreach (var album in possibleAlbums)
|
||||
{
|
||||
candidateReleases.AddRange(GetDbCandidatesByAlbum(localAlbumRelease, album, includeExisting));
|
||||
}
|
||||
}
|
||||
|
||||
return candidateReleases;
|
||||
}
|
||||
|
||||
private List<CandidateAlbumRelease> GetDbCandidates(LocalAlbumRelease localAlbumRelease, bool includeExisting)
|
||||
{
|
||||
// most general version, nothing has been specified.
|
||||
// get all plausible artists, then all plausible albums, then get releases for each of these.
|
||||
var candidateReleases = new List<CandidateAlbumRelease>();
|
||||
|
||||
// check if it looks like VA.
|
||||
if (TrackGroupingService.IsVariousArtists(localAlbumRelease.LocalTracks))
|
||||
{
|
||||
var va = _artistService.FindById(DistanceCalculator.VariousArtistIds[0]);
|
||||
if (va != null)
|
||||
{
|
||||
candidateReleases.AddRange(GetDbCandidatesByArtist(localAlbumRelease, va, includeExisting));
|
||||
}
|
||||
}
|
||||
|
||||
var artistTag = localAlbumRelease.LocalTracks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? "";
|
||||
if (artistTag.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var possibleArtists = _artistService.GetCandidates(artistTag);
|
||||
foreach (var artist in possibleArtists)
|
||||
{
|
||||
candidateReleases.AddRange(GetDbCandidatesByArtist(localAlbumRelease, artist, includeExisting));
|
||||
}
|
||||
}
|
||||
|
||||
return candidateReleases;
|
||||
}
|
||||
|
||||
public List<CandidateAlbumRelease> GetDbCandidatesFromFingerprint(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, bool includeExisting)
|
||||
{
|
||||
var recordingIds = localAlbumRelease.LocalTracks.Where(x => x.AcoustIdResults != null).SelectMany(x => x.AcoustIdResults).ToList();
|
||||
var allReleases = _releaseService.GetReleasesByRecordingIds(recordingIds);
|
||||
|
||||
// make sure releases are consistent with those selected by the user
|
||||
if (idOverrides?.AlbumRelease != null)
|
||||
{
|
||||
allReleases = allReleases.Where(x => x.Id == idOverrides.AlbumRelease.Id).ToList();
|
||||
}
|
||||
else if (idOverrides?.Album != null)
|
||||
{
|
||||
allReleases = allReleases.Where(x => x.AlbumId == idOverrides.Album.Id).ToList();
|
||||
}
|
||||
else if (idOverrides?.Artist != null)
|
||||
{
|
||||
allReleases = allReleases.Where(x => x.Album.Value.ArtistMetadataId == idOverrides.Artist.ArtistMetadataId).ToList();
|
||||
}
|
||||
|
||||
return GetDbCandidatesByRelease(allReleases.Select(x => new
|
||||
{
|
||||
Release = x,
|
||||
TrackCount = x.TrackCount,
|
||||
CommonProportion = x.Tracks.Value.Select(y => y.ForeignRecordingId).Intersect(recordingIds).Count() / localAlbumRelease.TrackCount
|
||||
})
|
||||
.Where(x => x.CommonProportion > 0.6)
|
||||
.ToList()
|
||||
.OrderBy(x => Math.Abs(x.TrackCount - localAlbumRelease.TrackCount))
|
||||
.ThenByDescending(x => x.CommonProportion)
|
||||
.Select(x => x.Release)
|
||||
.Take(10)
|
||||
.ToList(), includeExisting);
|
||||
}
|
||||
|
||||
public List<CandidateAlbumRelease> GetRemoteCandidates(LocalAlbumRelease localAlbumRelease)
|
||||
{
|
||||
// Gets candidate album releases from the metadata server.
|
||||
// Will eventually need adding locally if we find a match
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
List<Album> remoteAlbums;
|
||||
var candidates = new List<CandidateAlbumRelease>();
|
||||
|
||||
var albumIds = localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.AlbumMBId).Distinct().ToList();
|
||||
var recordingIds = localAlbumRelease.LocalTracks.Where(x => x.AcoustIdResults != null).SelectMany(x => x.AcoustIdResults).Distinct().ToList();
|
||||
|
||||
try
|
||||
{
|
||||
if (albumIds.Count == 1 && albumIds[0].IsNotNullOrWhiteSpace())
|
||||
{
|
||||
// Use mbids in tags if set
|
||||
remoteAlbums = _albumSearchService.SearchForNewAlbum($"mbid:{albumIds[0]}", null);
|
||||
}
|
||||
else if (recordingIds.Any())
|
||||
{
|
||||
// If fingerprints present use those
|
||||
remoteAlbums = _albumSearchService.SearchForNewAlbumByRecordingIds(recordingIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fall back to artist / album name search
|
||||
string artistTag;
|
||||
|
||||
if (TrackGroupingService.IsVariousArtists(localAlbumRelease.LocalTracks))
|
||||
{
|
||||
artistTag = "Various Artists";
|
||||
}
|
||||
else
|
||||
{
|
||||
artistTag = localAlbumRelease.LocalTracks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? "";
|
||||
}
|
||||
|
||||
var albumTag = localAlbumRelease.LocalTracks.MostCommon(x => x.FileTrackInfo.AlbumTitle) ?? "";
|
||||
|
||||
if (artistTag.IsNullOrWhiteSpace() || albumTag.IsNullOrWhiteSpace())
|
||||
{
|
||||
return candidates;
|
||||
}
|
||||
|
||||
remoteAlbums = _albumSearchService.SearchForNewAlbum(albumTag, artistTag);
|
||||
}
|
||||
}
|
||||
catch (SkyHookException e)
|
||||
{
|
||||
_logger.Info(e, "Skipping album due to SkyHook error");
|
||||
remoteAlbums = new List<Album>();
|
||||
}
|
||||
|
||||
foreach (var album in remoteAlbums)
|
||||
{
|
||||
// We have to make sure various bits and pieces are populated that are normally handled
|
||||
// by a database lazy load
|
||||
foreach (var release in album.AlbumReleases.Value)
|
||||
{
|
||||
release.Album = album;
|
||||
candidates.Add(new CandidateAlbumRelease
|
||||
{
|
||||
AlbumRelease = release,
|
||||
ExistingTracks = new List<TrackFile>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
_logger.Debug($"Getting {candidates.Count} remote candidates from tags for {localAlbumRelease.LocalTracks.Count} tracks took {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
public static class DistanceCalculator
|
||||
{
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DistanceCalculator));
|
||||
|
||||
public static readonly List<string> VariousArtistIds = new List<string> { "89ad4ac3-39f7-470e-963a-56509c546377" };
|
||||
private static readonly List<string> VariousArtistNames = new List<string> { "various artists", "various", "va", "unknown" };
|
||||
private static readonly List<IsoCountry> PreferredCountries = new List<string>
|
||||
{
|
||||
"United States",
|
||||
"United Kingdom",
|
||||
"Europe",
|
||||
"[Worldwide]"
|
||||
}.Select(x => IsoCountries.Find(x)).ToList();
|
||||
|
||||
private static bool TrackIndexIncorrect(LocalTrack localTrack, Track mbTrack, int totalTrackNumber)
|
||||
{
|
||||
return localTrack.FileTrackInfo.TrackNumbers[0] != mbTrack.AbsoluteTrackNumber &&
|
||||
localTrack.FileTrackInfo.TrackNumbers[0] != totalTrackNumber;
|
||||
}
|
||||
|
||||
public static int GetTotalTrackNumber(Track track, List<Track> allTracks)
|
||||
{
|
||||
return track.AbsoluteTrackNumber + allTracks.Count(t => t.MediumNumber < track.MediumNumber);
|
||||
}
|
||||
|
||||
public static Distance TrackDistance(LocalTrack localTrack, Track mbTrack, int totalTrackNumber, bool includeArtist = false)
|
||||
{
|
||||
var dist = new Distance();
|
||||
|
||||
var localLength = localTrack.FileTrackInfo.Duration.TotalSeconds;
|
||||
var mbLength = mbTrack.Duration / 1000;
|
||||
var diff = Math.Abs(localLength - mbLength) - 10;
|
||||
|
||||
if (mbLength > 0)
|
||||
{
|
||||
dist.AddRatio("track_length", diff, 30);
|
||||
}
|
||||
|
||||
// musicbrainz never has 'featuring' in the track title
|
||||
// see https://musicbrainz.org/doc/Style/Artist_Credits
|
||||
dist.AddString("track_title", localTrack.FileTrackInfo.CleanTitle ?? "", mbTrack.Title);
|
||||
|
||||
if (includeArtist && localTrack.FileTrackInfo.ArtistTitle.IsNotNullOrWhiteSpace()
|
||||
&& !VariousArtistNames.Any(x => x.Equals(localTrack.FileTrackInfo.ArtistTitle, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
dist.AddString("track_artist", localTrack.FileTrackInfo.ArtistTitle, mbTrack.ArtistMetadata.Value.Name);
|
||||
}
|
||||
|
||||
if (localTrack.FileTrackInfo.TrackNumbers.FirstOrDefault() > 0 && mbTrack.AbsoluteTrackNumber > 0)
|
||||
{
|
||||
dist.AddBool("track_index", TrackIndexIncorrect(localTrack, mbTrack, totalTrackNumber));
|
||||
}
|
||||
|
||||
var recordingId = localTrack.FileTrackInfo.RecordingMBId;
|
||||
if (recordingId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
dist.AddBool("recording_id", localTrack.FileTrackInfo.RecordingMBId != mbTrack.ForeignRecordingId &&
|
||||
!mbTrack.OldForeignRecordingIds.Contains(localTrack.FileTrackInfo.RecordingMBId));
|
||||
}
|
||||
|
||||
// for fingerprinted files
|
||||
if (localTrack.AcoustIdResults != null)
|
||||
{
|
||||
dist.AddBool("recording_id", !localTrack.AcoustIdResults.Contains(mbTrack.ForeignRecordingId));
|
||||
}
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
public static Distance AlbumReleaseDistance(List<LocalTrack> localTracks, AlbumRelease release, TrackMapping mapping)
|
||||
{
|
||||
var dist = new Distance();
|
||||
|
||||
if (!VariousArtistIds.Contains(release.Album.Value.ArtistMetadata.Value.ForeignArtistId))
|
||||
{
|
||||
var artist = localTracks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? "";
|
||||
dist.AddString("artist", artist, release.Album.Value.ArtistMetadata.Value.Name);
|
||||
Logger.Trace("artist: {0} vs {1}; {2}", artist, release.Album.Value.ArtistMetadata.Value.Name, dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
var title = localTracks.MostCommon(x => x.FileTrackInfo.AlbumTitle) ?? "";
|
||||
|
||||
// Use the album title since the differences in release titles can cause confusion and
|
||||
// aren't always correct in the tags
|
||||
dist.AddString("album", title, release.Album.Value.Title);
|
||||
Logger.Trace("album: {0} vs {1}; {2}", title, release.Title, dist.NormalizedDistance());
|
||||
|
||||
// Number of discs, either as tagged or the max disc number seen
|
||||
var discCount = localTracks.MostCommon(x => x.FileTrackInfo.DiscCount);
|
||||
discCount = discCount != 0 ? discCount : localTracks.Max(x => x.FileTrackInfo.DiscNumber);
|
||||
if (discCount > 0)
|
||||
{
|
||||
dist.AddNumber("media_count", discCount, release.Media.Count);
|
||||
Logger.Trace("media_count: {0} vs {1}; {2}", discCount, release.Media.Count, dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
// Media format
|
||||
if (release.Media.Select(x => x.Format).Contains("Unknown"))
|
||||
{
|
||||
dist.Add("media_format", 1.0);
|
||||
}
|
||||
|
||||
// Year
|
||||
var localYear = localTracks.MostCommon(x => x.FileTrackInfo.Year);
|
||||
if (localYear > 0 && (release.Album.Value.ReleaseDate.HasValue || release.ReleaseDate.HasValue))
|
||||
{
|
||||
var albumYear = release.Album.Value.ReleaseDate?.Year ?? 0;
|
||||
var releaseYear = release.ReleaseDate?.Year ?? 0;
|
||||
if (localYear == albumYear || localYear == releaseYear)
|
||||
{
|
||||
dist.Add("year", 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remoteYear = albumYear > 0 ? albumYear : releaseYear;
|
||||
var diff = Math.Abs(localYear - remoteYear);
|
||||
var diff_max = Math.Abs(DateTime.Now.Year - remoteYear);
|
||||
dist.AddRatio("year", diff, diff_max);
|
||||
}
|
||||
|
||||
Logger.Trace($"year: {localYear} vs {release.Album.Value.ReleaseDate?.Year} or {release.ReleaseDate?.Year}; {dist.NormalizedDistance()}");
|
||||
}
|
||||
|
||||
// If we parsed a country from the files use that, otherwise use our preference
|
||||
var country = localTracks.MostCommon(x => x.FileTrackInfo.Country);
|
||||
if (release.Country.Count > 0)
|
||||
{
|
||||
if (country != null)
|
||||
{
|
||||
dist.AddEquality("country", country.Name, release.Country);
|
||||
Logger.Trace("country: {0} vs {1}; {2}", country.Name, string.Join(", ", release.Country), dist.NormalizedDistance());
|
||||
}
|
||||
else if (PreferredCountries.Count > 0)
|
||||
{
|
||||
dist.AddPriority("country", release.Country, PreferredCountries.Select(x => x.Name).ToList());
|
||||
Logger.Trace("country priority: {0} vs {1}; {2}", string.Join(", ", PreferredCountries.Select(x => x.Name)), string.Join(", ", release.Country), dist.NormalizedDistance());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// full penalty if MusicBrainz release is missing a country
|
||||
dist.Add("country", 1.0);
|
||||
}
|
||||
|
||||
var label = localTracks.MostCommon(x => x.FileTrackInfo.Label);
|
||||
if (label.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
dist.AddEquality("label", label, release.Label);
|
||||
Logger.Trace("label: {0} vs {1}; {2}", label, string.Join(", ", release.Label), dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
var disambig = localTracks.MostCommon(x => x.FileTrackInfo.Disambiguation);
|
||||
if (disambig.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
dist.AddString("album_disambiguation", disambig, release.Disambiguation);
|
||||
Logger.Trace("album_disambiguation: {0} vs {1}; {2}", disambig, release.Disambiguation, dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
var mbAlbumId = localTracks.MostCommon(x => x.FileTrackInfo.ReleaseMBId);
|
||||
if (mbAlbumId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
dist.AddBool("album_id", mbAlbumId != release.ForeignReleaseId && !release.OldForeignReleaseIds.Contains(mbAlbumId));
|
||||
Logger.Trace("album_id: {0} vs {1} or {2}; {3}", mbAlbumId, release.ForeignReleaseId, string.Join(", ", release.OldForeignReleaseIds), dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
// tracks
|
||||
foreach (var pair in mapping.Mapping)
|
||||
{
|
||||
dist.Add("tracks", pair.Value.Item2.NormalizedDistance());
|
||||
}
|
||||
|
||||
Logger.Trace("after trackMapping: {0}", dist.NormalizedDistance());
|
||||
|
||||
// missing tracks
|
||||
foreach (var track in mapping.MBExtra.Take(localTracks.Count))
|
||||
{
|
||||
dist.Add("missing_tracks", 1.0);
|
||||
}
|
||||
|
||||
Logger.Trace("after missing tracks: {0}", dist.NormalizedDistance());
|
||||
|
||||
// unmatched tracks
|
||||
foreach (var track in mapping.LocalExtra.Take(localTracks.Count))
|
||||
{
|
||||
dist.Add("unmatched_tracks", 1.0);
|
||||
}
|
||||
|
||||
Logger.Trace("after unmatched tracks: {0}", dist.NormalizedDistance());
|
||||
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
|||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation;
|
||||
|
@ -17,59 +17,39 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
{
|
||||
public interface IIdentificationService
|
||||
{
|
||||
List<LocalAlbumRelease> Identify(List<LocalTrack> localTracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease, bool includeExisting);
|
||||
List<LocalAlbumRelease> Identify(List<LocalTrack> localTracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config);
|
||||
}
|
||||
|
||||
public class IdentificationService : IIdentificationService
|
||||
{
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IReleaseService _releaseService;
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly ITrackGroupingService _trackGroupingService;
|
||||
private readonly IFingerprintingService _fingerprintingService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IAugmentingService _augmentingService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly ICandidateService _candidateService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public IdentificationService(IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
IReleaseService releaseService,
|
||||
ITrackService trackService,
|
||||
public IdentificationService(ITrackService trackService,
|
||||
ITrackGroupingService trackGroupingService,
|
||||
IFingerprintingService fingerprintingService,
|
||||
IAudioTagService audioTagService,
|
||||
IAugmentingService augmentingService,
|
||||
IMediaFileService mediaFileService,
|
||||
ICandidateService candidateService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
{
|
||||
_artistService = artistService;
|
||||
_albumService = albumService;
|
||||
_releaseService = releaseService;
|
||||
_trackService = trackService;
|
||||
_trackGroupingService = trackGroupingService;
|
||||
_fingerprintingService = fingerprintingService;
|
||||
_audioTagService = audioTagService;
|
||||
_augmentingService = augmentingService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_candidateService = candidateService;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private readonly List<IsoCountry> _preferredCountries = new List<string>
|
||||
{
|
||||
"United States",
|
||||
"United Kingdom",
|
||||
"Europe",
|
||||
"[Worldwide]"
|
||||
}.Select(x => IsoCountries.Find(x)).ToList();
|
||||
|
||||
private readonly List<string> _variousArtistNames = new List<string> { "various artists", "various", "va", "unknown" };
|
||||
private readonly List<string> _variousArtistIds = new List<string> { "89ad4ac3-39f7-470e-963a-56509c546377" };
|
||||
|
||||
private void LogTestCaseOutput(List<LocalTrack> localTracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease)
|
||||
{
|
||||
var trackData = localTracks.Select(x => new BasicLocalTrack
|
||||
|
@ -104,17 +84,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
_logger.Debug($"*** IdentificationService TestCaseGenerator ***\n{output}");
|
||||
}
|
||||
|
||||
public List<LocalAlbumRelease> Identify(List<LocalTrack> localTracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease, bool includeExisting)
|
||||
public List<LocalAlbumRelease> GetLocalAlbumReleases(List<LocalTrack> localTracks, bool singleRelease)
|
||||
{
|
||||
// 1 group localTracks so that we think they represent a single release
|
||||
// 2 get candidates given specified artist, album and release. Candidates can include extra files already on disk.
|
||||
// 3 find best candidate
|
||||
// 4 If best candidate worse than threshold, try fingerprinting
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
_logger.Debug("Starting track identification");
|
||||
LogTestCaseOutput(localTracks, artist, album, release, newDownload, singleRelease);
|
||||
|
||||
List<LocalAlbumRelease> releases = null;
|
||||
if (singleRelease)
|
||||
{
|
||||
|
@ -137,8 +109,29 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
{
|
||||
_logger.Warn($"Augmentation failed for {localRelease}");
|
||||
}
|
||||
}
|
||||
|
||||
IdentifyRelease(localRelease, artist, album, release, newDownload, includeExisting);
|
||||
return releases;
|
||||
}
|
||||
|
||||
public List<LocalAlbumRelease> Identify(List<LocalTrack> localTracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config)
|
||||
{
|
||||
// 1 group localTracks so that we think they represent a single release
|
||||
// 2 get candidates given specified artist, album and release. Candidates can include extra files already on disk.
|
||||
// 3 find best candidate
|
||||
// 4 If best candidate worse than threshold, try fingerprinting
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
_logger.Debug("Starting track identification");
|
||||
|
||||
var releases = GetLocalAlbumReleases(localTracks, config.SingleRelease);
|
||||
|
||||
int i = 0;
|
||||
foreach (var localRelease in releases)
|
||||
{
|
||||
i++;
|
||||
_logger.ProgressInfo($"Identifying album {i}/{releases.Count}");
|
||||
IdentifyRelease(localRelease, idOverrides, config);
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
|
@ -191,25 +184,37 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
AdditionalFile = true,
|
||||
Quality = x.Quality
|
||||
}))
|
||||
.ToList();
|
||||
.ToList();
|
||||
|
||||
localTracks.ForEach(x => _augmentingService.Augment(x, true));
|
||||
|
||||
return localTracks;
|
||||
}
|
||||
|
||||
private void IdentifyRelease(LocalAlbumRelease localAlbumRelease, Artist artist, Album album, AlbumRelease release, bool newDownload, bool includeExisting)
|
||||
private void IdentifyRelease(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config)
|
||||
{
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
bool fingerprinted = false;
|
||||
|
||||
var candidateReleases = GetCandidatesFromTags(localAlbumRelease, artist, album, release, includeExisting);
|
||||
if (candidateReleases.Count == 0 && FingerprintingAllowed(newDownload))
|
||||
var candidateReleases = _candidateService.GetDbCandidatesFromTags(localAlbumRelease, idOverrides, config.IncludeExisting);
|
||||
|
||||
if (candidateReleases.Count == 0 && config.AddNewArtists)
|
||||
{
|
||||
candidateReleases = _candidateService.GetRemoteCandidates(localAlbumRelease);
|
||||
}
|
||||
|
||||
if (candidateReleases.Count == 0 && FingerprintingAllowed(config.NewDownload))
|
||||
{
|
||||
_logger.Debug("No candidates found, fingerprinting");
|
||||
_fingerprintingService.Lookup(localAlbumRelease.LocalTracks, 0.5);
|
||||
fingerprinted = true;
|
||||
candidateReleases = GetCandidatesFromFingerprint(localAlbumRelease, artist, album, release, includeExisting);
|
||||
candidateReleases = _candidateService.GetDbCandidatesFromFingerprint(localAlbumRelease, idOverrides, config.IncludeExisting);
|
||||
|
||||
if (candidateReleases.Count == 0 && config.AddNewArtists)
|
||||
{
|
||||
// Now fingerprints are populated this will return a different answer
|
||||
candidateReleases = _candidateService.GetRemoteCandidates(localAlbumRelease);
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateReleases.Count == 0)
|
||||
|
@ -220,32 +225,36 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
|
||||
_logger.Debug($"Got {candidateReleases.Count} candidates for {localAlbumRelease.LocalTracks.Count} tracks in {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
var allTracks = _trackService.GetTracksByReleases(candidateReleases.Select(x => x.AlbumRelease.Id).ToList());
|
||||
PopulateTracks(candidateReleases);
|
||||
|
||||
// convert all the TrackFiles that represent extra files to List<LocalTrack>
|
||||
var allLocalTracks = ToLocalTrack(candidateReleases
|
||||
.SelectMany(x => x.ExistingTracks)
|
||||
.DistinctBy(x => x.Path), localAlbumRelease);
|
||||
|
||||
_logger.Debug($"Retrieved {allTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms");
|
||||
_logger.Debug($"Retrieved {allLocalTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
GetBestRelease(localAlbumRelease, candidateReleases, allTracks, allLocalTracks);
|
||||
GetBestRelease(localAlbumRelease, candidateReleases, allLocalTracks);
|
||||
|
||||
// If result isn't great and we haven't fingerprinted, try that
|
||||
// Note that this can improve the match even if we try the same candidates
|
||||
if (!fingerprinted && FingerprintingAllowed(newDownload) && ShouldFingerprint(localAlbumRelease))
|
||||
if (!fingerprinted && FingerprintingAllowed(config.NewDownload) && ShouldFingerprint(localAlbumRelease))
|
||||
{
|
||||
_logger.Debug($"Match not good enough, fingerprinting");
|
||||
_fingerprintingService.Lookup(localAlbumRelease.LocalTracks, 0.5);
|
||||
|
||||
// Only include extra possible candidates if neither album nor release are specified
|
||||
// Will generally be specified as part of manual import
|
||||
if (album == null && release == null)
|
||||
if (idOverrides?.Album == null && idOverrides?.AlbumRelease == null)
|
||||
{
|
||||
var extraCandidates = GetCandidatesFromFingerprint(localAlbumRelease, artist, album, release, includeExisting);
|
||||
var dbCandidates = _candidateService.GetDbCandidatesFromFingerprint(localAlbumRelease, idOverrides, config.IncludeExisting);
|
||||
var remoteCandidates = config.AddNewArtists ? _candidateService.GetRemoteCandidates(localAlbumRelease) : new List<CandidateAlbumRelease>();
|
||||
var extraCandidates = dbCandidates.Concat(remoteCandidates);
|
||||
var newCandidates = extraCandidates.ExceptBy(x => x.AlbumRelease.Id, candidateReleases, y => y.AlbumRelease.Id, EqualityComparer<int>.Default);
|
||||
candidateReleases.AddRange(newCandidates);
|
||||
allTracks.AddRange(_trackService.GetTracksByReleases(newCandidates.Select(x => x.AlbumRelease.Id).ToList()));
|
||||
|
||||
PopulateTracks(candidateReleases);
|
||||
|
||||
allLocalTracks.AddRange(ToLocalTrack(newCandidates
|
||||
.SelectMany(x => x.ExistingTracks)
|
||||
.DistinctBy(x => x.Path)
|
||||
|
@ -256,7 +265,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
// fingerprint all the local files in candidates we might be matching against
|
||||
_fingerprintingService.Lookup(allLocalTracks, 0.5);
|
||||
|
||||
GetBestRelease(localAlbumRelease, candidateReleases, allTracks, allLocalTracks);
|
||||
GetBestRelease(localAlbumRelease, candidateReleases, allLocalTracks);
|
||||
}
|
||||
|
||||
_logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms");
|
||||
|
@ -266,186 +275,22 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
_logger.Debug($"IdentifyRelease done in {watch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
public List<CandidateAlbumRelease> GetCandidatesFromTags(LocalAlbumRelease localAlbumRelease, Artist artist, Album album, AlbumRelease release, bool includeExisting)
|
||||
public void PopulateTracks(List<CandidateAlbumRelease> candidateReleases)
|
||||
{
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
// Generally artist, album and release are null. But if they're not then limit candidates appropriately.
|
||||
// We've tried to make sure that tracks are all for a single release.
|
||||
List<CandidateAlbumRelease> candidateReleases;
|
||||
var releasesMissingTracks = candidateReleases.Where(x => !x.AlbumRelease.Tracks.IsLoaded);
|
||||
var allTracks = _trackService.GetTracksByReleases(releasesMissingTracks.Select(x => x.AlbumRelease.Id).ToList());
|
||||
|
||||
// if we have a release ID, use that
|
||||
AlbumRelease tagMbidRelease = null;
|
||||
List<CandidateAlbumRelease> tagCandidate = null;
|
||||
_logger.Debug($"Retrieved {allTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
var releaseIds = localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.ReleaseMBId).Distinct().ToList();
|
||||
if (releaseIds.Count == 1 && releaseIds[0].IsNotNullOrWhiteSpace())
|
||||
foreach (var release in releasesMissingTracks)
|
||||
{
|
||||
_logger.Debug("Selecting release from consensus ForeignReleaseId [{0}]", releaseIds[0]);
|
||||
tagMbidRelease = _releaseService.GetReleaseByForeignReleaseId(releaseIds[0], true);
|
||||
|
||||
if (tagMbidRelease != null)
|
||||
{
|
||||
tagCandidate = GetCandidatesByRelease(new List<AlbumRelease> { tagMbidRelease }, includeExisting);
|
||||
}
|
||||
release.AlbumRelease.Tracks = allTracks.Where(x => x.AlbumReleaseId == release.AlbumRelease.Id).ToList();
|
||||
}
|
||||
|
||||
if (release != null)
|
||||
{
|
||||
// this case overrides the release picked up from the file tags
|
||||
_logger.Debug("Release {0} [{1} tracks] was forced", release, release.TrackCount);
|
||||
candidateReleases = GetCandidatesByRelease(new List<AlbumRelease> { release }, includeExisting);
|
||||
}
|
||||
else if (album != null)
|
||||
{
|
||||
// use the release from file tags if it exists and agrees with the specified album
|
||||
if (tagMbidRelease?.AlbumId == album.Id)
|
||||
{
|
||||
candidateReleases = tagCandidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateReleases = GetCandidatesByAlbum(localAlbumRelease, album, includeExisting);
|
||||
}
|
||||
}
|
||||
else if (artist != null)
|
||||
{
|
||||
// use the release from file tags if it exists and agrees with the specified album
|
||||
if (tagMbidRelease?.Album.Value.ArtistMetadataId == artist.ArtistMetadataId)
|
||||
{
|
||||
candidateReleases = tagCandidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateReleases = GetCandidatesByArtist(localAlbumRelease, artist, includeExisting);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tagMbidRelease != null)
|
||||
{
|
||||
candidateReleases = tagCandidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateReleases = GetCandidates(localAlbumRelease, includeExisting);
|
||||
}
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
_logger.Debug($"Getting candidates from tags for {localAlbumRelease.LocalTracks.Count} tracks took {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
// if we haven't got any candidates then try fingerprinting
|
||||
return candidateReleases;
|
||||
}
|
||||
|
||||
private List<CandidateAlbumRelease> GetCandidatesByRelease(List<AlbumRelease> releases, bool includeExisting)
|
||||
{
|
||||
// get the local tracks on disk for each album
|
||||
var albumTracks = releases.Select(x => x.AlbumId)
|
||||
.Distinct()
|
||||
.ToDictionary(id => id, id => includeExisting ? _mediaFileService.GetFilesByAlbum(id) : new List<TrackFile>());
|
||||
|
||||
return releases.Select(x => new CandidateAlbumRelease
|
||||
{
|
||||
AlbumRelease = x,
|
||||
ExistingTracks = albumTracks[x.AlbumId]
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private List<CandidateAlbumRelease> GetCandidatesByAlbum(LocalAlbumRelease localAlbumRelease, Album album, bool includeExisting)
|
||||
{
|
||||
// sort candidate releases by closest track count so that we stand a chance of
|
||||
// getting a perfect match early on
|
||||
return GetCandidatesByRelease(_releaseService.GetReleasesByAlbum(album.Id)
|
||||
.OrderBy(x => Math.Abs(localAlbumRelease.TrackCount - x.TrackCount))
|
||||
.ToList(), includeExisting);
|
||||
}
|
||||
|
||||
private List<CandidateAlbumRelease> GetCandidatesByArtist(LocalAlbumRelease localAlbumRelease, Artist artist, bool includeExisting)
|
||||
{
|
||||
_logger.Trace("Getting candidates for {0}", artist);
|
||||
var candidateReleases = new List<CandidateAlbumRelease>();
|
||||
|
||||
var albumTag = MostCommon(localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.AlbumTitle)) ?? "";
|
||||
if (albumTag.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var possibleAlbums = _albumService.GetCandidates(artist.ArtistMetadataId, albumTag);
|
||||
foreach (var album in possibleAlbums)
|
||||
{
|
||||
candidateReleases.AddRange(GetCandidatesByAlbum(localAlbumRelease, album, includeExisting));
|
||||
}
|
||||
}
|
||||
|
||||
return candidateReleases;
|
||||
}
|
||||
|
||||
private List<CandidateAlbumRelease> GetCandidates(LocalAlbumRelease localAlbumRelease, bool includeExisting)
|
||||
{
|
||||
// most general version, nothing has been specified.
|
||||
// get all plausible artists, then all plausible albums, then get releases for each of these.
|
||||
|
||||
// check if it looks like VA.
|
||||
if (TrackGroupingService.IsVariousArtists(localAlbumRelease.LocalTracks))
|
||||
{
|
||||
throw new NotImplementedException("Various artists not supported");
|
||||
}
|
||||
|
||||
var candidateReleases = new List<CandidateAlbumRelease>();
|
||||
|
||||
var artistTag = MostCommon(localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.ArtistTitle)) ?? "";
|
||||
if (artistTag.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var possibleArtists = _artistService.GetCandidates(artistTag);
|
||||
foreach (var artist in possibleArtists)
|
||||
{
|
||||
candidateReleases.AddRange(GetCandidatesByArtist(localAlbumRelease, artist, includeExisting));
|
||||
}
|
||||
}
|
||||
|
||||
return candidateReleases;
|
||||
}
|
||||
|
||||
public List<CandidateAlbumRelease> GetCandidatesFromFingerprint(LocalAlbumRelease localAlbumRelease, Artist artist, Album album, AlbumRelease release, bool includeExisting)
|
||||
{
|
||||
var recordingIds = localAlbumRelease.LocalTracks.Where(x => x.AcoustIdResults != null).SelectMany(x => x.AcoustIdResults).ToList();
|
||||
var allReleases = _releaseService.GetReleasesByRecordingIds(recordingIds);
|
||||
|
||||
// make sure releases are consistent with those selected by the user
|
||||
if (release != null)
|
||||
{
|
||||
allReleases = allReleases.Where(x => x.Id == release.Id).ToList();
|
||||
}
|
||||
else if (album != null)
|
||||
{
|
||||
allReleases = allReleases.Where(x => x.AlbumId == album.Id).ToList();
|
||||
}
|
||||
else if (artist != null)
|
||||
{
|
||||
allReleases = allReleases.Where(x => x.Album.Value.ArtistMetadataId == artist.ArtistMetadataId).ToList();
|
||||
}
|
||||
|
||||
return GetCandidatesByRelease(allReleases.Select(x => new
|
||||
{
|
||||
Release = x,
|
||||
TrackCount = x.TrackCount,
|
||||
CommonProportion = x.Tracks.Value.Select(y => y.ForeignRecordingId).Intersect(recordingIds).Count() / localAlbumRelease.TrackCount
|
||||
})
|
||||
.Where(x => x.CommonProportion > 0.6)
|
||||
.ToList()
|
||||
.OrderBy(x => Math.Abs(x.TrackCount - localAlbumRelease.TrackCount))
|
||||
.ThenByDescending(x => x.CommonProportion)
|
||||
.Select(x => x.Release)
|
||||
.Take(10)
|
||||
.ToList(), includeExisting);
|
||||
}
|
||||
|
||||
private T MostCommon<T>(IEnumerable<T> items)
|
||||
{
|
||||
return items.GroupBy(x => x).OrderByDescending(x => x.Count()).First().Key;
|
||||
}
|
||||
|
||||
private void GetBestRelease(LocalAlbumRelease localAlbumRelease, List<CandidateAlbumRelease> candidateReleases, List<Track> dbTracks, List<LocalTrack> extraTracksOnDisk)
|
||||
private void GetBestRelease(LocalAlbumRelease localAlbumRelease, List<CandidateAlbumRelease> candidateReleases, List<LocalTrack> extraTracksOnDisk)
|
||||
{
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
|
@ -464,8 +309,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
var extraTracks = extraTracksOnDisk.Where(x => extraTrackPaths.Contains(x.Path)).ToList();
|
||||
var allLocalTracks = localAlbumRelease.LocalTracks.Concat(extraTracks).DistinctBy(x => x.Path).ToList();
|
||||
|
||||
var mapping = MapReleaseTracks(allLocalTracks, dbTracks.Where(x => x.AlbumReleaseId == release.Id).ToList());
|
||||
var distance = AlbumReleaseDistance(allLocalTracks, release, mapping);
|
||||
var mapping = MapReleaseTracks(allLocalTracks, release.Tracks.Value);
|
||||
var distance = DistanceCalculator.AlbumReleaseDistance(allLocalTracks, release, mapping);
|
||||
var currDistance = distance.NormalizedDistance();
|
||||
|
||||
rwatch.Stop();
|
||||
|
@ -493,11 +338,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
_logger.Debug($"Best release: {localAlbumRelease.AlbumRelease} Distance {localAlbumRelease.Distance.NormalizedDistance()} found in {watch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
public int GetTotalTrackNumber(Track track, List<Track> allTracks)
|
||||
{
|
||||
return track.AbsoluteTrackNumber + allTracks.Count(t => t.MediumNumber < track.MediumNumber);
|
||||
}
|
||||
|
||||
public TrackMapping MapReleaseTracks(List<LocalTrack> localTracks, List<Track> mbTracks)
|
||||
{
|
||||
var distances = new Distance[localTracks.Count, mbTracks.Count];
|
||||
|
@ -505,10 +345,10 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
|
||||
for (int col = 0; col < mbTracks.Count; col++)
|
||||
{
|
||||
var totalTrackNumber = GetTotalTrackNumber(mbTracks[col], mbTracks);
|
||||
var totalTrackNumber = DistanceCalculator.GetTotalTrackNumber(mbTracks[col], mbTracks);
|
||||
for (int row = 0; row < localTracks.Count; row++)
|
||||
{
|
||||
distances[row, col] = TrackDistance(localTracks[row], mbTracks[col], totalTrackNumber, false);
|
||||
distances[row, col] = DistanceCalculator.TrackDistance(localTracks[row], mbTracks[col], totalTrackNumber, false);
|
||||
costs[row, col] = distances[row, col].NormalizedDistance();
|
||||
}
|
||||
}
|
||||
|
@ -531,178 +371,5 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool TrackIndexIncorrect(LocalTrack localTrack, Track mbTrack, int totalTrackNumber)
|
||||
{
|
||||
return localTrack.FileTrackInfo.TrackNumbers[0] != mbTrack.AbsoluteTrackNumber &&
|
||||
localTrack.FileTrackInfo.TrackNumbers[0] != totalTrackNumber;
|
||||
}
|
||||
|
||||
public Distance TrackDistance(LocalTrack localTrack, Track mbTrack, int totalTrackNumber, bool includeArtist = false)
|
||||
{
|
||||
var dist = new Distance();
|
||||
|
||||
var localLength = localTrack.FileTrackInfo.Duration.TotalSeconds;
|
||||
var mbLength = mbTrack.Duration / 1000;
|
||||
var diff = Math.Abs(localLength - mbLength) - 10;
|
||||
|
||||
if (mbLength > 0)
|
||||
{
|
||||
dist.AddRatio("track_length", diff, 30);
|
||||
}
|
||||
|
||||
// musicbrainz never has 'featuring' in the track title
|
||||
// see https://musicbrainz.org/doc/Style/Artist_Credits
|
||||
dist.AddString("track_title", localTrack.FileTrackInfo.CleanTitle ?? "", mbTrack.Title);
|
||||
|
||||
if (includeArtist && localTrack.FileTrackInfo.ArtistTitle.IsNotNullOrWhiteSpace()
|
||||
&& !_variousArtistNames.Any(x => x.Equals(localTrack.FileTrackInfo.ArtistTitle, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
dist.AddString("track_artist", localTrack.FileTrackInfo.ArtistTitle, mbTrack.ArtistMetadata.Value.Name);
|
||||
}
|
||||
|
||||
if (localTrack.FileTrackInfo.TrackNumbers.FirstOrDefault() > 0 && mbTrack.AbsoluteTrackNumber > 0)
|
||||
{
|
||||
dist.AddBool("track_index", TrackIndexIncorrect(localTrack, mbTrack, totalTrackNumber));
|
||||
}
|
||||
|
||||
var recordingId = localTrack.FileTrackInfo.RecordingMBId;
|
||||
if (recordingId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
dist.AddBool("recording_id", localTrack.FileTrackInfo.RecordingMBId != mbTrack.ForeignRecordingId &&
|
||||
!mbTrack.OldForeignRecordingIds.Contains(localTrack.FileTrackInfo.RecordingMBId));
|
||||
}
|
||||
|
||||
// for fingerprinted files
|
||||
if (localTrack.AcoustIdResults != null)
|
||||
{
|
||||
dist.AddBool("recording_id", !localTrack.AcoustIdResults.Contains(mbTrack.ForeignRecordingId));
|
||||
}
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
public Distance AlbumReleaseDistance(List<LocalTrack> localTracks, AlbumRelease release, TrackMapping mapping)
|
||||
{
|
||||
var dist = new Distance();
|
||||
|
||||
if (!_variousArtistIds.Contains(release.Album.Value.ArtistMetadata.Value.ForeignArtistId))
|
||||
{
|
||||
var artist = MostCommon(localTracks.Select(x => x.FileTrackInfo.ArtistTitle)) ?? "";
|
||||
dist.AddString("artist", artist, release.Album.Value.ArtistMetadata.Value.Name);
|
||||
_logger.Trace("artist: {0} vs {1}; {2}", artist, release.Album.Value.ArtistMetadata.Value.Name, dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
var title = MostCommon(localTracks.Select(x => x.FileTrackInfo.AlbumTitle)) ?? "";
|
||||
|
||||
// Use the album title since the differences in release titles can cause confusion and
|
||||
// aren't always correct in the tags
|
||||
dist.AddString("album", title, release.Album.Value.Title);
|
||||
_logger.Trace("album: {0} vs {1}; {2}", title, release.Title, dist.NormalizedDistance());
|
||||
|
||||
// Number of discs, either as tagged or the max disc number seen
|
||||
var discCount = MostCommon(localTracks.Select(x => x.FileTrackInfo.DiscCount));
|
||||
discCount = discCount != 0 ? discCount : localTracks.Max(x => x.FileTrackInfo.DiscNumber);
|
||||
if (discCount > 0)
|
||||
{
|
||||
dist.AddNumber("media_count", discCount, release.Media.Count);
|
||||
_logger.Trace("media_count: {0} vs {1}; {2}", discCount, release.Media.Count, dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
// Media format
|
||||
if (release.Media.Select(x => x.Format).Contains("Unknown"))
|
||||
{
|
||||
dist.Add("media_format", 1.0);
|
||||
}
|
||||
|
||||
// Year
|
||||
var localYear = MostCommon(localTracks.Select(x => x.FileTrackInfo.Year));
|
||||
if (localYear > 0 && (release.Album.Value.ReleaseDate.HasValue || release.ReleaseDate.HasValue))
|
||||
{
|
||||
var albumYear = release.Album.Value.ReleaseDate?.Year ?? 0;
|
||||
var releaseYear = release.ReleaseDate?.Year ?? 0;
|
||||
if (localYear == albumYear || localYear == releaseYear)
|
||||
{
|
||||
dist.Add("year", 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remoteYear = albumYear > 0 ? albumYear : releaseYear;
|
||||
var diff = Math.Abs(localYear - remoteYear);
|
||||
var diff_max = Math.Abs(DateTime.Now.Year - remoteYear);
|
||||
dist.AddRatio("year", diff, diff_max);
|
||||
}
|
||||
|
||||
_logger.Trace($"year: {localYear} vs {release.Album.Value.ReleaseDate?.Year} or {release.ReleaseDate?.Year}; {dist.NormalizedDistance()}");
|
||||
}
|
||||
|
||||
// If we parsed a country from the files use that, otherwise use our preference
|
||||
var country = MostCommon(localTracks.Select(x => x.FileTrackInfo.Country));
|
||||
if (release.Country.Count > 0)
|
||||
{
|
||||
if (country != null)
|
||||
{
|
||||
dist.AddEquality("country", country.Name, release.Country);
|
||||
_logger.Trace("country: {0} vs {1}; {2}", country.Name, string.Join(", ", release.Country), dist.NormalizedDistance());
|
||||
}
|
||||
else if (_preferredCountries.Count > 0)
|
||||
{
|
||||
dist.AddPriority("country", release.Country, _preferredCountries.Select(x => x.Name).ToList());
|
||||
_logger.Trace("country priority: {0} vs {1}; {2}", string.Join(", ", _preferredCountries.Select(x => x.Name)), string.Join(", ", release.Country), dist.NormalizedDistance());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// full penalty if MusicBrainz release is missing a country
|
||||
dist.Add("country", 1.0);
|
||||
}
|
||||
|
||||
var label = MostCommon(localTracks.Select(x => x.FileTrackInfo.Label));
|
||||
if (label.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
dist.AddEquality("label", label, release.Label);
|
||||
_logger.Trace("label: {0} vs {1}; {2}", label, string.Join(", ", release.Label), dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
var disambig = MostCommon(localTracks.Select(x => x.FileTrackInfo.Disambiguation));
|
||||
if (disambig.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
dist.AddString("album_disambiguation", disambig, release.Disambiguation);
|
||||
_logger.Trace("album_disambiguation: {0} vs {1}; {2}", disambig, release.Disambiguation, dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
var mbAlbumId = MostCommon(localTracks.Select(x => x.FileTrackInfo.ReleaseMBId));
|
||||
if (mbAlbumId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
dist.AddBool("album_id", mbAlbumId != release.ForeignReleaseId && !release.OldForeignReleaseIds.Contains(mbAlbumId));
|
||||
_logger.Trace("album_id: {0} vs {1} or {2}; {3}", mbAlbumId, release.ForeignReleaseId, string.Join(", ", release.OldForeignReleaseIds), dist.NormalizedDistance());
|
||||
}
|
||||
|
||||
// tracks
|
||||
foreach (var pair in mapping.Mapping)
|
||||
{
|
||||
dist.Add("tracks", pair.Value.Item2.NormalizedDistance());
|
||||
}
|
||||
|
||||
_logger.Trace("after trackMapping: {0}", dist.NormalizedDistance());
|
||||
|
||||
// missing tracks
|
||||
foreach (var track in mapping.MBExtra.Take(localTracks.Count))
|
||||
{
|
||||
dist.Add("missing_tracks", 1.0);
|
||||
}
|
||||
|
||||
_logger.Trace("after missing tracks: {0}", dist.NormalizedDistance());
|
||||
|
||||
// unmatched tracks
|
||||
foreach (var track in mapping.LocalExtra.Take(localTracks.Count))
|
||||
{
|
||||
dist.Add("unmatched_tracks", 1.0);
|
||||
}
|
||||
|
||||
_logger.Trace("after unmatched tracks: {0}", dist.NormalizedDistance());
|
||||
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using NLog;
|
|||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
||||
|
@ -26,6 +27,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
|
||||
public List<LocalAlbumRelease> GroupTracks(List<LocalTrack> localTracks)
|
||||
{
|
||||
_logger.ProgressInfo($"Grouping {localTracks.Count} tracks");
|
||||
|
||||
var releases = new List<LocalAlbumRelease>();
|
||||
|
||||
// first attempt, assume grouped by folder
|
||||
|
|
|
@ -4,14 +4,19 @@ using System.Linq;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Extras;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Commands;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||
{
|
||||
|
@ -26,81 +31,94 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAddArtistService _addArtistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IRefreshAlbumService _refreshAlbumService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly IRecycleBinProvider _recycleBinProvider;
|
||||
private readonly IExtraService _extraService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IReleaseService _releaseService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ImportApprovedTracks(IUpgradeMediaFiles trackFileUpgrader,
|
||||
IMediaFileService mediaFileService,
|
||||
IAudioTagService audioTagService,
|
||||
ITrackService trackService,
|
||||
IArtistService artistService,
|
||||
IAddArtistService addArtistService,
|
||||
IAlbumService albumService,
|
||||
IRefreshAlbumService refreshAlbumService,
|
||||
IRootFolderService rootFolderService,
|
||||
IRecycleBinProvider recycleBinProvider,
|
||||
IExtraService extraService,
|
||||
IDiskProvider diskProvider,
|
||||
IReleaseService releaseService,
|
||||
IEventAggregator eventAggregator,
|
||||
IManageCommandQueue commandQueueManager,
|
||||
Logger logger)
|
||||
{
|
||||
_trackFileUpgrader = trackFileUpgrader;
|
||||
_mediaFileService = mediaFileService;
|
||||
_audioTagService = audioTagService;
|
||||
_trackService = trackService;
|
||||
_artistService = artistService;
|
||||
_addArtistService = addArtistService;
|
||||
_albumService = albumService;
|
||||
_refreshAlbumService = refreshAlbumService;
|
||||
_rootFolderService = rootFolderService;
|
||||
_recycleBinProvider = recycleBinProvider;
|
||||
_extraService = extraService;
|
||||
_diskProvider = diskProvider;
|
||||
_releaseService = releaseService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_commandQueueManager = commandQueueManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<ImportResult> Import(List<ImportDecision<LocalTrack>> decisions, bool replaceExisting, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto)
|
||||
{
|
||||
var qualifiedImports = decisions.Where(c => c.Approved)
|
||||
.GroupBy(c => c.Item.Artist.Id, (i, s) => s
|
||||
.OrderByDescending(c => c.Item.Quality, new QualityModelComparer(s.First().Item.Artist.QualityProfile))
|
||||
.ThenByDescending(c => c.Item.Size))
|
||||
.SelectMany(c => c)
|
||||
.ToList();
|
||||
|
||||
_logger.Debug($"Importing {qualifiedImports.Count} files. replaceExisting: {replaceExisting}");
|
||||
|
||||
var importResults = new List<ImportResult>();
|
||||
var allImportedTrackFiles = new List<TrackFile>();
|
||||
var allOldTrackFiles = new List<TrackFile>();
|
||||
var addedArtists = new List<Artist>();
|
||||
|
||||
var albumDecisions = decisions.Where(e => e.Item.Album != null && e.Approved)
|
||||
.GroupBy(e => e.Item.Album.Id).ToList();
|
||||
.GroupBy(e => e.Item.Album.ForeignAlbumId).ToList();
|
||||
|
||||
int iDecision = 1;
|
||||
foreach (var albumDecision in albumDecisions)
|
||||
{
|
||||
var album = albumDecision.First().Item.Album;
|
||||
var newRelease = albumDecision.First().Item.Release;
|
||||
_logger.ProgressInfo($"Importing album {iDecision++}/{albumDecisions.Count}");
|
||||
|
||||
var decisionList = albumDecision.ToList();
|
||||
|
||||
var artist = EnsureArtistAdded(decisionList, addedArtists);
|
||||
|
||||
if (artist == null)
|
||||
{
|
||||
// failed to add the artist, carry on with next album
|
||||
continue;
|
||||
}
|
||||
|
||||
var album = EnsureAlbumAdded(decisionList);
|
||||
|
||||
if (album == null)
|
||||
{
|
||||
// failed to add the album, carry on with next one
|
||||
continue;
|
||||
}
|
||||
|
||||
if (replaceExisting)
|
||||
{
|
||||
var artist = albumDecision.First().Item.Artist;
|
||||
var rootFolder = _diskProvider.GetParentFolder(artist.Path);
|
||||
var previousFiles = _mediaFileService.GetFilesByAlbum(album.Id);
|
||||
|
||||
_logger.Debug($"Deleting {previousFiles.Count} existing files for {album}");
|
||||
|
||||
foreach (var previousFile in previousFiles)
|
||||
{
|
||||
var subfolder = rootFolder.GetRelativePath(_diskProvider.GetParentFolder(previousFile.Path));
|
||||
if (_diskProvider.FileExists(previousFile.Path))
|
||||
{
|
||||
_logger.Debug("Removing existing track file: {0}", previousFile);
|
||||
_recycleBinProvider.DeleteFile(previousFile.Path, subfolder);
|
||||
}
|
||||
|
||||
_mediaFileService.Delete(previousFile, DeleteMediaFileReason.Upgrade);
|
||||
}
|
||||
RemoveExistingTrackFiles(artist, album);
|
||||
}
|
||||
|
||||
// set the correct release to be monitored before importing the new files
|
||||
var newRelease = albumDecision.First().Item.Release;
|
||||
_logger.Debug("Updating release to {0} [{1} tracks]", newRelease, newRelease.TrackCount);
|
||||
album.AlbumReleases = _releaseService.SetMonitored(newRelease);
|
||||
|
||||
|
@ -109,6 +127,16 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
_eventAggregator.PublishEvent(new AlbumEditedEvent(album, album));
|
||||
}
|
||||
|
||||
var qualifiedImports = decisions.Where(c => c.Approved)
|
||||
.GroupBy(c => c.Item.Artist.Id, (i, s) => s
|
||||
.OrderByDescending(c => c.Item.Quality, new QualityModelComparer(s.First().Item.Artist.QualityProfile))
|
||||
.ThenByDescending(c => c.Item.Size))
|
||||
.SelectMany(c => c)
|
||||
.ToList();
|
||||
|
||||
_logger.ProgressInfo($"Importing {qualifiedImports.Count} tracks");
|
||||
_logger.Debug($"Importing {qualifiedImports.Count} files. replaceExisting: {replaceExisting}");
|
||||
|
||||
var filesToAdd = new List<TrackFile>(qualifiedImports.Count);
|
||||
var albumReleasesDict = new Dictionary<int, List<AlbumRelease>>(albumDecisions.Count);
|
||||
var trackImportedEvents = new List<TrackImportedEvent>(qualifiedImports.Count);
|
||||
|
@ -184,7 +212,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
|
||||
if (!localTrack.ExistingFile)
|
||||
{
|
||||
trackFile.SceneName = GetSceneReleaseName(downloadClientItem, localTrack);
|
||||
trackFile.SceneName = GetSceneReleaseName(downloadClientItem);
|
||||
|
||||
var moveResult = _trackFileUpgrader.UpgradeTrackFile(trackFile, localTrack, copyOnly);
|
||||
oldFiles = moveResult.OldFiles;
|
||||
|
@ -283,10 +311,147 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
importResults.AddRange(decisions.Where(c => !c.Approved)
|
||||
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
|
||||
|
||||
// Refresh any artists we added
|
||||
if (addedArtists.Any())
|
||||
{
|
||||
_commandQueueManager.Push(new BulkRefreshArtistCommand(addedArtists.Select(x => x.Id).ToList(), true));
|
||||
}
|
||||
|
||||
return importResults;
|
||||
}
|
||||
|
||||
private string GetSceneReleaseName(DownloadClientItem downloadClientItem, LocalTrack localTrack)
|
||||
private Artist EnsureArtistAdded(List<ImportDecision<LocalTrack>> decisions, List<Artist> addedArtists)
|
||||
{
|
||||
var artist = decisions.First().Item.Artist;
|
||||
|
||||
if (artist.Id == 0)
|
||||
{
|
||||
var dbArtist = _artistService.FindById(artist.ForeignArtistId);
|
||||
|
||||
if (dbArtist == null)
|
||||
{
|
||||
_logger.Debug($"Adding remote artist {artist}");
|
||||
var rootFolder = _rootFolderService.GetBestRootFolder(decisions.First().Item.Path);
|
||||
|
||||
artist.RootFolderPath = rootFolder.Path;
|
||||
artist.MetadataProfileId = rootFolder.DefaultMetadataProfileId;
|
||||
artist.QualityProfileId = rootFolder.DefaultQualityProfileId;
|
||||
artist.AlbumFolder = true;
|
||||
artist.Monitored = rootFolder.DefaultMonitorOption != MonitorTypes.None;
|
||||
artist.Tags = rootFolder.DefaultTags;
|
||||
artist.AddOptions = new AddArtistOptions
|
||||
{
|
||||
SearchForMissingAlbums = false,
|
||||
Monitored = artist.Monitored,
|
||||
Monitor = rootFolder.DefaultMonitorOption
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
dbArtist = _addArtistService.AddArtist(artist, false);
|
||||
addedArtists.Add(dbArtist);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Failed to add artist {0}", artist);
|
||||
foreach (var decision in decisions)
|
||||
{
|
||||
decision.Reject(new Rejection("Failed to add missing artist", RejectionType.Temporary));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Put in the newly loaded artist
|
||||
foreach (var decision in decisions)
|
||||
{
|
||||
decision.Item.Artist = dbArtist;
|
||||
decision.Item.Album.Artist = dbArtist;
|
||||
decision.Item.Album.ArtistMetadataId = dbArtist.ArtistMetadataId;
|
||||
}
|
||||
|
||||
artist = dbArtist;
|
||||
}
|
||||
|
||||
return artist;
|
||||
}
|
||||
|
||||
private Album EnsureAlbumAdded(List<ImportDecision<LocalTrack>> decisions)
|
||||
{
|
||||
var album = decisions.First().Item.Album;
|
||||
|
||||
if (album.Id == 0)
|
||||
{
|
||||
var dbAlbum = _albumService.FindById(album.ForeignAlbumId);
|
||||
|
||||
if (dbAlbum == null)
|
||||
{
|
||||
_logger.Debug($"Adding remote album {album}");
|
||||
try
|
||||
{
|
||||
_albumService.InsertMany(new List<Album> { album });
|
||||
_refreshAlbumService.RefreshAlbumInfo(album, new List<Album> { album }, false);
|
||||
dbAlbum = _albumService.FindById(album.ForeignAlbumId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Failed to add album {0}", album);
|
||||
RejectAlbum(decisions);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var release = dbAlbum.AlbumReleases.Value.ExclusiveOrDefault(x => x.ForeignReleaseId == decisions.First().Item.Release.ForeignReleaseId);
|
||||
if (release == null)
|
||||
{
|
||||
RejectAlbum(decisions);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Populate the new DB album
|
||||
foreach (var decision in decisions)
|
||||
{
|
||||
decision.Item.Album = dbAlbum;
|
||||
decision.Item.Release = release;
|
||||
var trackIds = decision.Item.Tracks.Select(x => x.ForeignTrackId).ToList();
|
||||
decision.Item.Tracks = release.Tracks.Value.Where(x => trackIds.Contains(x.ForeignTrackId)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
return album;
|
||||
}
|
||||
|
||||
private void RejectAlbum(List<ImportDecision<LocalTrack>> decisions)
|
||||
{
|
||||
foreach (var decision in decisions)
|
||||
{
|
||||
decision.Reject(new Rejection("Failed to add missing album", RejectionType.Temporary));
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveExistingTrackFiles(Artist artist, Album album)
|
||||
{
|
||||
var rootFolder = _diskProvider.GetParentFolder(artist.Path);
|
||||
var previousFiles = _mediaFileService.GetFilesByAlbum(album.Id);
|
||||
|
||||
_logger.Debug($"Deleting {previousFiles.Count} existing files for {album}");
|
||||
|
||||
foreach (var previousFile in previousFiles)
|
||||
{
|
||||
var subfolder = rootFolder.GetRelativePath(_diskProvider.GetParentFolder(previousFile.Path));
|
||||
if (_diskProvider.FileExists(previousFile.Path))
|
||||
{
|
||||
_logger.Debug("Removing existing track file: {0}", previousFile);
|
||||
_recycleBinProvider.DeleteFile(previousFile.Path, subfolder);
|
||||
}
|
||||
|
||||
_mediaFileService.Delete(previousFile, DeleteMediaFileReason.Upgrade);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSceneReleaseName(DownloadClientItem downloadClientItem)
|
||||
{
|
||||
if (downloadClientItem != null)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||
{
|
||||
public class ImportArtistDefaults
|
||||
{
|
||||
public int MetadataProfileId { get; set; }
|
||||
public int LanguageProfileId { get; set; }
|
||||
public int QualityProfileId { get; set; }
|
||||
public bool AlbumFolder { get; set; }
|
||||
public MonitorTypes Monitored { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
}
|
||||
}
|
|
@ -3,23 +3,44 @@ using System.Collections.Generic;
|
|||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Profiles.Qualities;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||
{
|
||||
public interface IMakeImportDecision
|
||||
{
|
||||
List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, FilterFilesType filter, bool includeExisting);
|
||||
List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo);
|
||||
List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, Album album, AlbumRelease albumRelease, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo, FilterFilesType filter, bool newDownload, bool singleRelease, bool includeExisting);
|
||||
List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, IdentificationOverrides idOverrides, ImportDecisionMakerInfo itemInfo, ImportDecisionMakerConfig config);
|
||||
}
|
||||
|
||||
public class IdentificationOverrides
|
||||
{
|
||||
public Artist Artist { get; set; }
|
||||
public Album Album { get; set; }
|
||||
public AlbumRelease AlbumRelease { get; set; }
|
||||
}
|
||||
|
||||
public class ImportDecisionMakerInfo
|
||||
{
|
||||
public DownloadClientItem DownloadClientItem { get; set; }
|
||||
public ParsedTrackInfo ParsedTrackInfo { get; set; }
|
||||
}
|
||||
|
||||
public class ImportDecisionMakerConfig
|
||||
{
|
||||
public FilterFilesType Filter { get; set; }
|
||||
public bool NewDownload { get; set; }
|
||||
public bool SingleRelease { get; set; }
|
||||
public bool IncludeExisting { get; set; }
|
||||
public bool AddNewArtists { get; set; }
|
||||
}
|
||||
|
||||
public class ImportDecisionMaker : IMakeImportDecision
|
||||
|
@ -30,10 +51,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IAugmentingService _augmentingService;
|
||||
private readonly IIdentificationService _identificationService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IReleaseService _releaseService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly IProfileService _qualityProfileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification<LocalTrack>> trackSpecifications,
|
||||
|
@ -42,10 +61,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
IAudioTagService audioTagService,
|
||||
IAugmentingService augmentingService,
|
||||
IIdentificationService identificationService,
|
||||
IAlbumService albumService,
|
||||
IReleaseService releaseService,
|
||||
IEventAggregator eventAggregator,
|
||||
IDiskProvider diskProvider,
|
||||
IRootFolderService rootFolderService,
|
||||
IProfileService qualityProfileService,
|
||||
Logger logger)
|
||||
{
|
||||
_trackSpecifications = trackSpecifications;
|
||||
|
@ -54,29 +71,17 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
_audioTagService = audioTagService;
|
||||
_augmentingService = augmentingService;
|
||||
_identificationService = identificationService;
|
||||
_albumService = albumService;
|
||||
_releaseService = releaseService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_diskProvider = diskProvider;
|
||||
_rootFolderService = rootFolderService;
|
||||
_qualityProfileService = qualityProfileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, FilterFilesType filter, bool includeExisting)
|
||||
{
|
||||
return GetImportDecisions(musicFiles, artist, null, null, null, null, filter, false, false, true);
|
||||
}
|
||||
|
||||
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo)
|
||||
{
|
||||
return GetImportDecisions(musicFiles, artist, null, null, downloadClientItem, folderInfo, FilterFilesType.None, true, false, false);
|
||||
}
|
||||
|
||||
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, Artist artist, Album album, AlbumRelease albumRelease, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo, FilterFilesType filter, bool newDownload, bool singleRelease, bool includeExisting)
|
||||
public Tuple<List<LocalTrack>, List<ImportDecision<LocalTrack>>> GetLocalTracks(List<IFileInfo> musicFiles, DownloadClientItem downloadClientItem, ParsedTrackInfo folderInfo, FilterFilesType filter)
|
||||
{
|
||||
var watch = new System.Diagnostics.Stopwatch();
|
||||
watch.Start();
|
||||
|
||||
var files = filter != FilterFilesType.None && (artist != null) ? _mediaFileService.FilterUnchangedFiles(musicFiles, artist, filter) : musicFiles;
|
||||
var files = _mediaFileService.FilterUnchangedFiles(musicFiles, filter);
|
||||
|
||||
var localTracks = new List<LocalTrack>();
|
||||
var decisions = new List<ImportDecision<LocalTrack>>();
|
||||
|
@ -85,7 +90,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
|
||||
if (!files.Any())
|
||||
{
|
||||
return decisions;
|
||||
return Tuple.Create(localTracks, decisions);
|
||||
}
|
||||
|
||||
ParsedAlbumInfo downloadClientItemInfo = null;
|
||||
|
@ -95,19 +100,19 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
downloadClientItemInfo = Parser.Parser.ParseAlbumTitle(downloadClientItem.Title);
|
||||
}
|
||||
|
||||
int i = 1;
|
||||
foreach (var file in files)
|
||||
{
|
||||
_logger.ProgressInfo($"Reading file {i++}/{files.Count}");
|
||||
|
||||
var localTrack = new LocalTrack
|
||||
{
|
||||
Artist = artist,
|
||||
Album = album,
|
||||
DownloadClientAlbumInfo = downloadClientItemInfo,
|
||||
FolderTrackInfo = folderInfo,
|
||||
Path = file.FullName,
|
||||
Size = file.Length,
|
||||
Modified = file.LastWriteTimeUtc,
|
||||
FileTrackInfo = _audioTagService.ReadTags(file.FullName),
|
||||
ExistingFile = !newDownload,
|
||||
AdditionalFile = false
|
||||
};
|
||||
|
||||
|
@ -131,18 +136,36 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
|
||||
_logger.Debug($"Tags parsed for {files.Count} files in {watch.ElapsedMilliseconds}ms");
|
||||
|
||||
var releases = _identificationService.Identify(localTracks, artist, album, albumRelease, newDownload, singleRelease, includeExisting);
|
||||
return Tuple.Create(localTracks, decisions);
|
||||
}
|
||||
|
||||
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, IdentificationOverrides idOverrides, ImportDecisionMakerInfo itemInfo, ImportDecisionMakerConfig config)
|
||||
{
|
||||
idOverrides = idOverrides ?? new IdentificationOverrides();
|
||||
itemInfo = itemInfo ?? new ImportDecisionMakerInfo();
|
||||
|
||||
var trackData = GetLocalTracks(musicFiles, itemInfo.DownloadClientItem, itemInfo.ParsedTrackInfo, config.Filter);
|
||||
var localTracks = trackData.Item1;
|
||||
var decisions = trackData.Item2;
|
||||
|
||||
localTracks.ForEach(x => x.ExistingFile = !config.NewDownload);
|
||||
|
||||
var releases = _identificationService.Identify(localTracks, idOverrides, config);
|
||||
|
||||
foreach (var release in releases)
|
||||
{
|
||||
release.NewDownload = newDownload;
|
||||
var releaseDecision = GetDecision(release, downloadClientItem);
|
||||
// make sure the appropriate quality profile is set for the release artist
|
||||
// in case it's a new artist
|
||||
EnsureData(release);
|
||||
release.NewDownload = config.NewDownload;
|
||||
|
||||
var releaseDecision = GetDecision(release, itemInfo.DownloadClientItem);
|
||||
|
||||
foreach (var localTrack in release.LocalTracks)
|
||||
{
|
||||
if (releaseDecision.Approved)
|
||||
{
|
||||
decisions.AddIfNotNull(GetDecision(localTrack, downloadClientItem));
|
||||
decisions.AddIfNotNull(GetDecision(localTrack, itemInfo.DownloadClientItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -154,6 +177,19 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
return decisions;
|
||||
}
|
||||
|
||||
private void EnsureData(LocalAlbumRelease release)
|
||||
{
|
||||
if (release.AlbumRelease != null && release.AlbumRelease.Album.Value.Artist.Value.QualityProfileId == 0)
|
||||
{
|
||||
var rootFolder = _rootFolderService.GetBestRootFolder(release.LocalTracks.First().Path);
|
||||
var qualityProfile = _qualityProfileService.Get(rootFolder.DefaultQualityProfileId);
|
||||
|
||||
var artist = release.AlbumRelease.Album.Value.Artist.Value;
|
||||
artist.QualityProfileId = qualityProfile.Id;
|
||||
artist.QualityProfile = qualityProfile;
|
||||
}
|
||||
}
|
||||
|
||||
private ImportDecision<LocalAlbumRelease> GetDecision(LocalAlbumRelease localAlbumRelease, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
ImportDecision<LocalAlbumRelease> decision = null;
|
||||
|
|
|
@ -15,7 +15,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string Name { get; set; }
|
||||
public long Size { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
|
|
|
@ -9,6 +9,7 @@ using NzbDrone.Common.Crypto;
|
|||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
@ -16,6 +17,7 @@ using NzbDrone.Core.Messaging.Events;
|
|||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||
{
|
||||
|
@ -29,6 +31,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly IArtistService _artistService;
|
||||
|
@ -44,6 +47,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
|
||||
public ManualImportService(IDiskProvider diskProvider,
|
||||
IParsingService parsingService,
|
||||
IRootFolderService rootFolderService,
|
||||
IDiskScanService diskScanService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
IArtistService artistService,
|
||||
|
@ -59,6 +63,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
{
|
||||
_diskProvider = diskProvider;
|
||||
_parsingService = parsingService;
|
||||
_rootFolderService = rootFolderService;
|
||||
_diskScanService = diskScanService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_artistService = artistService;
|
||||
|
@ -94,8 +99,19 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
return new List<ManualImportItem>();
|
||||
}
|
||||
|
||||
var decision = _importDecisionMaker.GetImportDecisions(new List<IFileInfo> { _diskProvider.GetFileInfo(path) }, null, null, null, null, null, FilterFilesType.None, true, false, !replaceExistingFiles);
|
||||
var result = MapItem(decision.First(), Path.GetDirectoryName(path), downloadId, replaceExistingFiles, false);
|
||||
var files = new List<IFileInfo> { _diskProvider.GetFileInfo(path) };
|
||||
|
||||
var config = new ImportDecisionMakerConfig
|
||||
{
|
||||
Filter = FilterFilesType.None,
|
||||
NewDownload = true,
|
||||
SingleRelease = false,
|
||||
IncludeExisting = !replaceExistingFiles,
|
||||
AddNewArtists = false
|
||||
};
|
||||
|
||||
var decision = _importDecisionMaker.GetImportDecisions(files, null, null, config);
|
||||
var result = MapItem(decision.First(), downloadId, replaceExistingFiles, false);
|
||||
|
||||
return new List<ManualImportItem> { result };
|
||||
}
|
||||
|
@ -120,9 +136,26 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
}
|
||||
}
|
||||
|
||||
var folderInfo = Parser.Parser.ParseMusicTitle(directoryInfo.Name);
|
||||
var artistFiles = _diskScanService.GetAudioFiles(folder).ToList();
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(artistFiles, artist, null, null, downloadClientItem, folderInfo, filter, true, false, !replaceExistingFiles);
|
||||
var idOverrides = new IdentificationOverrides
|
||||
{
|
||||
Artist = artist
|
||||
};
|
||||
var itemInfo = new ImportDecisionMakerInfo
|
||||
{
|
||||
DownloadClientItem = downloadClientItem,
|
||||
ParsedTrackInfo = Parser.Parser.ParseMusicTitle(directoryInfo.Name)
|
||||
};
|
||||
var config = new ImportDecisionMakerConfig
|
||||
{
|
||||
Filter = filter,
|
||||
NewDownload = true,
|
||||
SingleRelease = false,
|
||||
IncludeExisting = !replaceExistingFiles,
|
||||
AddNewArtists = false
|
||||
};
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(artistFiles, idOverrides, itemInfo, config);
|
||||
|
||||
// paths will be different for new and old files which is why we need to map separately
|
||||
var newFiles = artistFiles.Join(decisions,
|
||||
|
@ -131,9 +164,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
(f, d) => new { File = f, Decision = d },
|
||||
PathEqualityComparer.Instance);
|
||||
|
||||
var newItems = newFiles.Select(x => MapItem(x.Decision, folder, downloadId, replaceExistingFiles, false));
|
||||
var newItems = newFiles.Select(x => MapItem(x.Decision, downloadId, replaceExistingFiles, false));
|
||||
var existingDecisions = decisions.Except(newFiles.Select(x => x.Decision));
|
||||
var existingItems = existingDecisions.Select(x => MapItem(x, x.Item.Artist.Path, null, replaceExistingFiles, false));
|
||||
var existingItems = existingDecisions.Select(x => MapItem(x, null, replaceExistingFiles, false));
|
||||
|
||||
return newItems.Concat(existingItems).ToList();
|
||||
}
|
||||
|
@ -152,7 +185,22 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
|
||||
var disableReleaseSwitching = group.First().DisableReleaseSwitching;
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(group.Select(x => _diskProvider.GetFileInfo(x.Path)).ToList(), group.First().Artist, group.First().Album, group.First().Release, null, null, FilterFilesType.None, true, true, !replaceExistingFiles);
|
||||
var files = group.Select(x => _diskProvider.GetFileInfo(x.Path)).ToList();
|
||||
var idOverride = new IdentificationOverrides
|
||||
{
|
||||
Artist = group.First().Artist,
|
||||
Album = group.First().Album,
|
||||
AlbumRelease = group.First().Release
|
||||
};
|
||||
var config = new ImportDecisionMakerConfig
|
||||
{
|
||||
Filter = FilterFilesType.None,
|
||||
NewDownload = true,
|
||||
SingleRelease = true,
|
||||
IncludeExisting = !replaceExistingFiles,
|
||||
AddNewArtists = false
|
||||
};
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(files, idOverride, null, config);
|
||||
|
||||
var existingItems = group.Join(decisions,
|
||||
i => i.Path,
|
||||
|
@ -187,19 +235,18 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
}
|
||||
|
||||
var newDecisions = decisions.Except(existingItems.Select(x => x.Decision));
|
||||
result.AddRange(newDecisions.Select(x => MapItem(x, x.Item.Artist.Path, null, replaceExistingFiles, disableReleaseSwitching)));
|
||||
result.AddRange(newDecisions.Select(x => MapItem(x, null, replaceExistingFiles, disableReleaseSwitching)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ManualImportItem MapItem(ImportDecision<LocalTrack> decision, string folder, string downloadId, bool replaceExistingFiles, bool disableReleaseSwitching)
|
||||
private ManualImportItem MapItem(ImportDecision<LocalTrack> decision, string downloadId, bool replaceExistingFiles, bool disableReleaseSwitching)
|
||||
{
|
||||
var item = new ManualImportItem();
|
||||
|
||||
item.Id = HashConverter.GetHashInt31(decision.Item.Path);
|
||||
item.Path = decision.Item.Path;
|
||||
item.RelativePath = folder.GetRelativePath(decision.Item.Path);
|
||||
item.Name = Path.GetFileNameWithoutExtension(decision.Item.Path);
|
||||
item.DownloadId = downloadId;
|
||||
|
||||
|
@ -276,7 +323,14 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
Release = release
|
||||
};
|
||||
|
||||
albumImportDecisions.Add(new ImportDecision<LocalTrack>(localTrack));
|
||||
var importDecision = new ImportDecision<LocalTrack>(localTrack);
|
||||
if (_rootFolderService.GetBestRootFolder(artist.Path) == null)
|
||||
{
|
||||
_logger.Warn($"Destination artist folder {artist.Path} not in a Root Folder, skipping import");
|
||||
importDecision.Reject(new Rejection($"Destination artist folder {artist.Path} is not in a Root Folder"));
|
||||
}
|
||||
|
||||
albumImportDecisions.Add(importDecision);
|
||||
fileCount += 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,9 +18,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Specifications
|
|||
|
||||
public Decision IsSatisfiedBy(LocalAlbumRelease item, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var artist = item.AlbumRelease.Album.Value.Artist.Value;
|
||||
var qualityComparer = new QualityModelComparer(artist.QualityProfile);
|
||||
|
||||
// check if we are changing release
|
||||
var currentRelease = item.AlbumRelease.Album.Value.AlbumReleases.Value.Single(x => x.Monitored);
|
||||
var newRelease = item.AlbumRelease;
|
||||
|
@ -28,6 +25,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Specifications
|
|||
// if we are, check we are upgrading
|
||||
if (newRelease.Id != currentRelease.Id)
|
||||
{
|
||||
var qualityComparer = new QualityModelComparer(item.AlbumRelease.Album.Value.Artist.Value.QualityProfile);
|
||||
|
||||
// min quality of all new tracks
|
||||
var newMinQuality = item.LocalTracks.Select(x => x.Quality).OrderBy(x => x, qualityComparer).First();
|
||||
_logger.Debug("Min quality of new files: {0}", newMinQuality);
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport.Specifications
|
||||
{
|
||||
public class ArtistPathInRootFolderSpecification : IImportDecisionEngineSpecification<LocalAlbumRelease>
|
||||
{
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ArtistPathInRootFolderSpecification(IRootFolderService rootFolderService,
|
||||
Logger logger)
|
||||
{
|
||||
_rootFolderService = rootFolderService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalAlbumRelease item, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
// Prevent imports to artists that are no longer inside a root folder Lidarr manages
|
||||
var artist = item.AlbumRelease.Album.Value.Artist.Value;
|
||||
|
||||
// a new artist will have empty path, and will end up having path assinged based on file location
|
||||
var pathToCheck = artist.Path.IsNotNullOrWhiteSpace() ? artist.Path : item.LocalTracks.First().Path.GetParentPath();
|
||||
|
||||
if (_rootFolderService.GetBestRootFolder(pathToCheck) == null)
|
||||
{
|
||||
_logger.Warn($"Destination folder {pathToCheck} not in a Root Folder, skipping import");
|
||||
return Decision.Reject($"Destination folder {pathToCheck} is not in a Root Folder");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,12 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Specifications
|
|||
|
||||
public Decision IsSatisfiedBy(LocalTrack item, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
if (!item.Tracks.Any(e => e.TrackFileId > 0))
|
||||
{
|
||||
// No existing tracks, skip. This guards against new artists not having a QualityProfile.
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
|
||||
var qualityComparer = new QualityModelComparer(item.Artist.QualityProfile);
|
||||
|
||||
|
|
|
@ -6,5 +6,6 @@ namespace NzbDrone.Core.MetadataSource
|
|||
public interface ISearchForNewAlbum
|
||||
{
|
||||
List<Album> SearchForNewAlbum(string title, string artist);
|
||||
List<Album> SearchForNewAlbumByRecordingIds(List<string> recordingIds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ using System.Net;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
|
||||
|
@ -21,7 +21,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
private readonly IArtistService _artistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IMetadataRequestBuilder _requestBuilder;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IMetadataProfileService _metadataProfileService;
|
||||
|
||||
private static readonly List<string> NonAudioMedia = new List<string> { "DVD", "DVD-Video", "Blu-ray", "HD-DVD", "VCD", "SVCD", "UMD", "VHS" };
|
||||
|
@ -32,11 +31,9 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
Logger logger,
|
||||
IConfigService configService,
|
||||
IMetadataProfileService metadataProfileService)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_configService = configService;
|
||||
_metadataProfileService = metadataProfileService;
|
||||
_requestBuilder = requestBuilder;
|
||||
_artistService = artistService;
|
||||
|
@ -259,6 +256,21 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
}
|
||||
}
|
||||
|
||||
public List<Album> SearchForNewAlbumByRecordingIds(List<string> recordingIds)
|
||||
{
|
||||
var ids = recordingIds.Where(x => x.IsNotNullOrWhiteSpace()).Distinct();
|
||||
var httpRequest = _requestBuilder.GetRequestBuilder().Create()
|
||||
.SetSegment("route", "search/fingerprint")
|
||||
.Build();
|
||||
|
||||
httpRequest.SetContent(ids.ToJson());
|
||||
httpRequest.Headers.ContentType = "application/json";
|
||||
|
||||
var httpResponse = _httpClient.Post<List<AlbumResource>>(httpRequest);
|
||||
|
||||
return httpResponse.Resource.SelectList(MapSearchResult);
|
||||
}
|
||||
|
||||
public List<object> SearchForNewEntity(string title)
|
||||
{
|
||||
try
|
||||
|
@ -351,6 +363,17 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
if (resource.Releases != null)
|
||||
{
|
||||
album.AlbumReleases = resource.Releases.Select(x => MapRelease(x, artistDict)).Where(x => x.TrackCount > 0).ToList();
|
||||
|
||||
// Monitor the release with most tracks
|
||||
var mostTracks = album.AlbumReleases.Value.OrderByDescending(x => x.TrackCount).FirstOrDefault();
|
||||
if (mostTracks != null)
|
||||
{
|
||||
mostTracks.Monitored = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
album.AlbumReleases = new List<AlbumRelease>();
|
||||
}
|
||||
|
||||
album.AnyReleaseOk = true;
|
||||
|
|
25
src/NzbDrone.Core/Music/Commands/BulkRefreshArtistCommand.cs
Normal file
25
src/NzbDrone.Core/Music/Commands/BulkRefreshArtistCommand.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.Music.Commands
|
||||
{
|
||||
public class BulkRefreshArtistCommand : Command
|
||||
{
|
||||
public BulkRefreshArtistCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public BulkRefreshArtistCommand(List<int> artistIds, bool areNewArtists = false)
|
||||
{
|
||||
ArtistIds = artistIds;
|
||||
AreNewArtists = areNewArtists;
|
||||
}
|
||||
|
||||
public List<int> ArtistIds { get; set; }
|
||||
public bool AreNewArtists { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
|
||||
public override bool UpdateScheduledTask => false;
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ namespace NzbDrone.Core.Music
|
|||
private readonly IArtistService _artistService;
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly IAlbumAddedService _albumAddedService;
|
||||
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ArtistScannedHandler(IAlbumMonitoredService albumMonitoredService,
|
||||
|
|
|
@ -6,6 +6,7 @@ using FluentValidation;
|
|||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Organizer;
|
||||
|
@ -115,12 +116,35 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
private Artist SetPropertiesAndValidate(Artist newArtist)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(newArtist.Path))
|
||||
var path = newArtist.Path;
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
var folderName = _fileNameBuilder.GetArtistFolder(newArtist);
|
||||
newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName);
|
||||
path = Path.Combine(newArtist.RootFolderPath, folderName);
|
||||
}
|
||||
|
||||
// Disambiguate artist path if it exists already
|
||||
if (_artistService.ArtistPathExists(path))
|
||||
{
|
||||
if (newArtist.Metadata.Value.Disambiguation.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
path += $" ({newArtist.Metadata.Value.Disambiguation})";
|
||||
}
|
||||
|
||||
if (_artistService.ArtistPathExists(path))
|
||||
{
|
||||
var basepath = path;
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
i++;
|
||||
path = basepath + $" ({i})";
|
||||
}
|
||||
while (_artistService.ArtistPathExists(path));
|
||||
}
|
||||
}
|
||||
|
||||
newArtist.Path = path;
|
||||
newArtist.CleanName = newArtist.Metadata.Value.Name.CleanArtistName();
|
||||
newArtist.SortName = Parser.Parser.NormalizeTitle(newArtist.Metadata.Value.Name).ToLower();
|
||||
newArtist.Added = DateTime.UtcNow;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
@ -34,7 +33,7 @@ namespace NzbDrone.Core.Music
|
|||
tracks.ForEach(x => x.TrackFileId = 0);
|
||||
_trackService.SetFileIds(tracks);
|
||||
|
||||
_commandQueueManager.Push(new RescanArtistCommand(message.Album.ArtistId, FilterFilesType.Matched));
|
||||
_commandQueueManager.Push(new RescanFoldersCommand());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,10 +144,6 @@ namespace NzbDrone.Core.Music
|
|||
.OrderByDescending(s => s.MatchProb)
|
||||
.ToList();
|
||||
|
||||
_logger.Trace("\nFuzzy album match on '{0}':\n{1}",
|
||||
title,
|
||||
string.Join("\n", sortedAlbums.Select(x => $"[{x.Album.Title}] {x.Album.CleanTitle}: {x.MatchProb}")));
|
||||
|
||||
return sortedAlbums.TakeWhile((x, i) => i == 0 || sortedAlbums[i - 1].MatchProb - x.MatchProb < fuzzGap)
|
||||
.TakeWhile((x, i) => x.MatchProb > fuzzThreshold || (i > 0 && sortedAlbums[i - 1].MatchProb > fuzzThreshold))
|
||||
.Select(x => x.Album)
|
||||
|
|
|
@ -99,6 +99,7 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
tc((a, t) => a.CleanName.FuzzyMatch(t), cleanTitle),
|
||||
tc((a, t) => a.Name.FuzzyMatch(t), title),
|
||||
tc((a, t) => a.Metadata.Value.Aliases.Concat(new List<string> { a.Name }).Max(x => x.CleanArtistName().FuzzyMatch(t)), cleanTitle),
|
||||
};
|
||||
|
||||
if (title.StartsWith("The ", StringComparison.CurrentCultureIgnoreCase))
|
||||
|
@ -156,10 +157,6 @@ namespace NzbDrone.Core.Music
|
|||
.OrderByDescending(s => s.MatchProb)
|
||||
.ToList();
|
||||
|
||||
_logger.Trace("\nFuzzy artist match on '{0}':\n{1}",
|
||||
title,
|
||||
string.Join("\n", sortedArtists.Select(x => $"[{x.Artist.Name}] {x.Artist.CleanName}: {x.MatchProb}")));
|
||||
|
||||
return sortedArtists.TakeWhile((x, i) => i == 0 || sortedArtists[i - 1].MatchProb - x.MatchProb < fuzzGap)
|
||||
.TakeWhile((x, i) => x.MatchProb > fuzzThreshold || (i > 0 && sortedArtists[i - 1].MatchProb > fuzzThreshold))
|
||||
.Select(x => x.Artist)
|
||||
|
|
|
@ -11,24 +11,29 @@ using NzbDrone.Core.Exceptions;
|
|||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.ImportLists.Exclusions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Music.Commands;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public class RefreshArtistService : RefreshEntityServiceBase<Artist, Album>, IExecute<RefreshArtistCommand>
|
||||
public class RefreshArtistService : RefreshEntityServiceBase<Artist, Album>,
|
||||
IExecute<RefreshArtistCommand>,
|
||||
IExecute<BulkRefreshArtistCommand>
|
||||
{
|
||||
private readonly IProvideArtistInfo _artistInfo;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IRefreshAlbumService _refreshAlbumService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IImportListExclusionService _importListExclusionService;
|
||||
|
@ -40,9 +45,10 @@ namespace NzbDrone.Core.Music
|
|||
IAlbumService albumService,
|
||||
IRefreshAlbumService refreshAlbumService,
|
||||
IEventAggregator eventAggregator,
|
||||
IManageCommandQueue commandQueueManager,
|
||||
IMediaFileService mediaFileService,
|
||||
IHistoryService historyService,
|
||||
IDiskScanService diskScanService,
|
||||
IRootFolderService rootFolderService,
|
||||
ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed,
|
||||
IConfigService configService,
|
||||
IImportListExclusionService importListExclusionService,
|
||||
|
@ -54,9 +60,10 @@ namespace NzbDrone.Core.Music
|
|||
_albumService = albumService;
|
||||
_refreshAlbumService = refreshAlbumService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_commandQueueManager = commandQueueManager;
|
||||
_mediaFileService = mediaFileService;
|
||||
_historyService = historyService;
|
||||
_diskScanService = diskScanService;
|
||||
_rootFolderService = rootFolderService;
|
||||
_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed;
|
||||
_configService = configService;
|
||||
_importListExclusionService = importListExclusionService;
|
||||
|
@ -258,24 +265,24 @@ namespace NzbDrone.Core.Music
|
|||
_eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(entity, newChildren, updateChildren));
|
||||
}
|
||||
|
||||
private void RescanArtist(Artist artist, bool isNew, CommandTrigger trigger, bool infoUpdated)
|
||||
private void Rescan(List<int> artistIds, bool isNew, CommandTrigger trigger, bool infoUpdated)
|
||||
{
|
||||
var rescanAfterRefresh = _configService.RescanAfterRefresh;
|
||||
var shouldRescan = true;
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
_logger.Trace("Forcing rescan of {0}. Reason: New artist", artist);
|
||||
_logger.Trace("Forcing rescan. Reason: New artist added");
|
||||
shouldRescan = true;
|
||||
}
|
||||
else if (rescanAfterRefresh == RescanAfterRefreshType.Never)
|
||||
{
|
||||
_logger.Trace("Skipping rescan of {0}. Reason: never recan after refresh", artist);
|
||||
_logger.Trace("Skipping rescan. Reason: never rescan after refresh");
|
||||
shouldRescan = false;
|
||||
}
|
||||
else if (rescanAfterRefresh == RescanAfterRefreshType.AfterManual && trigger != CommandTrigger.Manual)
|
||||
{
|
||||
_logger.Trace("Skipping rescan of {0}. Reason: not after automatic scans", artist);
|
||||
_logger.Trace("Skipping rescan. Reason: not after automatic refreshes");
|
||||
shouldRescan = false;
|
||||
}
|
||||
|
||||
|
@ -289,14 +296,43 @@ namespace NzbDrone.Core.Music
|
|||
// If some metadata has been updated then rescan unmatched files.
|
||||
// Otherwise only scan files that haven't been seen before.
|
||||
var filter = infoUpdated ? FilterFilesType.Matched : FilterFilesType.Known;
|
||||
_diskScanService.Scan(artist, filter);
|
||||
_logger.Trace($"InfoUpdated: {infoUpdated}, using scan filter {filter}");
|
||||
|
||||
var folders = _rootFolderService.All().Select(x => x.Path).ToList();
|
||||
|
||||
_commandQueueManager.Push(new RescanFoldersCommand(folders, filter, artistIds));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't rescan artist {0}", artist);
|
||||
_logger.Error(e, "Couldn't rescan");
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshSelectedArtists(List<int> artistIds, bool isNew, CommandTrigger trigger)
|
||||
{
|
||||
bool updated = false;
|
||||
var artists = _artistService.GetArtists(artistIds);
|
||||
|
||||
foreach (var artist in artists)
|
||||
{
|
||||
try
|
||||
{
|
||||
updated |= RefreshEntityInfo(artist, null, true, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't refresh info for {0}", artist);
|
||||
}
|
||||
}
|
||||
|
||||
Rescan(artistIds, isNew, trigger, updated);
|
||||
}
|
||||
|
||||
public void Execute(BulkRefreshArtistCommand message)
|
||||
{
|
||||
RefreshSelectedArtists(message.ArtistIds, message.AreNewArtists, message.Trigger);
|
||||
}
|
||||
|
||||
public void Execute(RefreshArtistCommand message)
|
||||
{
|
||||
var trigger = message.Trigger;
|
||||
|
@ -304,49 +340,36 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
if (message.ArtistId.HasValue)
|
||||
{
|
||||
var artist = _artistService.GetArtist(message.ArtistId.Value);
|
||||
bool updated = false;
|
||||
try
|
||||
{
|
||||
updated = RefreshEntityInfo(artist, null, true, false);
|
||||
_logger.Trace($"Artist {artist} updated: {updated}");
|
||||
RescanArtist(artist, isNew, trigger, updated);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't refresh info for {0}", artist);
|
||||
RescanArtist(artist, isNew, trigger, updated);
|
||||
throw;
|
||||
}
|
||||
RefreshSelectedArtists(new List<int> { message.ArtistId.Value }, isNew, trigger);
|
||||
}
|
||||
else
|
||||
{
|
||||
var allArtists = _artistService.GetAllArtists().OrderBy(c => c.Name).ToList();
|
||||
var updated = false;
|
||||
var artists = _artistService.GetAllArtists().OrderBy(c => c.Name).ToList();
|
||||
var artistIds = artists.Select(x => x.Id).ToList();
|
||||
|
||||
foreach (var artist in allArtists)
|
||||
foreach (var artist in artists)
|
||||
{
|
||||
var manualTrigger = message.Trigger == CommandTrigger.Manual;
|
||||
|
||||
if (manualTrigger || _checkIfArtistShouldBeRefreshed.ShouldRefresh(artist))
|
||||
{
|
||||
bool updated = false;
|
||||
try
|
||||
{
|
||||
updated = RefreshEntityInfo(artist, null, manualTrigger, false);
|
||||
updated |= RefreshEntityInfo(artist, null, manualTrigger, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't refresh info for {0}", artist);
|
||||
}
|
||||
|
||||
RescanArtist(artist, false, trigger, updated);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Skipping refresh of artist: {0}", artist.Name);
|
||||
RescanArtist(artist, false, trigger, false);
|
||||
}
|
||||
}
|
||||
|
||||
Rescan(artistIds, isNew, trigger, updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace NzbDrone.Core.Music
|
|||
public AddArtistValidator(RootFolderValidator rootFolderValidator,
|
||||
ArtistPathValidator artistPathValidator,
|
||||
ArtistAncestorValidator artistAncestorValidator,
|
||||
ProfileExistsValidator profileExistsValidator,
|
||||
QualityProfileExistsValidator qualityProfileExistsValidator,
|
||||
MetadataProfileExistsValidator metadataProfileExistsValidator)
|
||||
{
|
||||
RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure)
|
||||
|
@ -24,7 +24,7 @@ namespace NzbDrone.Core.Music
|
|||
.SetValidator(artistPathValidator)
|
||||
.SetValidator(artistAncestorValidator);
|
||||
|
||||
RuleFor(c => c.QualityProfileId).SetValidator(profileExistsValidator);
|
||||
RuleFor(c => c.QualityProfileId).SetValidator(qualityProfileExistsValidator);
|
||||
|
||||
RuleFor(c => c.MetadataProfileId).SetValidator(metadataProfileExistsValidator);
|
||||
}
|
||||
|
|
|
@ -326,9 +326,11 @@ namespace NzbDrone.Core.Parser
|
|||
return null;
|
||||
}
|
||||
|
||||
var artistName = artist.Name == "Various Artists" ? "VA" : artist.Name.RemoveAccent();
|
||||
|
||||
Logger.Debug("Parsing string '{0}' using search criteria artist: '{1}' album: '{2}'",
|
||||
title,
|
||||
artist.Name.RemoveAccent(),
|
||||
artistName.RemoveAccent(),
|
||||
string.Join(", ", album.Select(a => a.Title.RemoveAccent())));
|
||||
|
||||
var releaseTitle = RemoveFileExtension(title);
|
||||
|
@ -339,7 +341,7 @@ namespace NzbDrone.Core.Parser
|
|||
|
||||
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle);
|
||||
|
||||
var escapedArtist = Regex.Escape(artist.Name.RemoveAccent()).Replace(@"\ ", @"[\W_]");
|
||||
var escapedArtist = Regex.Escape(artistName.RemoveAccent()).Replace(@"\ ", @"[\W_]");
|
||||
var escapedAlbums = string.Join("|", album.Select(s => Regex.Escape(s.Title.RemoveAccent())).ToList()).Replace(@"\ ", @"[\W_]");
|
||||
|
||||
var releaseRegex = new Regex(@"^(\W*|\b)(?<artist>" + escapedArtist + @")(\W*|\b).*(\W*|\b)(?<album>" + escapedAlbums + @")(\W*|\b)", RegexOptions.IgnoreCase);
|
||||
|
@ -492,10 +494,8 @@ namespace NzbDrone.Core.Parser
|
|||
|
||||
public static string CleanArtistName(this string name)
|
||||
{
|
||||
long number = 0;
|
||||
|
||||
//If Title only contains numbers return it as is.
|
||||
if (long.TryParse(name, out number))
|
||||
// If Title only contains numbers return it as is.
|
||||
if (long.TryParse(name, out _))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
@ -650,9 +650,6 @@ namespace NzbDrone.Core.Parser
|
|||
|
||||
artistName = artistName.Trim(' ');
|
||||
|
||||
int trackNumber;
|
||||
int.TryParse(matchCollection[0].Groups["trackNumber"].Value, out trackNumber);
|
||||
|
||||
ParsedTrackInfo result = new ParsedTrackInfo();
|
||||
|
||||
result.ArtistTitle = artistName;
|
||||
|
|
|
@ -6,6 +6,7 @@ using NzbDrone.Core.ImportLists;
|
|||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.Profiles.Metadata
|
||||
{
|
||||
|
@ -25,16 +26,19 @@ namespace NzbDrone.Core.Profiles.Metadata
|
|||
private readonly IMetadataProfileRepository _profileRepository;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IImportListFactory _importListFactory;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MetadataProfileService(IMetadataProfileRepository profileRepository,
|
||||
IArtistService artistService,
|
||||
IImportListFactory importListFactory,
|
||||
IRootFolderService rootFolderService,
|
||||
Logger logger)
|
||||
{
|
||||
_profileRepository = profileRepository;
|
||||
_artistService = artistService;
|
||||
_importListFactory = importListFactory;
|
||||
_rootFolderService = rootFolderService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -59,7 +63,8 @@ namespace NzbDrone.Core.Profiles.Metadata
|
|||
|
||||
if (profile.Name == NONE_PROFILE_NAME ||
|
||||
_artistService.GetAllArtists().Any(c => c.MetadataProfileId == id) ||
|
||||
_importListFactory.All().Any(c => c.MetadataProfileId == id))
|
||||
_importListFactory.All().Any(c => c.MetadataProfileId == id) ||
|
||||
_rootFolderService.All().Any(c => c.DefaultMetadataProfileId == id))
|
||||
{
|
||||
throw new MetadataProfileInUseException(profile.Name);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ using NzbDrone.Core.Lifecycle;
|
|||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.Profiles.Qualities
|
||||
{
|
||||
|
@ -25,13 +26,19 @@ namespace NzbDrone.Core.Profiles.Qualities
|
|||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IImportListFactory _importListFactory;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public QualityProfileService(IProfileRepository profileRepository, IArtistService artistService, IImportListFactory importListFactory, Logger logger)
|
||||
public QualityProfileService(IProfileRepository profileRepository,
|
||||
IArtistService artistService,
|
||||
IImportListFactory importListFactory,
|
||||
IRootFolderService rootFolderService,
|
||||
Logger logger)
|
||||
{
|
||||
_profileRepository = profileRepository;
|
||||
_artistService = artistService;
|
||||
_importListFactory = importListFactory;
|
||||
_rootFolderService = rootFolderService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -47,7 +54,9 @@ namespace NzbDrone.Core.Profiles.Qualities
|
|||
|
||||
public void Delete(int id)
|
||||
{
|
||||
if (_artistService.GetAllArtists().Any(c => c.QualityProfileId == id) || _importListFactory.All().Any(c => c.ProfileId == id))
|
||||
if (_artistService.GetAllArtists().Any(c => c.QualityProfileId == id) ||
|
||||
_importListFactory.All().Any(c => c.ProfileId == id) ||
|
||||
_rootFolderService.All().Any(c => c.DefaultQualityProfileId == id))
|
||||
{
|
||||
var profile = _profileRepository.Get(id);
|
||||
throw new QualityProfileInUseException(profile.Name);
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.RootFolders
|
||||
{
|
||||
public class RootFolder : ModelBase
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public int DefaultMetadataProfileId { get; set; }
|
||||
public int DefaultQualityProfileId { get; set; }
|
||||
public MonitorTypes DefaultMonitorOption { get; set; }
|
||||
public HashSet<int> DefaultTags { get; set; }
|
||||
|
||||
public bool Accessible { get; set; }
|
||||
public long? FreeSpace { get; set; }
|
||||
public long? TotalSpace { get; set; }
|
||||
|
||||
public List<UnmappedFolder> UnmappedFolders { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,5 +15,12 @@ namespace NzbDrone.Core.RootFolders
|
|||
}
|
||||
|
||||
protected override bool PublishModelEvents => true;
|
||||
|
||||
public new void Delete(int id)
|
||||
{
|
||||
var model = Get(id);
|
||||
base.Delete(id);
|
||||
ModelDeleted(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,17 +7,22 @@ using NLog;
|
|||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.RootFolders
|
||||
{
|
||||
public interface IRootFolderService
|
||||
{
|
||||
List<RootFolder> All();
|
||||
List<RootFolder> AllWithUnmappedFolders();
|
||||
RootFolder Add(RootFolder rootDir);
|
||||
List<RootFolder> AllWithSpaceStats();
|
||||
RootFolder Add(RootFolder rootFolder);
|
||||
RootFolder Update(RootFolder rootFolder);
|
||||
void Remove(int id);
|
||||
RootFolder Get(int id);
|
||||
List<RootFolder> AllForTag(int tagId);
|
||||
RootFolder GetBestRootFolder(string path);
|
||||
string GetBestRootFolderPath(string path);
|
||||
}
|
||||
|
||||
|
@ -25,30 +30,17 @@ namespace NzbDrone.Core.RootFolders
|
|||
{
|
||||
private readonly IRootFolderRepository _rootFolderRepository;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IArtistRepository _artistRepository;
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private static readonly HashSet<string> SpecialFolders = new HashSet<string>
|
||||
{
|
||||
"$recycle.bin",
|
||||
"system volume information",
|
||||
"recycler",
|
||||
"lost+found",
|
||||
".appledb",
|
||||
".appledesktop",
|
||||
".appledouble",
|
||||
"@eadir",
|
||||
".grab"
|
||||
};
|
||||
|
||||
public RootFolderService(IRootFolderRepository rootFolderRepository,
|
||||
IDiskProvider diskProvider,
|
||||
IArtistRepository artistRepository,
|
||||
IManageCommandQueue commandQueueManager,
|
||||
Logger logger)
|
||||
{
|
||||
_rootFolderRepository = rootFolderRepository;
|
||||
_diskProvider = diskProvider;
|
||||
_artistRepository = artistRepository;
|
||||
_commandQueueManager = commandQueueManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -59,7 +51,7 @@ namespace NzbDrone.Core.RootFolders
|
|||
return rootFolders;
|
||||
}
|
||||
|
||||
public List<RootFolder> AllWithUnmappedFolders()
|
||||
public List<RootFolder> AllWithSpaceStats()
|
||||
{
|
||||
var rootFolders = _rootFolderRepository.All().ToList();
|
||||
|
||||
|
@ -77,17 +69,14 @@ namespace NzbDrone.Core.RootFolders
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to get free space and unmapped folders for root folder {0}", folder.Path);
|
||||
folder.UnmappedFolders = new List<UnmappedFolder>();
|
||||
}
|
||||
});
|
||||
|
||||
return rootFolders;
|
||||
}
|
||||
|
||||
public RootFolder Add(RootFolder rootFolder)
|
||||
private void VerifyRootFolder(RootFolder rootFolder)
|
||||
{
|
||||
var all = All();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rootFolder.Path) || !Path.IsPathRooted(rootFolder.Path))
|
||||
{
|
||||
throw new ArgumentException("Invalid path");
|
||||
|
@ -98,18 +87,36 @@ namespace NzbDrone.Core.RootFolders
|
|||
throw new DirectoryNotFoundException("Can't add root directory that doesn't exist.");
|
||||
}
|
||||
|
||||
if (all.Exists(r => r.Path.PathEquals(rootFolder.Path)))
|
||||
{
|
||||
throw new InvalidOperationException("Recent directory already exists.");
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderWritable(rootFolder.Path))
|
||||
{
|
||||
throw new UnauthorizedAccessException(string.Format("Root folder path '{0}' is not writable by user '{1}'", rootFolder.Path, Environment.UserName));
|
||||
}
|
||||
}
|
||||
|
||||
public RootFolder Add(RootFolder rootFolder)
|
||||
{
|
||||
VerifyRootFolder(rootFolder);
|
||||
|
||||
if (All().Exists(r => r.Path.PathEquals(rootFolder.Path)))
|
||||
{
|
||||
throw new InvalidOperationException("Root folder already exists.");
|
||||
}
|
||||
|
||||
_rootFolderRepository.Insert(rootFolder);
|
||||
|
||||
_commandQueueManager.Push(new RescanFoldersCommand(new List<string> { rootFolder.Path }, FilterFilesType.None, null));
|
||||
|
||||
GetDetails(rootFolder);
|
||||
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
public RootFolder Update(RootFolder rootFolder)
|
||||
{
|
||||
VerifyRootFolder(rootFolder);
|
||||
|
||||
_rootFolderRepository.Update(rootFolder);
|
||||
|
||||
GetDetails(rootFolder);
|
||||
|
||||
return rootFolder;
|
||||
|
@ -120,40 +127,6 @@ namespace NzbDrone.Core.RootFolders
|
|||
_rootFolderRepository.Delete(id);
|
||||
}
|
||||
|
||||
private List<UnmappedFolder> GetUnmappedFolders(string path)
|
||||
{
|
||||
_logger.Debug("Generating list of unmapped folders");
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentException("Invalid path provided", nameof(path));
|
||||
}
|
||||
|
||||
var results = new List<UnmappedFolder>();
|
||||
var artist = _artistRepository.All().ToList();
|
||||
|
||||
if (!_diskProvider.FolderExists(path))
|
||||
{
|
||||
_logger.Debug("Path supplied does not exist: {0}", path);
|
||||
return results;
|
||||
}
|
||||
|
||||
var possibleArtistFolders = _diskProvider.GetDirectories(path).ToList();
|
||||
var unmappedFolders = possibleArtistFolders.Except(artist.Select(s => s.Path), PathEqualityComparer.Instance).ToList();
|
||||
|
||||
foreach (string unmappedFolder in unmappedFolders)
|
||||
{
|
||||
var di = new DirectoryInfo(unmappedFolder.Normalize());
|
||||
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName });
|
||||
}
|
||||
|
||||
var setToRemove = SpecialFolders;
|
||||
results.RemoveAll(x => setToRemove.Contains(new DirectoryInfo(x.Path.ToLowerInvariant()).Name));
|
||||
|
||||
_logger.Debug("{0} unmapped folders detected.", results.Count);
|
||||
return results.OrderBy(u => u.Name, StringComparer.InvariantCultureIgnoreCase).ToList();
|
||||
}
|
||||
|
||||
public RootFolder Get(int id)
|
||||
{
|
||||
var rootFolder = _rootFolderRepository.Get(id);
|
||||
|
@ -162,11 +135,21 @@ namespace NzbDrone.Core.RootFolders
|
|||
return rootFolder;
|
||||
}
|
||||
|
||||
public string GetBestRootFolderPath(string path)
|
||||
public List<RootFolder> AllForTag(int tagId)
|
||||
{
|
||||
var possibleRootFolder = All().Where(r => r.Path.IsParentPath(path))
|
||||
return All().Where(r => r.DefaultTags.Contains(tagId)).ToList();
|
||||
}
|
||||
|
||||
public RootFolder GetBestRootFolder(string path)
|
||||
{
|
||||
return All().Where(r => PathEqualityComparer.Instance.Equals(r.Path, path) || r.Path.IsParentPath(path))
|
||||
.OrderByDescending(r => r.Path.Length)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public string GetBestRootFolderPath(string path)
|
||||
{
|
||||
var possibleRootFolder = GetBestRootFolder(path);
|
||||
|
||||
if (possibleRootFolder == null)
|
||||
{
|
||||
|
@ -185,7 +168,6 @@ namespace NzbDrone.Core.RootFolders
|
|||
rootFolder.Accessible = true;
|
||||
rootFolder.FreeSpace = _diskProvider.GetAvailableSpace(rootFolder.Path);
|
||||
rootFolder.TotalSpace = _diskProvider.GetTotalSize(rootFolder.Path);
|
||||
rootFolder.UnmappedFolders = GetUnmappedFolders(rootFolder.Path);
|
||||
}
|
||||
}).Wait(5000);
|
||||
}
|
||||
|
|
|
@ -12,12 +12,13 @@ namespace NzbDrone.Core.Tags
|
|||
public List<int> RestrictionIds { get; set; }
|
||||
public List<int> DelayProfileIds { get; set; }
|
||||
public List<int> ImportListIds { get; set; }
|
||||
public List<int> RootFolderIds { get; set; }
|
||||
|
||||
public bool InUse
|
||||
{
|
||||
get
|
||||
{
|
||||
return ArtistIds.Any() || NotificationIds.Any() || RestrictionIds.Any() || DelayProfileIds.Any() || ImportListIds.Any();
|
||||
return ArtistIds.Any() || NotificationIds.Any() || RestrictionIds.Any() || DelayProfileIds.Any() || ImportListIds.Any() || RootFolderIds.Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using NzbDrone.Core.Music;
|
|||
using NzbDrone.Core.Notifications;
|
||||
using NzbDrone.Core.Profiles.Delay;
|
||||
using NzbDrone.Core.Profiles.Releases;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.Tags
|
||||
{
|
||||
|
@ -31,6 +32,7 @@ namespace NzbDrone.Core.Tags
|
|||
private readonly INotificationFactory _notificationFactory;
|
||||
private readonly IReleaseProfileService _releaseProfileService;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
|
||||
public TagService(ITagRepository repo,
|
||||
IEventAggregator eventAggregator,
|
||||
|
@ -38,7 +40,8 @@ namespace NzbDrone.Core.Tags
|
|||
ImportListFactory importListFactory,
|
||||
INotificationFactory notificationFactory,
|
||||
IReleaseProfileService releaseProfileService,
|
||||
IArtistService artistService)
|
||||
IArtistService artistService,
|
||||
IRootFolderService rootFolderService)
|
||||
{
|
||||
_repo = repo;
|
||||
_eventAggregator = eventAggregator;
|
||||
|
@ -47,6 +50,7 @@ namespace NzbDrone.Core.Tags
|
|||
_notificationFactory = notificationFactory;
|
||||
_releaseProfileService = releaseProfileService;
|
||||
_artistService = artistService;
|
||||
_rootFolderService = rootFolderService;
|
||||
}
|
||||
|
||||
public Tag GetTag(int tagId)
|
||||
|
@ -74,6 +78,7 @@ namespace NzbDrone.Core.Tags
|
|||
var notifications = _notificationFactory.AllForTag(tagId);
|
||||
var restrictions = _releaseProfileService.AllForTag(tagId);
|
||||
var artist = _artistService.AllForTag(tagId);
|
||||
var rootFolders = _rootFolderService.AllForTag(tagId);
|
||||
|
||||
return new TagDetails
|
||||
{
|
||||
|
@ -83,7 +88,8 @@ namespace NzbDrone.Core.Tags
|
|||
ImportListIds = importLists.Select(c => c.Id).ToList(),
|
||||
NotificationIds = notifications.Select(c => c.Id).ToList(),
|
||||
RestrictionIds = restrictions.Select(c => c.Id).ToList(),
|
||||
ArtistIds = artist.Select(c => c.Id).ToList()
|
||||
ArtistIds = artist.Select(c => c.Id).ToList(),
|
||||
RootFolderIds = rootFolders.Select(c => c.Id).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -95,6 +101,7 @@ namespace NzbDrone.Core.Tags
|
|||
var notifications = _notificationFactory.All();
|
||||
var restrictions = _releaseProfileService.All();
|
||||
var artists = _artistService.GetAllArtists();
|
||||
var rootFolders = _rootFolderService.All();
|
||||
|
||||
var details = new List<TagDetails>();
|
||||
|
||||
|
@ -108,7 +115,8 @@ namespace NzbDrone.Core.Tags
|
|||
ImportListIds = importLists.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(),
|
||||
NotificationIds = notifications.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(),
|
||||
RestrictionIds = restrictions.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(),
|
||||
ArtistIds = artists.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList()
|
||||
ArtistIds = artists.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(),
|
||||
RootFolderIds = rootFolders.Where(c => c.DefaultTags.Contains(tag.Id)).Select(c => c.Id).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ using NzbDrone.Core.Profiles.Qualities;
|
|||
|
||||
namespace NzbDrone.Core.Validation
|
||||
{
|
||||
public class ProfileExistsValidator : PropertyValidator
|
||||
public class QualityProfileExistsValidator : PropertyValidator
|
||||
{
|
||||
private readonly IProfileService _profileService;
|
||||
|
||||
public ProfileExistsValidator(IProfileService profileService)
|
||||
public QualityProfileExistsValidator(IProfileService profileService)
|
||||
: base("Quality Profile does not exist")
|
||||
{
|
||||
_profileService = profileService;
|
|
@ -115,7 +115,6 @@ namespace NzbDrone.Integration.Test.ApiTests
|
|||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().Should().ContainKey("path");
|
||||
result.First().Should().ContainKey("relativePath");
|
||||
result.First().Should().ContainKey("name");
|
||||
|
||||
result.First()["name"].Should().Be("somevideo.mp3");
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Lidarr.Api.V1.RootFolders;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Integration.Test.ApiTests
|
||||
|
@ -8,6 +10,20 @@ namespace NzbDrone.Integration.Test.ApiTests
|
|||
[TestFixture]
|
||||
public class WantedFixture : IntegrationTest
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
// Add a root folder
|
||||
RootFolders.Post(new RootFolderResource
|
||||
{
|
||||
Name = "TestLibrary",
|
||||
Path = ArtistRootFolder,
|
||||
DefaultMetadataProfileId = 1,
|
||||
DefaultQualityProfileId = 1,
|
||||
DefaultMonitorOption = MonitorTypes.All
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Order(0)]
|
||||
public void missing_should_be_empty()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue