using System; using System.Collections.Generic; using System.IO; using System.Linq; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore; using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Music.Events; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.History { public interface IHistoryService { PagingSpec Paged(PagingSpec pagingSpec); EntityHistory MostRecentForAlbum(int albumId); EntityHistory MostRecentForDownloadId(string downloadId); EntityHistory Get(int historyId); List GetByArtist(int artistId, EntityHistoryEventType? eventType); List GetByAlbum(int albumId, EntityHistoryEventType? eventType); List Find(string downloadId, EntityHistoryEventType eventType); List FindByDownloadId(string downloadId); string FindDownloadId(TrackImportedEvent trackedDownload); List Since(DateTime date, EntityHistoryEventType? eventType); void UpdateMany(IList items); } public class EntityHistoryService : IHistoryService, IHandle, IHandle, IHandle, IHandle, IHandle, IHandle, IHandle, IHandle, IHandle, IHandle { private readonly IHistoryRepository _historyRepository; private readonly Logger _logger; public EntityHistoryService(IHistoryRepository historyRepository, Logger logger) { _historyRepository = historyRepository; _logger = logger; } public PagingSpec Paged(PagingSpec pagingSpec) { return _historyRepository.GetPaged(pagingSpec); } public EntityHistory MostRecentForAlbum(int albumId) { return _historyRepository.MostRecentForAlbum(albumId); } public EntityHistory MostRecentForDownloadId(string downloadId) { return _historyRepository.MostRecentForDownloadId(downloadId); } public EntityHistory Get(int historyId) { return _historyRepository.Get(historyId); } public List GetByArtist(int artistId, EntityHistoryEventType? eventType) { return _historyRepository.GetByArtist(artistId, eventType); } public List GetByAlbum(int albumId, EntityHistoryEventType? eventType) { return _historyRepository.GetByAlbum(albumId, eventType); } public List Find(string downloadId, EntityHistoryEventType eventType) { return _historyRepository.FindByDownloadId(downloadId).Where(c => c.EventType == eventType).ToList(); } public List FindByDownloadId(string downloadId) { return _historyRepository.FindByDownloadId(downloadId); } public string FindDownloadId(TrackImportedEvent trackedDownload) { _logger.Debug("Trying to find downloadId for {0} from history", trackedDownload.ImportedTrack.Path); var albumIds = trackedDownload.TrackInfo.Tracks.Select(c => c.AlbumId).ToList(); var allHistory = _historyRepository.FindDownloadHistory(trackedDownload.TrackInfo.Artist.Id, trackedDownload.ImportedTrack.Quality); // Find download related items for these episodes var albumsHistory = allHistory.Where(h => albumIds.Contains(h.AlbumId)).ToList(); var processedDownloadId = albumsHistory .Where(c => c.EventType != EntityHistoryEventType.Grabbed && c.DownloadId != null) .Select(c => c.DownloadId); var stillDownloading = albumsHistory.Where(c => c.EventType == EntityHistoryEventType.Grabbed && !processedDownloadId.Contains(c.DownloadId)).ToList(); string downloadId = null; if (stillDownloading.Any()) { foreach (var matchingHistory in trackedDownload.TrackInfo.Tracks.Select(e => stillDownloading.Where(c => c.AlbumId == e.AlbumId).ToList())) { if (matchingHistory.Count != 1) { return null; } var newDownloadId = matchingHistory.Single().DownloadId; if (downloadId == null || downloadId == newDownloadId) { downloadId = newDownloadId; } else { return null; } } } return downloadId; } public void Handle(AlbumGrabbedEvent message) { foreach (var album in message.Album.Albums) { var history = new EntityHistory { EventType = EntityHistoryEventType.Grabbed, Date = DateTime.UtcNow, Quality = message.Album.ParsedAlbumInfo.Quality, SourceTitle = message.Album.Release.Title, ArtistId = album.ArtistId, AlbumId = album.Id, DownloadId = message.DownloadId }; history.Data.Add("Indexer", message.Album.Release.Indexer); history.Data.Add("NzbInfoUrl", message.Album.Release.InfoUrl); history.Data.Add("ReleaseGroup", message.Album.ParsedAlbumInfo.ReleaseGroup); history.Data.Add("Age", message.Album.Release.Age.ToString()); history.Data.Add("AgeHours", message.Album.Release.AgeHours.ToString()); history.Data.Add("AgeMinutes", message.Album.Release.AgeMinutes.ToString()); history.Data.Add("PublishedDate", message.Album.Release.PublishDate.ToString("s") + "Z"); history.Data.Add("DownloadClient", message.DownloadClient); history.Data.Add("Size", message.Album.Release.Size.ToString()); history.Data.Add("DownloadUrl", message.Album.Release.DownloadUrl); history.Data.Add("Guid", message.Album.Release.Guid); history.Data.Add("Protocol", ((int)message.Album.Release.DownloadProtocol).ToString()); history.Data.Add("DownloadForced", (!message.Album.DownloadAllowed).ToString()); if (!message.Album.ParsedAlbumInfo.ReleaseHash.IsNullOrWhiteSpace()) { history.Data.Add("ReleaseHash", message.Album.ParsedAlbumInfo.ReleaseHash); } var torrentRelease = message.Album.Release as TorrentInfo; if (torrentRelease != null) { history.Data.Add("TorrentInfoHash", torrentRelease.InfoHash); } _historyRepository.Insert(history); } } public void Handle(AlbumImportIncompleteEvent message) { foreach (var album in message.TrackedDownload.RemoteAlbum.Albums) { var history = new EntityHistory { EventType = EntityHistoryEventType.AlbumImportIncomplete, Date = DateTime.UtcNow, Quality = message.TrackedDownload.RemoteAlbum.ParsedAlbumInfo?.Quality ?? new QualityModel(), SourceTitle = message.TrackedDownload.DownloadItem.Title, ArtistId = album.ArtistId, AlbumId = album.Id, DownloadId = message.TrackedDownload.DownloadItem.DownloadId }; history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson()); history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup); _historyRepository.Insert(history); } } public void Handle(TrackImportedEvent message) { if (!message.NewDownload) { return; } var downloadId = message.DownloadId; if (downloadId.IsNullOrWhiteSpace()) { downloadId = FindDownloadId(message); } foreach (var track in message.TrackInfo.Tracks) { var history = new EntityHistory { EventType = EntityHistoryEventType.TrackFileImported, Date = DateTime.UtcNow, Quality = message.TrackInfo.Quality, SourceTitle = message.ImportedTrack.SceneName ?? Path.GetFileNameWithoutExtension(message.TrackInfo.Path), ArtistId = message.TrackInfo.Artist.Id, AlbumId = message.TrackInfo.Album.Id, TrackId = track.Id, DownloadId = downloadId }; // Won't have a value since we publish this event before saving to DB. // history.Data.Add("FileId", message.ImportedEpisode.Id.ToString()); history.Data.Add("DroppedPath", message.TrackInfo.Path); history.Data.Add("ImportedPath", message.ImportedTrack.Path); history.Data.Add("DownloadClient", message.DownloadClientInfo.Name); history.Data.Add("ReleaseGroup", message.TrackInfo.ReleaseGroup); _historyRepository.Insert(history); } } public void Handle(DownloadFailedEvent message) { foreach (var albumId in message.AlbumIds) { var history = new EntityHistory { EventType = EntityHistoryEventType.DownloadFailed, Date = DateTime.UtcNow, Quality = message.Quality, SourceTitle = message.SourceTitle, ArtistId = message.ArtistId, AlbumId = albumId, DownloadId = message.DownloadId }; history.Data.Add("DownloadClient", message.DownloadClient); history.Data.Add("Message", message.Message); _historyRepository.Insert(history); } } public void Handle(DownloadCompletedEvent message) { foreach (var album in message.TrackedDownload.RemoteAlbum.Albums) { var history = new EntityHistory { EventType = EntityHistoryEventType.DownloadImported, Date = DateTime.UtcNow, Quality = message.TrackedDownload.RemoteAlbum.ParsedAlbumInfo?.Quality ?? new QualityModel(), SourceTitle = message.TrackedDownload.DownloadItem.Title, ArtistId = album.ArtistId, AlbumId = album.Id, DownloadId = message.TrackedDownload.DownloadItem.DownloadId }; history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup); _historyRepository.Insert(history); } } public void Handle(TrackFileDeletedEvent message) { if (message.Reason == DeleteMediaFileReason.NoLinkedEpisodes) { _logger.Debug("Removing track file from DB as part of cleanup routine, not creating history event."); return; } else if (message.Reason == DeleteMediaFileReason.ManualOverride) { _logger.Debug("Removing track file from DB as part of manual override of existing file, not creating history event."); return; } foreach (var track in message.TrackFile.Tracks.Value) { var history = new EntityHistory { EventType = EntityHistoryEventType.TrackFileDeleted, Date = DateTime.UtcNow, Quality = message.TrackFile.Quality, SourceTitle = message.TrackFile.Path, ArtistId = message.TrackFile.Artist.Value.Id, AlbumId = message.TrackFile.AlbumId, TrackId = track.Id, }; history.Data.Add("Reason", message.Reason.ToString()); history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup); _historyRepository.Insert(history); } } public void Handle(TrackFileRenamedEvent message) { var sourcePath = message.OriginalPath; var path = message.TrackFile.Path; foreach (var track in message.TrackFile.Tracks.Value) { var history = new EntityHistory { EventType = EntityHistoryEventType.TrackFileRenamed, Date = DateTime.UtcNow, Quality = message.TrackFile.Quality, SourceTitle = message.OriginalPath, ArtistId = message.TrackFile.Artist.Value.Id, AlbumId = message.TrackFile.AlbumId, TrackId = track.Id, }; history.Data.Add("SourcePath", sourcePath); history.Data.Add("Path", path); history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup); _historyRepository.Insert(history); } } public void Handle(TrackFileRetaggedEvent message) { var path = message.TrackFile.Path; foreach (var track in message.TrackFile.Tracks.Value) { var history = new EntityHistory { EventType = EntityHistoryEventType.TrackFileRetagged, Date = DateTime.UtcNow, Quality = message.TrackFile.Quality, SourceTitle = path, ArtistId = message.TrackFile.Artist.Value.Id, AlbumId = message.TrackFile.AlbumId, TrackId = track.Id, }; history.Data.Add("TagsScrubbed", message.Scrubbed.ToString()); history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup); history.Data.Add("Diff", message.Diff.Select(x => new { Field = x.Key, OldValue = x.Value.Item1, NewValue = x.Value.Item2 }).ToJson()); _historyRepository.Insert(history); } } public void Handle(ArtistsDeletedEvent message) { _historyRepository.DeleteForArtists(message.Artists.Select(x => x.Id).ToList()); } public void Handle(DownloadIgnoredEvent message) { var historyToAdd = new List(); foreach (var albumId in message.AlbumIds) { var history = new EntityHistory { EventType = EntityHistoryEventType.DownloadIgnored, Date = DateTime.UtcNow, Quality = message.Quality, SourceTitle = message.SourceTitle, ArtistId = message.ArtistId, AlbumId = albumId, DownloadId = message.DownloadId }; history.Data.Add("DownloadClient", message.DownloadClientInfo.Name); history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup); history.Data.Add("Message", message.Message); historyToAdd.Add(history); } _historyRepository.InsertMany(historyToAdd); } public List Since(DateTime date, EntityHistoryEventType? eventType) { return _historyRepository.Since(date, eventType); } public void UpdateMany(IList items) { _historyRepository.UpdateMany(items); } } }