public void should_match_tracks(string file) { var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Identification", file); var testcase = JsonConvert.DeserializeObject <IdTestCase>(File.ReadAllText(path)); var artists = GivenArtists(testcase.LibraryAuthors); var specifiedArtist = artists.SingleOrDefault(x => x.Metadata.Value.ForeignAuthorId == testcase.Author); var idOverrides = new IdentificationOverrides { Author = specifiedArtist }; var tracks = testcase.Tracks.Select(x => new LocalBook { Path = x.Path.AsOsAgnostic(), FileTrackInfo = x.FileTrackInfo }).ToList(); if (testcase.Fingerprints != null) { GivenFingerprints(testcase.Fingerprints); } var config = new ImportDecisionMakerConfig { NewDownload = testcase.NewDownload, SingleRelease = testcase.SingleRelease, IncludeExisting = false }; var result = _Subject.Identify(tracks, idOverrides, config); result.Should().HaveCount(testcase.ExpectedMusicBrainzReleaseIds.Count); }
public List <LocalEdition> Identify(List <LocalBook> localTracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) { // 1 group localTracks so that we think they represent a single release // 2 get candidates given specified author, book and release. Candidates can include extra files already on disk. // 3 find best candidate var watch = System.Diagnostics.Stopwatch.StartNew(); _logger.Debug("Starting track identification"); var releases = GetLocalBookReleases(localTracks, config.SingleRelease); int i = 0; foreach (var localRelease in releases) { i++; _logger.ProgressInfo($"Identifying book {i}/{releases.Count}"); IdentifyRelease(localRelease, idOverrides, config); } watch.Stop(); _logger.Debug($"Track identification for {localTracks.Count} tracks took {watch.ElapsedMilliseconds}ms"); return(releases); }
private List <ManualImportItem> ProcessFolder(string folder, string downloadId, Author author, FilterFilesType filter, bool replaceExistingFiles) { DownloadClientItem downloadClientItem = null; var directoryInfo = new DirectoryInfo(folder); author = author ?? _parsingService.GetAuthor(directoryInfo.Name); if (downloadId.IsNotNullOrWhiteSpace()) { var trackedDownload = _trackedDownloadService.Find(downloadId); downloadClientItem = trackedDownload?.DownloadItem; if (author == null) { author = trackedDownload?.RemoteBook?.Author; } } var authorFiles = _diskScanService.GetBookFiles(folder).ToList(); var idOverrides = new IdentificationOverrides { Author = author }; var itemInfo = new ImportDecisionMakerInfo { DownloadClientItem = downloadClientItem, ParsedTrackInfo = Parser.Parser.ParseTitle(directoryInfo.Name) }; var config = new ImportDecisionMakerConfig { Filter = filter, NewDownload = true, SingleRelease = false, IncludeExisting = !replaceExistingFiles, AddNewAuthors = false, KeepAllEditions = true }; var decisions = _importDecisionMaker.GetImportDecisions(authorFiles, idOverrides, itemInfo, config); // paths will be different for new and old files which is why we need to map separately var newFiles = authorFiles.Join(decisions, f => f.FullName, d => d.Item.Path, (f, d) => new { File = f, Decision = d }, PathEqualityComparer.Instance); 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, null, replaceExistingFiles, false)); return(newItems.Concat(existingItems).ToList()); }
private List <ImportResult> ProcessFile(IFileInfo fileInfo, ImportMode importMode, Author author, DownloadClientItem downloadClientItem) { if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._")) { _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName); return(new List <ImportResult> { new ImportResult(new ImportDecision <LocalBook>(new LocalBook { Path = fileInfo.FullName }, new Rejection("Invalid music file, filename starts with '._'")), "Invalid music file, filename starts with '._'") }); } if (downloadClientItem == null) { if (_diskProvider.IsFileLocked(fileInfo.FullName)) { return(new List <ImportResult> { FileIsLockedResult(fileInfo.FullName) }); } } var idOverrides = new IdentificationOverrides { Author = author }; var idInfo = new ImportDecisionMakerInfo { DownloadClientItem = downloadClientItem }; var idConfig = new ImportDecisionMakerConfig { Filter = FilterFilesType.None, NewDownload = true, SingleRelease = false, IncludeExisting = false, AddNewAuthors = false }; var decisions = _importDecisionMaker.GetImportDecisions(new List <IFileInfo>() { fileInfo }, idOverrides, idInfo, idConfig); return(_importApprovedTracks.Import(decisions, true, downloadClientItem, importMode)); }
public void get_candidates_should_only_return_specified_release_if_set() { var tracks = GivenTracks(3); var release = GivenAlbumRelease("album", tracks); var localTracks = GivenLocalTracks(tracks, release); var localAlbumRelease = new LocalAlbumRelease(localTracks); var idOverrides = new IdentificationOverrides { AlbumRelease = release }; Subject.GetDbCandidatesFromTags(localAlbumRelease, idOverrides, false).Should().BeEquivalentTo( new List <CandidateAlbumRelease> { new CandidateAlbumRelease(release) }); }
private bool SatisfiesOverride(Edition edition, IdentificationOverrides idOverride) { if (idOverride?.Edition != null) { return(edition.ForeignEditionId == idOverride.Edition.ForeignEditionId); } if (idOverride?.Book != null) { return(edition.Book.Value.ForeignBookId == idOverride.Book.ForeignBookId); } if (idOverride?.Author != null) { return(edition.Book.Value.Author.Value.ForeignAuthorId == idOverride.Author.ForeignAuthorId); } return(true); }
private void IdentifyRelease(LocalEdition localBookRelease, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) { var watch = System.Diagnostics.Stopwatch.StartNew(); var candidateReleases = _candidateService.GetDbCandidatesFromTags(localBookRelease, idOverrides, config.IncludeExisting); if (candidateReleases.Count == 0 && config.AddNewAuthors) { candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease); } if (candidateReleases.Count == 0) { // can't find any candidates even after fingerprinting // populate the overrides and return foreach (var localTrack in localBookRelease.LocalBooks) { localTrack.Edition = idOverrides.Edition; localTrack.Book = idOverrides.Book; localTrack.Author = idOverrides.Author; } return; } _logger.Debug($"Got {candidateReleases.Count} candidates for {localBookRelease.LocalBooks.Count} tracks in {watch.ElapsedMilliseconds}ms"); // convert all the TrackFiles that represent extra files to List<LocalTrack> var allLocalTracks = ToLocalTrack(candidateReleases .SelectMany(x => x.ExistingFiles) .DistinctBy(x => x.Path), localBookRelease); _logger.Debug($"Retrieved {allLocalTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms"); GetBestRelease(localBookRelease, candidateReleases, allLocalTracks); _logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms"); localBookRelease.PopulateMatch(); _logger.Debug($"IdentifyRelease done in {watch.ElapsedMilliseconds}ms"); }
private void IdentifyRelease(LocalEdition localBookRelease, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) { var watch = System.Diagnostics.Stopwatch.StartNew(); bool usedRemote = false; IEnumerable <CandidateEdition> candidateReleases = _candidateService.GetDbCandidatesFromTags(localBookRelease, idOverrides, config.IncludeExisting); // convert all the TrackFiles that represent extra files to List<LocalTrack> // local candidates are actually a list so this is fine to enumerate var allLocalTracks = ToLocalTrack(candidateReleases .SelectMany(x => x.ExistingFiles) .DistinctBy(x => x.Path), localBookRelease); _logger.Debug($"Retrieved {allLocalTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms"); if (!candidateReleases.Any()) { candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease, idOverrides); if (!config.AddNewAuthors) { candidateReleases = candidateReleases.Where(x => x.Edition.Book.Value.Id > 0); } usedRemote = true; } if (!candidateReleases.Any()) { // can't find any candidates even after using remote search // populate the overrides and return foreach (var localTrack in localBookRelease.LocalBooks) { localTrack.Edition = idOverrides.Edition; localTrack.Book = idOverrides.Book; localTrack.Author = idOverrides.Author; } return; } GetBestRelease(localBookRelease, candidateReleases, allLocalTracks); // If the result isn't great and we haven't tried remote candidates, try looking for remote candidates // Goodreads may have a better edition of a local book if (localBookRelease.Distance.NormalizedDistance() > 0.15 && !usedRemote) { _logger.Debug("Match not good enough, trying remote candidates"); candidateReleases = _candidateService.GetRemoteCandidates(localBookRelease, idOverrides); if (!config.AddNewAuthors) { candidateReleases = candidateReleases.Where(x => x.Edition.Book.Value.Id > 0); } GetBestRelease(localBookRelease, candidateReleases, allLocalTracks); } _logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms"); localBookRelease.PopulateMatch(); _logger.Debug($"IdentifyRelease done in {watch.ElapsedMilliseconds}ms"); }
public List <ManualImportItem> UpdateItems(List <ManualImportItem> items) { var replaceExistingFiles = items.All(x => x.ReplaceExistingFiles); var groupedItems = items.Where(x => !x.AdditionalFile).GroupBy(x => x.Album?.Id); _logger.Debug($"UpdateItems, {groupedItems.Count()} groups, replaceExisting {replaceExistingFiles}"); var result = new List <ManualImportItem>(); foreach (var group in groupedItems) { _logger.Debug("UpdateItems, group key: {0}", group.Key); var disableReleaseSwitching = group.First().DisableReleaseSwitching; 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, d => d.Item.Path, (i, d) => new { Item = i, Decision = d }, PathEqualityComparer.Instance); foreach (var pair in existingItems) { var item = pair.Item; var decision = pair.Decision; if (decision.Item.Artist != null) { item.Artist = decision.Item.Artist; } if (decision.Item.Album != null) { item.Album = decision.Item.Album; item.Release = decision.Item.Release; } if (decision.Item.Tracks.Any()) { item.Tracks = decision.Item.Tracks; } item.Rejections = decision.Rejections; result.Add(item); } var newDecisions = decisions.Except(existingItems.Select(x => x.Decision)); result.AddRange(newDecisions.Select(x => MapItem(x, null, replaceExistingFiles, disableReleaseSwitching))); } return(result); }
public List <CandidateEdition> GetDbCandidatesFromTags(LocalEdition localEdition, IdentificationOverrides idOverrides, bool includeExisting) { var watch = System.Diagnostics.Stopwatch.StartNew(); // Generally author, book 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 <CandidateEdition> candidateReleases; // if we have a Book ID, use that Book tagMbidRelease = null; List <CandidateEdition> tagCandidate = null; // TODO: select by ISBN? // var releaseIds = localEdition.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<BookRelease> { tagMbidRelease }, includeExisting); // } // } if (idOverrides?.Edition != null) { var release = idOverrides.Edition; _logger.Debug("Edition {0} was forced", release); candidateReleases = GetDbCandidatesByEdition(new List <Edition> { release }, includeExisting); } else if (idOverrides?.Book != null) { // use the release from file tags if it exists and agrees with the specified book if (tagMbidRelease?.Id == idOverrides.Book.Id) { candidateReleases = tagCandidate; } else { candidateReleases = GetDbCandidatesByBook(idOverrides.Book, includeExisting); } } else if (idOverrides?.Author != null) { // use the release from file tags if it exists and agrees with the specified book if (tagMbidRelease?.AuthorMetadataId == idOverrides.Author.AuthorMetadataId) { candidateReleases = tagCandidate; } else { candidateReleases = GetDbCandidatesByAuthor(localEdition, idOverrides.Author, includeExisting); } } else { if (tagMbidRelease != null) { candidateReleases = tagCandidate; } else { candidateReleases = GetDbCandidates(localEdition, includeExisting); } } watch.Stop(); _logger.Debug($"Getting {candidateReleases.Count} candidates from tags for {localEdition.LocalBooks.Count} tracks took {watch.ElapsedMilliseconds}ms"); return(candidateReleases); }
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); }
public IEnumerable <CandidateEdition> GetRemoteCandidates(LocalEdition localEdition, IdentificationOverrides idOverrides) { // TODO handle edition override // Gets candidate book releases from the metadata server. // Will eventually need adding locally if we find a match List <Book> remoteBooks; var seenCandidates = new HashSet <string>(); var isbns = localEdition.LocalBooks.Select(x => x.FileTrackInfo.Isbn).Distinct().ToList(); var asins = localEdition.LocalBooks.Select(x => x.FileTrackInfo.Asin).Distinct().ToList(); var goodreads = localEdition.LocalBooks.Select(x => x.FileTrackInfo.GoodreadsId).Distinct().ToList(); // grab possibilities for all the IDs present if (isbns.Count == 1 && isbns[0].IsNotNullOrWhiteSpace()) { _logger.Trace($"Searching by isbn {isbns[0]}"); try { remoteBooks = _bookSearchService.SearchByIsbn(isbns[0]); } catch (GoodreadsException e) { _logger.Info(e, "Skipping ISBN search due to Goodreads Error"); remoteBooks = new List <Book>(); } foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides)) { yield return(candidate); } } if (asins.Count == 1 && asins[0].IsNotNullOrWhiteSpace() && asins[0].Length == 10) { _logger.Trace($"Searching by asin {asins[0]}"); try { remoteBooks = _bookSearchService.SearchByAsin(asins[0]); } catch (GoodreadsException e) { _logger.Info(e, "Skipping ASIN search due to Goodreads Error"); remoteBooks = new List <Book>(); } foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides)) { yield return(candidate); } } if (goodreads.Count == 1 && goodreads[0].IsNotNullOrWhiteSpace()) { if (int.TryParse(goodreads[0], out var id)) { _logger.Trace($"Searching by goodreads id {id}"); try { remoteBooks = _bookSearchService.SearchByGoodreadsId(id); } catch (GoodreadsException e) { _logger.Info(e, "Skipping Goodreads ID search due to Goodreads Error"); remoteBooks = new List <Book>(); } foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides)) { yield return(candidate); } } } // If we got an id result, or any overrides are set, stop if (seenCandidates.Any() || idOverrides?.Edition != null || idOverrides?.Book != null || idOverrides?.Author != null) { yield break; } // fall back to author / book name search var authorTags = new List <string>(); if (TrackGroupingService.IsVariousAuthors(localEdition.LocalBooks)) { authorTags.Add("Various Authors"); } else { authorTags.AddRange(localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.Authors)); } var bookTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.BookTitle) ?? ""; // If no valid author or book tags, stop if (!authorTags.Any() || bookTag.IsNullOrWhiteSpace()) { yield break; } // Search by author+book foreach (var authorTag in authorTags) { try { remoteBooks = _bookSearchService.SearchForNewBook(bookTag, authorTag); } catch (GoodreadsException e) { _logger.Info(e, "Skipping author/title search due to Goodreads Error"); remoteBooks = new List <Book>(); } foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides)) { yield return(candidate); } } // If we got an author/book search result, stop if (seenCandidates.Any()) { yield break; } // Search by just book title try { remoteBooks = _bookSearchService.SearchForNewBook(bookTag, null); } catch (GoodreadsException e) { _logger.Info(e, "Skipping book title search due to Goodreads Error"); remoteBooks = new List <Book>(); } foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides)) { yield return(candidate); } // Search by just author foreach (var a in authorTags) { try { remoteBooks = _bookSearchService.SearchForNewBook(a, null); } catch (GoodreadsException e) { _logger.Info(e, "Skipping author search due to Goodreads Error"); remoteBooks = new List <Book>(); } foreach (var candidate in ToCandidates(remoteBooks, seenCandidates, idOverrides)) { yield return(candidate); } } }
private void IdentifyRelease(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) { var watch = System.Diagnostics.Stopwatch.StartNew(); bool fingerprinted = false; 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 = _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) { // can't find any candidates even after fingerprinting // populate the overrides and return foreach (var localTrack in localAlbumRelease.LocalTracks) { localTrack.Release = idOverrides.AlbumRelease; localTrack.Album = idOverrides.Album; localTrack.Artist = idOverrides.Artist; } return; } _logger.Debug($"Got {candidateReleases.Count} candidates for {localAlbumRelease.LocalTracks.Count} tracks in {watch.ElapsedMilliseconds}ms"); 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 {allLocalTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms"); 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(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 (idOverrides?.Album == null && idOverrides?.AlbumRelease == null) { 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); PopulateTracks(candidateReleases); allLocalTracks.AddRange(ToLocalTrack(newCandidates .SelectMany(x => x.ExistingTracks) .DistinctBy(x => x.Path) .ExceptBy(x => x.Path, allLocalTracks, x => x.Path, PathEqualityComparer.Instance), localAlbumRelease)); } // fingerprint all the local files in candidates we might be matching against _fingerprintingService.Lookup(allLocalTracks, 0.5); GetBestRelease(localAlbumRelease, candidateReleases, allLocalTracks); } _logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms"); localAlbumRelease.PopulateMatch(); _logger.Debug($"IdentifyRelease done in {watch.ElapsedMilliseconds}ms"); }
public void Setup() { _albumpass1 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >(); _albumpass2 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >(); _albumpass3 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >(); _albumfail1 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >(); _albumfail2 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >(); _albumfail3 = new Mock <IImportDecisionEngineSpecification <LocalEdition> >(); _pass1 = new Mock <IImportDecisionEngineSpecification <LocalBook> >(); _pass2 = new Mock <IImportDecisionEngineSpecification <LocalBook> >(); _pass3 = new Mock <IImportDecisionEngineSpecification <LocalBook> >(); _fail1 = new Mock <IImportDecisionEngineSpecification <LocalBook> >(); _fail2 = new Mock <IImportDecisionEngineSpecification <LocalBook> >(); _fail3 = new Mock <IImportDecisionEngineSpecification <LocalBook> >(); _albumpass1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept()); _albumpass2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept()); _albumpass3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept()); _albumfail1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_albumfail1")); _albumfail2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_albumfail2")); _albumfail3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalEdition>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_albumfail3")); _pass1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept()); _pass2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept()); _pass3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Accept()); _fail1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_fail1")); _fail2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_fail2")); _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalBook>(), It.IsAny <DownloadClientItem>())).Returns(Decision.Reject("_fail3")); _artist = Builder <Author> .CreateNew() .With(e => e.QualityProfileId = 1) .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); _album = Builder <Book> .CreateNew() .With(x => x.Author = _artist) .Build(); _edition = Builder <Edition> .CreateNew() .With(x => x.Book = _album) .Build(); _quality = new QualityModel(Quality.MP3_320); _localTrack = new LocalBook { Author = _artist, Quality = _quality, Book = new Book(), Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() }; _idOverrides = new IdentificationOverrides { Author = _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 <LocalBook> >(), It.IsAny <IdentificationOverrides>(), It.IsAny <ImportDecisionMakerConfig>())) .Returns((List <LocalBook> tracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) => { var ret = new LocalEdition(tracks); ret.Edition = _edition; return(new List <LocalEdition> { ret }); }); Mocker.GetMock <IMediaFileService>() .Setup(c => c.FilterUnchangedFiles(It.IsAny <List <IFileInfo> >(), It.IsAny <FilterFilesType>())) .Returns((List <IFileInfo> files, FilterFilesType filter) => files); GivenSpecifications(_albumpass1); }
private List <ImportResult> ProcessFolder(IDirectoryInfo directoryInfo, ImportMode importMode, Author author, DownloadClientItem downloadClientItem) { if (_authorService.AuthorPathExists(directoryInfo.FullName)) { _logger.Warn("Unable to process folder that is mapped to an existing author"); return(new List <ImportResult>()); } var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); var folderInfo = Parser.Parser.ParseBookTitle(directoryInfo.Name); var trackInfo = new ParsedTrackInfo { }; if (folderInfo != null) { _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality); trackInfo = new ParsedTrackInfo { AlbumTitle = folderInfo.BookTitle, ArtistTitle = folderInfo.AuthorName, Quality = folderInfo.Quality, ReleaseGroup = folderInfo.ReleaseGroup, ReleaseHash = folderInfo.ReleaseHash, }; } else { trackInfo = null; } var audioFiles = _diskScanService.FilterFiles(directoryInfo.FullName, _diskScanService.GetBookFiles(directoryInfo.FullName)); if (downloadClientItem == null) { foreach (var audioFile in audioFiles) { if (_diskProvider.IsFileLocked(audioFile.FullName)) { return(new List <ImportResult> { FileIsLockedResult(audioFile.FullName) }); } } } var idOverrides = new IdentificationOverrides { Author = author }; var idInfo = new ImportDecisionMakerInfo { DownloadClientItem = downloadClientItem, ParsedTrackInfo = trackInfo }; var idConfig = new ImportDecisionMakerConfig { Filter = FilterFilesType.None, NewDownload = true, SingleRelease = false, IncludeExisting = false, AddNewAuthors = false }; var decisions = _importDecisionMaker.GetImportDecisions(audioFiles, idOverrides, idInfo, idConfig); var importResults = _importApprovedTracks.Import(decisions, true, downloadClientItem, importMode); if (importMode == ImportMode.Auto) { importMode = (downloadClientItem == null || downloadClientItem.CanMoveFiles) ? ImportMode.Move : ImportMode.Copy; } if (importMode == ImportMode.Move && importResults.Any(i => i.Result == ImportResultType.Imported) && ShouldDeleteFolder(directoryInfo, author)) { _logger.Debug("Deleting folder after importing valid files"); _diskProvider.DeleteFolder(directoryInfo.FullName, true); } return(importResults); }
private List <CandidateEdition> ToCandidates(IEnumerable <Book> books, HashSet <string> seenCandidates, IdentificationOverrides idOverrides) { var candidates = new List <CandidateEdition>(); foreach (var book in books) { // We have to make sure various bits and pieces are populated that are normally handled // by a database lazy load foreach (var edition in book.Editions.Value) { edition.Book = book; if (!seenCandidates.Contains(edition.ForeignEditionId) && SatisfiesOverride(edition, idOverrides)) { seenCandidates.Add(edition.ForeignEditionId); candidates.Add(new CandidateEdition { Edition = edition, ExistingFiles = new List <BookFile>() }); } } } return(candidates); }
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)); }