Пример #1
0
        private ImportDecision <LocalEdition> GetDecision(LocalEdition localEdition, DownloadClientItem downloadClientItem)
        {
            ImportDecision <LocalEdition> decision = null;

            if (localEdition.Edition == null)
            {
                decision = new ImportDecision <LocalEdition>(localEdition, new Rejection($"Couldn't find similar book for {localEdition}"));
            }
            else
            {
                var reasons = _bookSpecifications.Select(c => EvaluateSpec(c, localEdition, downloadClientItem))
                              .Where(c => c != null);

                decision = new ImportDecision <LocalEdition>(localEdition, reasons.ToArray());
            }

            if (decision == null)
            {
                _logger.Error("Unable to make a decision on {0}", localEdition);
            }
            else if (decision.Rejections.Any())
            {
                _logger.Debug("Book rejected for the following reasons: {0}", string.Join(", ", decision.Rejections));
            }
            else
            {
                _logger.Debug("Book accepted");
            }

            return(decision);
        }
Пример #2
0
        public void should_not_throw_on_goodreads_exception()
        {
            Mocker.GetMock <ISearchForNewBook>()
            .Setup(s => s.SearchForNewBook(It.IsAny <string>(), It.IsAny <string>()))
            .Throws(new GoodreadsException("Bad search"));

            var edition = new LocalEdition
            {
                LocalBooks = new List <LocalBook>
                {
                    new LocalBook
                    {
                        FileTrackInfo = new ParsedTrackInfo
                        {
                            Authors = new List <string> {
                                "Author"
                            },
                            BookTitle = "Book"
                        }
                    }
                }
            };

            Subject.GetRemoteCandidates(edition, null).Should().BeEmpty();
        }
Пример #3
0
        private List <CandidateEdition> GetDbCandidates(LocalEdition localEdition, bool includeExisting)
        {
            // most general version, nothing has been specified.
            // get all plausible authors, then all plausible books, then get releases for each of these.
            var candidateReleases = new List <CandidateEdition>();

            // check if it looks like VA.
            if (TrackGroupingService.IsVariousAuthors(localEdition.LocalBooks))
            {
                var va = _authorService.FindById(DistanceCalculator.VariousAuthorIds[0]);
                if (va != null)
                {
                    candidateReleases.AddRange(GetDbCandidatesByAuthor(localEdition, va, includeExisting));
                }
            }

            var authorTags = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.Authors) ?? new List <string>();

            if (authorTags.Any())
            {
                foreach (var authorTag in authorTags)
                {
                    var possibleAuthors = _authorService.GetCandidates(authorTag);
                    foreach (var author in possibleAuthors)
                    {
                        candidateReleases.AddRange(GetDbCandidatesByAuthor(localEdition, author, includeExisting));
                    }
                }
            }

            return(candidateReleases);
        }
Пример #4
0
        private List <CandidateEdition> GetDbCandidates(LocalEdition localEdition, bool includeExisting)
        {
            _logger.Trace($"Getting candidates for {0}", localEdition);

            // 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 <CandidateEdition>();

            // check if it looks like VA.
            if (TrackGroupingService.IsVariousArtists(localEdition.LocalBooks))
            {
                var va = _authorService.FindById(DistanceCalculator.VariousAuthorIds[0]);
                if (va != null)
                {
                    candidateReleases.AddRange(GetDbCandidatesByAuthor(localEdition, va, includeExisting));
                }
            }

            var artistTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? "";

            if (artistTag.IsNotNullOrWhiteSpace())
            {
                var possibleArtists = _authorService.GetCandidates(artistTag);
                foreach (var author in possibleArtists)
                {
                    candidateReleases.AddRange(GetDbCandidatesByAuthor(localEdition, author, includeExisting));
                }
            }

            return(candidateReleases);
        }
