New: Manual import improvements (#683)

* New: Manual import improvements

 - Detect and merge import with files already in library.
 - Allow selection of album release from Manual Import modal.
 - Loading indicator while fetching updated decisions

* Disable release switching if user manually overrode release
This commit is contained in:
ta264
2019-04-04 09:20:47 +01:00
committed by GitHub
parent 61cea37f05
commit 188e0e1040
45 changed files with 1295 additions and 371 deletions
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.Returns(_rootFolder);
Mocker.GetMock<IMakeImportDecision>()
.Setup(v => v.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>()))
.Setup(v => v.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Artist>(), It.IsAny<bool>()))
.Returns(new List<ImportDecision<LocalTrack>>());
Mocker.GetMock<IMediaFileService>()
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Never());
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist), Times.Never());
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist, false), Times.Never());
}
[Test]
@@ -162,7 +162,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Once());
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist), Times.Never());
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist, false), Times.Never());
}
[Test]
@@ -180,7 +180,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.Verify(v => v.Clean(It.IsAny<Artist>(), It.IsAny<List<string>>()), Times.Once());
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist), Times.Never());
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _artist, false), Times.Never());
}
[Test]
@@ -197,7 +197,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _artist, false), Times.Once());
}
[Test]
@@ -220,7 +220,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.Verify(v => v.GetFiles(It.IsAny<string>(), It.IsAny<SearchOption>()), Times.Once());
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
}
[Test]
@@ -238,7 +238,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
}
[Test]
@@ -261,7 +261,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _artist, false), Times.Once());
}
[Test]
@@ -277,7 +277,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
}
[Test]
@@ -296,7 +296,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
}
[Test]
@@ -316,7 +316,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
}
[Test]
@@ -333,7 +333,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
}
[Test]
@@ -350,7 +350,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
}
[Test]
@@ -369,7 +369,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _artist, false), Times.Once());
}
[Test]
@@ -387,7 +387,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_artist);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist), Times.Once());
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _artist, false), Times.Once());
}
}
}
@@ -160,7 +160,9 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_not_move_existing_files()
{
Subject.Import(new List<ImportDecision<LocalTrack>> { _approvedDecisions.First() }, false);
var track = _approvedDecisions.First();
track.Item.ExistingFile = true;
Subject.Import(new List<ImportDecision<LocalTrack>> { track }, false);
Mocker.GetMock<IUpgradeMediaFiles>()
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().Item, false),
@@ -215,13 +217,15 @@ namespace NzbDrone.Core.Test.MediaFiles
}
[Test]
public void should_delete_existing_metadata_files_with_the_same_path()
public void should_delete_existing_trackfiles_with_the_same_path()
{
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
.Returns(Builder<TrackFile>.CreateListOfSize(1).BuildList());
Subject.Import(new List<ImportDecision<LocalTrack>> { _approvedDecisions.First() }, false);
var track = _approvedDecisions.First();
track.Item.ExistingFile = true;
Subject.Import(new List<ImportDecision<LocalTrack>> { track }, false);
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Delete(It.IsAny<TrackFile>(), DeleteMediaFileReason.ManualOverride), Times.Once());
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
var local = GivenLocalAlbumRelease();
Subject.GetCandidatesFromFingerprint(local).ShouldBeEquivalentTo(new List<AlbumRelease>());
Subject.GetCandidatesFromFingerprint(local, null, null, null, false).ShouldBeEquivalentTo(new List<CandidateAlbumRelease>());
}
[Test]
@@ -133,7 +133,9 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
var localTracks = GivenLocalTracks(tracks, release);
var localAlbumRelease = new LocalAlbumRelease(localTracks);
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, release).ShouldBeEquivalentTo(new List<AlbumRelease> { release });
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, release, false).ShouldBeEquivalentTo(
new List<CandidateAlbumRelease> { new CandidateAlbumRelease(release) }
);
}
[Test]
@@ -149,7 +151,9 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
.Setup(x => x.GetReleaseByForeignReleaseId("xxx"))
.Returns(release);
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, null).ShouldBeEquivalentTo(new List<AlbumRelease> { release });
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, null, false).ShouldBeEquivalentTo(
new List<CandidateAlbumRelease> { new CandidateAlbumRelease(release) }
);
}
}
}
@@ -176,7 +176,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
GivenFingerprints(testcase.Fingerprints);
}
var result = Subject.Identify(tracks, specifiedArtist, null, null, testcase.NewDownload, testcase.SingleRelease);
var result = Subject.Identify(tracks, specifiedArtist, null, null, testcase.NewDownload, testcase.SingleRelease, false);
TestLogger.Debug($"Found releases:\n{result.Where(x => x.AlbumRelease != null).Select(x => x.AlbumRelease?.ForeignReleaseId).ToJson()}");
@@ -98,19 +98,23 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
Artist = _artist,
Quality = _quality,
Tracks = new List<Track> { new Track() },
Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi"
Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic()
};
GivenVideoFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() });
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>()))
.Returns((List<LocalTrack> tracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease) => {
.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) => {
var ret = new LocalAlbumRelease(tracks);
ret.AlbumRelease = _albumRelease;
return new List<LocalAlbumRelease> { ret };
});
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.FilterExistingFiles(It.IsAny<List<string>>(), It.IsAny<Artist>()))
.Returns((List<string> files, Artist artist) => files);
GivenSpecifications(_albumpass1);
}
@@ -119,7 +123,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
Mocker.SetConstant(mocks.Select(c => c.Object));
}
private void GivenVideoFiles(IEnumerable<string> videoFiles)
private void GivenAudioFiles(IEnumerable<string> videoFiles)
{
_audioFiles = videoFiles.ToList();
@@ -145,7 +149,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenAugmentationSuccess();
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3, _albumfail1, _albumfail2, _albumfail3);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, downloadClientItem, null, false, false, false);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, null, downloadClientItem, null, false, false, false, false);
_albumfail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
_albumfail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>()), Times.Once());
@@ -162,7 +166,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenAugmentationSuccess();
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, downloadClientItem, null, false, false, false);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, null, downloadClientItem, null, false, false, false, false);
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Once());
@@ -180,7 +184,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3, _albumfail1, _albumfail2, _albumfail3);
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, downloadClientItem, null, false, false, false);
Subject.GetImportDecisions(_audioFiles, new Artist(), null, null, downloadClientItem, null, false, false, false, false);
_fail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
_fail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalTrack>()), Times.Never());
@@ -196,7 +200,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumfail1);
GivenSpecifications(_pass1);
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
result.Single().Approved.Should().BeFalse();
}
@@ -207,7 +211,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1);
GivenSpecifications(_fail1);
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
result.Single().Approved.Should().BeFalse();
}
@@ -218,7 +222,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1, _albumfail1, _albumpass2, _albumpass3);
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
result.Single().Approved.Should().BeFalse();
}
@@ -229,7 +233,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3);
GivenSpecifications(_pass1, _fail1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
result.Single().Approved.Should().BeFalse();
}
@@ -241,7 +245,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_albumpass1, _albumpass2, _albumpass3);
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
result.Single().Approved.Should().BeTrue();
}
@@ -252,7 +256,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenAugmentationSuccess();
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
var result = Subject.GetImportDecisions(_audioFiles, new Artist());
var result = Subject.GetImportDecisions(_audioFiles, new Artist(), false);
result.Single().Rejections.Should().HaveCount(3);
}
@@ -265,16 +269,14 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
.Setup(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()))
.Throws<TestException>();
_audioFiles = new List<string>
GivenAudioFiles(new []
{
"The.Office.S03E115.DVDRip.XviD-OSiTV",
"The.Office.S03E115.DVDRip.XviD-OSiTV",
"The.Office.S03E115.DVDRip.XviD-OSiTV"
};
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic(),
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic(),
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
});
GivenVideoFiles(_audioFiles);
Subject.GetImportDecisions(_audioFiles, _artist);
Subject.GetImportDecisions(_audioFiles, _artist, false);
Mocker.GetMock<IAugmentingService>()
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
@@ -287,22 +289,20 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
{
GivenSpecifications(_pass1);
_audioFiles = new List<string>
{
"The.Office.S03E115.DVDRip.XviD-OSiTV",
"The.Office.S03E115.DVDRip.XviD-OSiTV",
"The.Office.S03E115.DVDRip.XviD-OSiTV"
};
GivenVideoFiles(_audioFiles);
GivenAudioFiles(new []
{
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic(),
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic(),
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".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>()))
.Returns((List<LocalTrack> tracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease) => {
.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) };
});
var decisions = Subject.GetImportDecisions(_audioFiles, _artist);
var decisions = Subject.GetImportDecisions(_audioFiles, _artist, false);
Mocker.GetMock<IAugmentingService>()
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
@@ -316,16 +316,14 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
{
GivenSpecifications(_pass1);
_audioFiles = new List<string>
GivenAudioFiles(new []
{
"The.Office.S03E115.DVDRip.XviD-OSiTV",
"The.Office.S03E115.DVDRip.XviD-OSiTV",
"The.Office.S03E115.DVDRip.XviD-OSiTV"
};
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic(),
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic(),
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
});
GivenVideoFiles(_audioFiles);
var decisions = Subject.GetImportDecisions(_audioFiles, _artist);
var decisions = Subject.GetImportDecisions(_audioFiles, _artist, false);
Mocker.GetMock<IAugmentingService>()
.Verify(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()), Times.Exactly(_audioFiles.Count));
@@ -341,14 +339,12 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
.Setup(c => c.Augment(It.IsAny<LocalTrack>(), It.IsAny<bool>()))
.Throws<TestException>();
_audioFiles = new List<string>
GivenAudioFiles(new []
{
"The.Office.S03E115.DVDRip.XviD-OSiTV"
};
@"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV".AsOsAgnostic()
});
GivenVideoFiles(_audioFiles);
Subject.GetImportDecisions(_audioFiles, _artist).Should().HaveCount(1);
Subject.GetImportDecisions(_audioFiles, _artist, false).Should().HaveCount(1);
ExceptionVerification.ExpectedErrors(1);
}