Пример #5
0
        private void EnsureData(LocalEdition edition)
        {
            if (edition.Edition != null && edition.Edition.Book.Value.Author.Value.QualityProfileId == 0)
            {
                var rootFolder     = _rootFolderService.GetBestRootFolder(edition.LocalBooks.First().Path);
                var qualityProfile = _qualityProfileService.Get(rootFolder.DefaultQualityProfileId);

                var author = edition.Edition.Book.Value.Author.Value;
                author.QualityProfileId = qualityProfile.Id;
                author.QualityProfile   = qualityProfile;
            }
        }
Пример #6
0
        private void GetBestRelease(LocalEdition localBookRelease, IEnumerable <CandidateEdition> candidateReleases, List <LocalBook> extraTracksOnDisk, out bool seenCandidate)
        {
            var watch = System.Diagnostics.Stopwatch.StartNew();

            _logger.Debug("Matching {0} track files against candidates", localBookRelease.TrackCount);
            _logger.Trace("Processing files:\n{0}", string.Join("\n", localBookRelease.LocalBooks.Select(x => x.Path)));

            var bestDistance = localBookRelease.Edition != null?localBookRelease.Distance.NormalizedDistance() : 1.0;

            seenCandidate = false;

            foreach (var candidateRelease in candidateReleases)
            {
                seenCandidate = true;

                var release = candidateRelease.Edition;
                _logger.Debug($"Trying Release {release}");
                var rwatch = System.Diagnostics.Stopwatch.StartNew();

                var extraTrackPaths = candidateRelease.ExistingFiles.Select(x => x.Path).ToList();
                var extraTracks     = extraTracksOnDisk.Where(x => extraTrackPaths.Contains(x.Path)).ToList();
                var allLocalTracks  = localBookRelease.LocalBooks.Concat(extraTracks).DistinctBy(x => x.Path).ToList();

                var distance     = DistanceCalculator.BookDistance(allLocalTracks, release);
                var currDistance = distance.NormalizedDistance();

                rwatch.Stop();
                _logger.Debug("Release {0} has distance {1} vs best distance {2} [{3}ms]",
                              release,
                              currDistance,
                              bestDistance,
                              rwatch.ElapsedMilliseconds);
                if (currDistance < bestDistance)
                {
                    bestDistance = currDistance;
                    localBookRelease.Distance       = distance;
                    localBookRelease.Edition        = release;
                    localBookRelease.ExistingTracks = extraTracks;
                    if (currDistance == 0.0)
                    {
                        break;
                    }
                }
            }

            watch.Stop();
            _logger.Debug($"Best release: {localBookRelease.Edition} Distance {localBookRelease.Distance.NormalizedDistance()} found in {watch.ElapsedMilliseconds}ms");
        }
Пример #7
0
        public LocalEdition Augment(LocalEdition localAlbum)
        {
            foreach (var augmenter in _albumAugmenters)
            {
                try
                {
                    augmenter.Aggregate(localAlbum, false);
                }
                catch (Exception ex)
                {
                    _logger.Warn(ex, ex.Message);
                }
            }

            return(localAlbum);
        }
Пример #8
0
        private List <CandidateEdition> GetDbCandidatesByAuthor(LocalEdition localEdition, Author author, bool includeExisting)
        {
            _logger.Trace("Getting candidates for {0}", author);
            var candidateReleases = new List <CandidateEdition>();

            var bookTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.BookTitle) ?? "";

            if (bookTag.IsNotNullOrWhiteSpace())
            {
                var possibleBooks = _bookService.GetCandidates(author.AuthorMetadataId, bookTag);
                foreach (var book in possibleBooks)
                {
                    candidateReleases.AddRange(GetDbCandidatesByBook(book, includeExisting));
                }
            }

            return(candidateReleases);
        }
Пример #9
0
        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");
        }
Пример #10
0
        private List <LocalBook> ToLocalTrack(IEnumerable <BookFile> trackfiles, LocalEdition localRelease)
        {
            var scanned     = trackfiles.Join(localRelease.LocalBooks, t => t.Path, l => l.Path, (track, localTrack) => localTrack);
            var toScan      = trackfiles.ExceptBy(t => t.Path, scanned, s => s.Path, StringComparer.InvariantCulture);
            var localTracks = scanned.Concat(toScan.Select(x => new LocalBook
            {
                Path           = x.Path,
                Size           = x.Size,
                Modified       = x.Modified,
                FileTrackInfo  = _audioTagService.ReadTags(x.Path),
                ExistingFile   = true,
                AdditionalFile = true,
                Quality        = x.Quality
            }))
                              .ToList();

            localTracks.ForEach(x => _augmentingService.Augment(x, true));

            return(localTracks);
        }
Пример #11
0
        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");
        }
Пример #12
0
        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);
        }
Пример #13
0
        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);
                }
            }
        }
Пример #14
0
        public List <CandidateEdition> GetRemoteCandidates(LocalEdition localEdition)
        {
            // Gets candidate book releases from the metadata server.
            // Will eventually need adding locally if we find a match
            var watch = System.Diagnostics.Stopwatch.StartNew();

            List <Book> remoteBooks = null;
            var         candidates  = new List <CandidateEdition>();

            var goodreads = localEdition.LocalBooks.Select(x => x.FileTrackInfo.GoodreadsId).Distinct().ToList();
            var isbns     = localEdition.LocalBooks.Select(x => x.FileTrackInfo.Isbn).Distinct().ToList();
            var asins     = localEdition.LocalBooks.Select(x => x.FileTrackInfo.Asin).Distinct().ToList();

            try
            {
                if (goodreads.Count == 1 && goodreads[0].IsNotNullOrWhiteSpace())
                {
                    if (int.TryParse(goodreads[0], out var id))
                    {
                        _logger.Trace($"Searching by goodreads id {id}");

                        remoteBooks = _bookSearchService.SearchByGoodreadsId(id);
                    }
                }

                if ((remoteBooks == null || !remoteBooks.Any()) &&
                    isbns.Count == 1 &&
                    isbns[0].IsNotNullOrWhiteSpace())
                {
                    _logger.Trace($"Searching by isbn {isbns[0]}");

                    remoteBooks = _bookSearchService.SearchByIsbn(isbns[0]);
                }

                // Calibre puts junk asins into books it creates so check for sensible length
                if ((remoteBooks == null || !remoteBooks.Any()) &&
                    asins.Count == 1 &&
                    asins[0].IsNotNullOrWhiteSpace() &&
                    asins[0].Length == 10)
                {
                    _logger.Trace($"Searching by asin {asins[0]}");

                    remoteBooks = _bookSearchService.SearchByAsin(asins[0]);
                }

                // if no asin/isbn or no result, fall back to text search
                if (remoteBooks == null || !remoteBooks.Any())
                {
                    // fall back to author / book name search
                    string artistTag;

                    if (TrackGroupingService.IsVariousArtists(localEdition.LocalBooks))
                    {
                        artistTag = "Various Artists";
                    }
                    else
                    {
                        artistTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? "";
                    }

                    var albumTag = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.AlbumTitle) ?? "";

                    if (artistTag.IsNullOrWhiteSpace() || albumTag.IsNullOrWhiteSpace())
                    {
                        return(candidates);
                    }

                    remoteBooks = _bookSearchService.SearchForNewBook(albumTag, artistTag);

                    if (!remoteBooks.Any())
                    {
                        var albumSearch  = _bookSearchService.SearchForNewBook(albumTag, null);
                        var artistSearch = _bookSearchService.SearchForNewBook(artistTag, null);

                        remoteBooks = albumSearch.Concat(artistSearch).DistinctBy(x => x.ForeignBookId).ToList();
                    }
                }
            }
            catch (SkyHookException e)
            {
                _logger.Info(e, "Skipping book due to SkyHook error");
                remoteBooks = new List <Book>();
            }

            foreach (var book in remoteBooks)
            {
                // 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;
                    candidates.Add(new CandidateEdition
                    {
                        Edition       = edition,
                        ExistingFiles = new List <BookFile>()
                    });
                }
            }

            watch.Stop();
            _logger.Debug($"Getting {candidates.Count} remote candidates from tags for {localEdition.LocalBooks.Count} tracks took {watch.ElapsedMilliseconds}ms");

            return(candidates);
        }
Пример #15
0
        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);
        }