From 770b89c2b36a123eb17b580fa7055a29e6674555 Mon Sep 17 00:00:00 2001
From: Mark McDowall <mark@mcdowall.ca>
Date: Tue, 3 Mar 2020 18:09:29 -0800
Subject: [PATCH] Track fully imported downloads in separate history table

New: Improved detection of already imported downloads
Closes #3554
---
 src/NzbDrone.Api/History/HistoryModule.cs     |  10 +-
 src/NzbDrone.Api/History/HistoryResource.cs   |   4 +-
 .../Datastore/DatabaseRelationshipFixture.cs  |   9 +-
 .../AlreadyImportedSpecificationFixture.cs    |  30 +--
 .../RssSync/HistorySpecificationFixture.cs    |  42 ++--
 .../ImportFixture.cs                          |  12 +-
 .../ProcessFixture.cs                         |   6 +-
 .../DownloadClientFixtureBase.cs              |   4 +-
 .../ProcessFailedFixture.cs                   |   6 +-
 .../ProcessFixture.cs                         |  10 +-
 .../TrackedDownloadAlreadyImportedFixture.cs  |  24 +-
 .../TrackedDownloadServiceFixture.cs          |  20 +-
 .../HistoryTests/HistoryRepositoryFixture.cs  |  12 +-
 .../HistoryTests/HistoryServiceFixture.cs     |  10 +-
 .../CleanupOrphanedHistoryItemsFixture.cs     |  11 +-
 .../QueueTests/QueueServiceFixture.cs         |   4 +
 .../Analytics/AnalyticsService.cs             |   2 +-
 .../Migration/139_add_download_history.cs     |  98 ++++++++
 src/NzbDrone.Core/Datastore/TableMapping.cs   |   7 +-
 .../AlreadyImportedSpecification.cs           |   4 +-
 .../RssSync/HistorySpecification.cs           |   2 +-
 .../Clients/Blackhole/TorrentBlackhole.cs     |   2 +-
 .../Clients/Blackhole/UsenetBlackhole.cs      |   2 +-
 .../Download/Clients/Deluge/Deluge.cs         |   2 +-
 .../DownloadStation/TorrentDownloadStation.cs |   2 +-
 .../DownloadStation/UsenetDownloadStation.cs  |   2 +-
 .../Download/Clients/Hadouken/Hadouken.cs     |   2 +-
 .../Download/Clients/NzbVortex/NzbVortex.cs   |   2 +-
 .../Download/Clients/Nzbget/Nzbget.cs         |   4 +-
 .../Download/Clients/Pneumatic/Pneumatic.cs   |   2 +-
 .../Clients/QBittorrent/QBittorrent.cs        |   2 +-
 .../Download/Clients/Sabnzbd/Sabnzbd.cs       |   4 +-
 .../Clients/Transmission/TransmissionBase.cs  |   2 +-
 .../Download/Clients/rTorrent/RTorrent.cs     |   2 +-
 .../Download/Clients/uTorrent/UTorrent.cs     |   2 +-
 .../Download/CompletedDownloadService.cs      |   5 +
 .../Download/DownloadClientItem.cs            |  30 ++-
 .../Download/DownloadEventHub.cs              |   4 +-
 .../Download/DownloadIgnoredEvent.cs          |   2 +-
 src/NzbDrone.Core/Download/DownloadService.cs |   3 +-
 .../Download/EpisodeGrabbedEvent.cs           |   2 +
 .../Download/FailedDownloadService.cs         |  21 +-
 .../Download/History/DownloadHistory.cs       |  35 +++
 .../History/DownloadHistoryRepository.cs      |  31 +++
 .../History/DownloadHistoryService.cs         | 216 ++++++++++++++++++
 src/NzbDrone.Core/Download/IDownloadClient.cs |   1 -
 .../Download/IgnoredDownloadService.cs        |   2 +-
 .../TrackedDownloadAlreadyImported.cs         |   6 +-
 .../TrackedDownloadService.cs                 |  52 ++---
 .../History/{History.cs => EpisodeHistory.cs} |   8 +-
 .../History/HistoryRepository.cs              |  50 ++--
 src/NzbDrone.Core/History/HistoryService.cs   |  85 +++----
 .../AlreadyImportedSpecification.cs           |   4 +-
 .../Events/EpisodeImportFailedEvent.cs        |   4 +-
 .../MediaFiles/Events/EpisodeImportedEvent.cs |   4 +-
 .../Notifications/NotificationService.cs      |   2 +-
 src/NzbDrone.Core/Queue/QueueService.cs       |   2 +-
 src/Sonarr.Api.V3/History/HistoryModule.cs    |  14 +-
 src/Sonarr.Api.V3/History/HistoryResource.cs  |   4 +-
 59 files changed, 688 insertions(+), 258 deletions(-)
 create mode 100644 src/NzbDrone.Core/Datastore/Migration/139_add_download_history.cs
 create mode 100644 src/NzbDrone.Core/Download/History/DownloadHistory.cs
 create mode 100644 src/NzbDrone.Core/Download/History/DownloadHistoryRepository.cs
 create mode 100644 src/NzbDrone.Core/Download/History/DownloadHistoryService.cs
 rename src/NzbDrone.Core/History/{History.cs => EpisodeHistory.cs} (85%)

diff --git a/src/NzbDrone.Api/History/HistoryModule.cs b/src/NzbDrone.Api/History/HistoryModule.cs
index 540800808..c4831a648 100644
--- a/src/NzbDrone.Api/History/HistoryModule.cs
+++ b/src/NzbDrone.Api/History/HistoryModule.cs
@@ -32,7 +32,7 @@ namespace NzbDrone.Api.History
             Post("/failed",  x => MarkAsFailed());
         }
 
-        protected HistoryResource MapToResource(Core.History.History model)
+        protected HistoryResource MapToResource(EpisodeHistory model)
         {
             var resource = model.ToResource();
 
@@ -50,12 +50,12 @@ namespace NzbDrone.Api.History
         private PagingResource<HistoryResource> GetHistory(PagingResource<HistoryResource> pagingResource)
         {
             var episodeId = Request.Query.EpisodeId;
-            var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, Core.History.History>("date", SortDirection.Descending);
+            var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, EpisodeHistory>("date", SortDirection.Descending);
             var filter = pagingResource.Filters.FirstOrDefault();
 
             if (filter != null && filter.Key == "eventType")
             {
-                var filterValue = (HistoryEventType)Convert.ToInt32(filter.Value);
+                var filterValue = (EpisodeHistoryEventType)Convert.ToInt32(filter.Value);
                 pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue);
             }
 
@@ -79,11 +79,11 @@ namespace NzbDrone.Api.History
             }
 
             DateTime date = DateTime.Parse(queryDate.Value);
-            HistoryEventType? eventType = null;
+            EpisodeHistoryEventType? eventType = null;
 
             if (queryEventType.HasValue)
             {
-                eventType = (HistoryEventType)Convert.ToInt32(queryEventType.Value);
+                eventType = (EpisodeHistoryEventType)Convert.ToInt32(queryEventType.Value);
             }
 
             return _historyService.Since(date, eventType).Select(MapToResource).ToList();
diff --git a/src/NzbDrone.Api/History/HistoryResource.cs b/src/NzbDrone.Api/History/HistoryResource.cs
index e15bb7f7e..9b0fc22c9 100644
--- a/src/NzbDrone.Api/History/HistoryResource.cs
+++ b/src/NzbDrone.Api/History/HistoryResource.cs
@@ -20,7 +20,7 @@ namespace NzbDrone.Api.History
         public string DownloadId { get; set; }
         public Language Language { get; set; }
 
-        public HistoryEventType EventType { get; set; }
+        public EpisodeHistoryEventType EventType { get; set; }
 
         public Dictionary<string, string> Data { get; set; }
 
@@ -30,7 +30,7 @@ namespace NzbDrone.Api.History
 
     public static class HistoryResourceMapper
     {
-        public static HistoryResource ToResource(this Core.History.History model)
+        public static HistoryResource ToResource(this EpisodeHistory model)
         {
             if (model == null) return null;
 
diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs
index cb9d7ea86..bed951e0d 100644
--- a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs
+++ b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs
@@ -2,6 +2,7 @@
 using FizzWare.NBuilder;
 using FluentAssertions;
 using NUnit.Framework;
+using NzbDrone.Core.History;
 using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Test.Framework;
@@ -59,21 +60,21 @@ namespace NzbDrone.Core.Test.Datastore
         {
             var quality = new QualityModel { Quality = Quality.Bluray720p, Revision = new Revision(version: 2 )};
 
-            var history = Builder<History.History>.CreateNew()
+            var history = Builder<EpisodeHistory>.CreateNew()
                                                   .With(c => c.Id = 0)
                                                   .With(c => c.Quality = quality)
                                                   .Build();
 
             Db.Insert(history);
 
-            var loadedQuality = Db.Single<History.History>().Quality;
+            var loadedQuality = Db.Single<EpisodeHistory>().Quality;
             loadedQuality.Should().Be(quality);
         }
 
         [Test]
         public void embedded_list_of_document_with_json()
         {
-            var history = Builder<History.History>.CreateListOfSize(2)
+            var history = Builder<EpisodeHistory>.CreateListOfSize(2)
                                                   .All().With(c => c.Id = 0)
                                                   .Build().ToList();
 
@@ -83,7 +84,7 @@ namespace NzbDrone.Core.Test.Datastore
 
             Db.InsertMany(history);
 
-            var returnedHistory = Db.All<History.History>();
+            var returnedHistory = Db.All<EpisodeHistory>();
 
             returnedHistory[0].Quality.Quality.Should().Be(Quality.HDTV1080p);
         }
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/AlreadyImportedSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/AlreadyImportedSpecificationFixture.cs
index 5e563e293..4ea248175 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/AlreadyImportedSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/AlreadyImportedSpecificationFixture.cs
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         private QualityModel _hdtv720p;
         private QualityModel _hdtv1080p;
         private RemoteEpisode _remoteEpisode;
-        private List<History.History> _history;
+        private List<EpisodeHistory> _history;
 
         [SetUp]
         public void Setup()
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
                                               .Build()
             };
 
-            _history = new List<History.History>();
+            _history = new List<EpisodeHistory>();
 
             Mocker.GetMock<IConfigService>()
                   .SetupGet(s => s.EnableCompletedDownloadHandling)
@@ -75,9 +75,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
                   .Returns(false);
         }
 
-        private void GivenHistoryItem(string downloadId, string sourceTitle, QualityModel quality, HistoryEventType eventType)
+        private void GivenHistoryItem(string downloadId, string sourceTitle, QualityModel quality, EpisodeHistoryEventType eventType)
         {
-            _history.Add(new History.History
+            _history.Add(new EpisodeHistory
                          {
                              DownloadId = downloadId,
                              SourceTitle = sourceTitle,
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         [Test]
         public void should_be_accepted_if_episode_does_not_have_imported_event()
         {
-            GivenHistoryItem(Guid.NewGuid().ToString().ToUpper(), TITLE, _hdtv720p, HistoryEventType.Grabbed);
+            GivenHistoryItem(Guid.NewGuid().ToString().ToUpper(), TITLE, _hdtv720p, EpisodeHistoryEventType.Grabbed);
 
             Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
         }
@@ -122,8 +122,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             var downloadId = Guid.NewGuid().ToString().ToUpper();
 
-            GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
-            GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.DownloadFolderImported);
+            GivenHistoryItem(downloadId, TITLE, _hdtv720p, EpisodeHistoryEventType.Grabbed);
+            GivenHistoryItem(downloadId, TITLE, _hdtv720p, EpisodeHistoryEventType.DownloadFolderImported);
 
             Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
         }
@@ -133,8 +133,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             var downloadId = Guid.NewGuid().ToString().ToUpper();
 
-            GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
-            GivenHistoryItem(downloadId, TITLE, _hdtv1080p, HistoryEventType.DownloadFolderImported);
+            GivenHistoryItem(downloadId, TITLE, _hdtv720p, EpisodeHistoryEventType.Grabbed);
+            GivenHistoryItem(downloadId, TITLE, _hdtv1080p, EpisodeHistoryEventType.DownloadFolderImported);
 
             _remoteEpisode.Release = Builder<TorrentInfo>.CreateNew()
                                                          .With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
@@ -149,8 +149,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             var downloadId = Guid.NewGuid().ToString().ToUpper();
 
-            GivenHistoryItem(null, TITLE, _hdtv720p, HistoryEventType.Grabbed);
-            GivenHistoryItem(downloadId, TITLE, _hdtv1080p, HistoryEventType.DownloadFolderImported);
+            GivenHistoryItem(null, TITLE, _hdtv720p, EpisodeHistoryEventType.Grabbed);
+            GivenHistoryItem(downloadId, TITLE, _hdtv1080p, EpisodeHistoryEventType.DownloadFolderImported);
 
             _remoteEpisode.Release = Builder<TorrentInfo>.CreateNew()
                                                          .With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
@@ -165,8 +165,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             var downloadId = Guid.NewGuid().ToString().ToUpper();
 
-            GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
-            GivenHistoryItem(downloadId, TITLE, _hdtv1080p, HistoryEventType.DownloadFolderImported);
+            GivenHistoryItem(downloadId, TITLE, _hdtv720p, EpisodeHistoryEventType.Grabbed);
+            GivenHistoryItem(downloadId, TITLE, _hdtv1080p, EpisodeHistoryEventType.DownloadFolderImported);
 
             _remoteEpisode.Release = Builder<TorrentInfo>.CreateNew()
                                                          .With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
@@ -181,8 +181,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             var downloadId = Guid.NewGuid().ToString().ToUpper();
 
-            GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
-            GivenHistoryItem(downloadId, TITLE, _hdtv1080p, HistoryEventType.DownloadFolderImported);
+            GivenHistoryItem(downloadId, TITLE, _hdtv720p, EpisodeHistoryEventType.Grabbed);
+            GivenHistoryItem(downloadId, TITLE, _hdtv1080p, EpisodeHistoryEventType.DownloadFolderImported);
 
             _remoteEpisode.Release = Builder<TorrentInfo>.CreateNew()
                                                          .With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs
index 00fd23463..4bf22de35 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs
@@ -84,10 +84,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
                   .Returns(true);
         }
 
-        private void GivenMostRecentForEpisode(int episodeId, string downloadId, Tuple<QualityModel, Language> quality, DateTime date, HistoryEventType eventType)
+        private void GivenMostRecentForEpisode(int episodeId, string downloadId, Tuple<QualityModel, Language> quality, DateTime date, EpisodeHistoryEventType eventType)
         {
             Mocker.GetMock<IHistoryService>().Setup(s => s.MostRecentForEpisode(episodeId))
-                  .Returns(new History.History { DownloadId = downloadId, Quality = quality.Item1, Date = date, EventType = eventType, Language = quality.Item2 });
+                  .Returns(new EpisodeHistory { DownloadId = downloadId, Quality = quality.Item1, Date = date, EventType = eventType, Language = quality.Item2 });
         }
 
         private void GivenCdhDisabled()
@@ -106,14 +106,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
         [Test]
         public void should_return_true_if_latest_history_item_is_null()
         {
-            Mocker.GetMock<IHistoryService>().Setup(s => s.MostRecentForEpisode(It.IsAny<int>())).Returns((History.History)null);
+            Mocker.GetMock<IHistoryService>().Setup(s => s.MostRecentForEpisode(It.IsAny<int>())).Returns((EpisodeHistory)null);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
         }
 
         [Test]
         public void should_return_true_if_latest_history_item_is_not_grabbed()
         {
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.DownloadFailed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.DownloadFailed);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
         }
 
@@ -127,46 +127,46 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
         [Test]
         public void should_return_true_if_latest_history_item_is_older_than_twelve_hours()
         {
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-13), HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-13), EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
         }
 
         [Test]
         public void should_be_upgradable_if_only_episode_is_upgradable()
         {
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue();
         }
 
         [Test]
         public void should_be_upgradable_if_both_episodes_are_upgradable()
         {
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
-            GivenMostRecentForEpisode(SECOND_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(SECOND_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
         }
 
         [Test]
         public void should_not_be_upgradable_if_both_episodes_are_not_upgradable()
         {
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
-            GivenMostRecentForEpisode(SECOND_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(SECOND_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse();
         }
 
         [Test]
         public void should_be_not_upgradable_if_only_first_episodes_is_upgradable()
         {
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse();
         }
 
         [Test]
         public void should_be_not_upgradable_if_only_second_episodes_is_upgradable()
         {
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
-            GivenMostRecentForEpisode(SECOND_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(SECOND_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse();
         }
 
@@ -177,7 +177,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
             _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1));
             _upgradableQuality = new Tuple<QualityModel, Language>(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.English);
 
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
 
             _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
         }
@@ -190,7 +190,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
             _parseResultSingle.ParsedEpisodeInfo.Language = Language.Spanish;
             _upgradableQuality = new Tuple<QualityModel, Language>(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.English);
 
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
 
             _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue();
         }
@@ -202,7 +202,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
             _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1));
             _upgradableQuality = new Tuple<QualityModel, Language>(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.Spanish);
 
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EpisodeHistoryEventType.Grabbed);
 
             _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
         }
@@ -210,7 +210,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
         [Test]
         public void should_return_false_if_latest_history_item_is_only_one_hour_old()
         {
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-1), HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-1), EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse();
         }
 
@@ -218,7 +218,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
         public void should_return_false_if_latest_history_has_a_download_id_and_cdh_is_disabled()
         {
             GivenCdhDisabled();
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue();
         }
 
@@ -230,7 +230,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
             _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1));
             _upgradableQuality = new Tuple<QualityModel, Language>(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.Spanish);
 
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), EpisodeHistoryEventType.Grabbed);
 
             _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
         }
@@ -239,7 +239,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
         public void should_return_false_if_only_episode_is_not_upgradable_and_cdh_is_disabled()
         {
             GivenCdhDisabled();
-            GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _notupgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed);
+            GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _notupgradableQuality, DateTime.UtcNow.AddDays(-100), EpisodeHistoryEventType.Grabbed);
             _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
         }
     }
diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs
index 8e061eb47..5ca8e2e9d 100644
--- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
 
             Mocker.GetMock<IHistoryService>()
                   .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
-                  .Returns(new History.History());
+                  .Returns(new EpisodeHistory());
 
             Mocker.GetMock<IParsingService>()
                   .Setup(s => s.GetSeries("Drone.S01E01.HDTV"))
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
             _trackedDownload.DownloadItem.Title = "Droned Pilot"; // Set a badly named download
             Mocker.GetMock<IHistoryService>()
                .Setup(s => s.MostRecentForDownloadId(It.Is<string>(i => i == "1234")))
-               .Returns(new History.History() { SourceTitle = "Droned S01E01" });
+               .Returns(new EpisodeHistory() { SourceTitle = "Droned S01E01" });
 
             Mocker.GetMock<IParsingService>()
                .Setup(s => s.GetSeries(It.IsAny<string>()))
@@ -187,7 +187,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
 
             Mocker.GetMock<IHistoryService>()
                   .Setup(s => s.FindByDownloadId(It.IsAny<string>()))
-                  .Returns(new List<History.History>());
+                  .Returns(new List<EpisodeHistory>());
 
             Subject.Import(_trackedDownload);
 
@@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
                                new ImportResult(new ImportDecision(new LocalEpisode{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure")
                            });
 
-            var history = Builder<History.History>.CreateListOfSize(2)
+            var history = Builder<EpisodeHistory>.CreateListOfSize(2)
                                                   .BuildList();
 
             Mocker.GetMock<IHistoryService>()
@@ -274,7 +274,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
                             new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv", Episodes = new List<Episode> { episode2 } }),"Test Failure")
                 });
 
-            var history = Builder<History.History>.CreateListOfSize(2)
+            var history = Builder<EpisodeHistory>.CreateListOfSize(2)
                                                   .BuildList();
 
             Mocker.GetMock<IHistoryService>()
@@ -282,7 +282,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
                   .Returns(history);
 
             Mocker.GetMock<ITrackedDownloadAlreadyImported>()
-                  .Setup(s => s.IsImported(It.IsAny<TrackedDownload>(), It.IsAny<List<History.History>>()))
+                  .Setup(s => s.IsImported(It.IsAny<TrackedDownload>(), It.IsAny<List<EpisodeHistory>>()))
                   .Returns(true);
 
             Subject.Import(_trackedDownload);
diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs
index 003fbb992..7946a890a 100644
--- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
 
             Mocker.GetMock<IHistoryService>()
                   .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
-                  .Returns(new History.History());
+                  .Returns(new EpisodeHistory());
 
             Mocker.GetMock<IParsingService>()
                   .Setup(s => s.GetSeries("Drone.S01E01.HDTV"))
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
         {
             Mocker.GetMock<IHistoryService>()
                 .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
-                .Returns((History.History)null);
+                .Returns((EpisodeHistory)null);
         }
 
         private void GivenSeriesMatch()
@@ -89,7 +89,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
             _trackedDownload.DownloadItem.Title = "Droned Pilot"; // Set a badly named download
             Mocker.GetMock<IHistoryService>()
                   .Setup(s => s.MostRecentForDownloadId(It.Is<string>(i => i == "1234")))
-                  .Returns(new History.History() { SourceTitle = "Droned S01E01" });
+                  .Returns(new EpisodeHistory() { SourceTitle = "Droned S01E01" });
 
             Mocker.GetMock<IParsingService>()
                   .Setup(s => s.GetSeries(It.IsAny<string>()))
diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs
index 762137861..0f048b75d 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs
@@ -62,7 +62,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
 
         protected void VerifyIdentifiable(DownloadClientItem downloadClientItem)
         {
-            downloadClientItem.DownloadClient.Should().Be(Subject.Definition.Name);
+            downloadClientItem.DownloadClientInfo.Protocol.Should().Be(Subject.Protocol);
+            downloadClientItem.DownloadClientInfo.Id.Should().Be(Subject.Definition.Id);
+            downloadClientItem.DownloadClientInfo.Name.Should().Be(Subject.Definition.Name);
             downloadClientItem.DownloadId.Should().NotBeNullOrEmpty();
             downloadClientItem.Title.Should().NotBeNullOrEmpty();
         }
diff --git a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs
index 8b2395435..29e9c998e 100644
--- a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
     public class ProcessFailedFixture : CoreTest<FailedDownloadService>
     {
         private TrackedDownload _trackedDownload;
-        private List<History.History> _grabHistory;
+        private List<EpisodeHistory> _grabHistory;
 
         [SetUp]
         public void Setup()
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
                                                     .With(h => h.Title = "Drone.S01E01.HDTV")
                                                     .Build();
 
-            _grabHistory = Builder<History.History>.CreateListOfSize(2).BuildList();
+            _grabHistory = Builder<EpisodeHistory>.CreateListOfSize(2).BuildList();
 
             var remoteEpisode = new RemoteEpisode
             {
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
 
 
             Mocker.GetMock<IHistoryService>()
-                  .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
+                  .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed))
                   .Returns(_grabHistory);
 
         }
diff --git a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs
index e54d16859..dc63d7b7e 100644
--- a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
     public class ProcessFixture : CoreTest<FailedDownloadService>
     {
         private TrackedDownload _trackedDownload;
-        private List<History.History> _grabHistory;
+        private List<EpisodeHistory> _grabHistory;
 
         [SetUp]
         public void Setup()
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
                                                     .With(h => h.Title = "Drone.S01E01.HDTV")
                                                     .Build();
 
-            _grabHistory = Builder<History.History>.CreateListOfSize(2).BuildList();
+            _grabHistory = Builder<EpisodeHistory>.CreateListOfSize(2).BuildList();
 
             var remoteEpisode = new RemoteEpisode
             {
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
 
 
             Mocker.GetMock<IHistoryService>()
-                  .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
+                  .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed))
                   .Returns(_grabHistory);
 
         }
@@ -54,8 +54,8 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
         private void GivenNoGrabbedHistory()
         {
             Mocker.GetMock<IHistoryService>()
-                .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
-                .Returns(new List<History.History>());
+                .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed))
+                .Returns(new List<EpisodeHistory>());
         }
 
         [Test]
diff --git a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs
index aaad9640a..87c3cebad 100644
--- a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
     {
         private List<Episode> _episodes;
         private TrackedDownload _trackedDownload;
-        private List<History.History> _historyItems;
+        private List<EpisodeHistory> _historyItems;
 
         [SetUp]
         public void Setup()
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
                                                        .With(t => t.RemoteEpisode = remoteEpisode)
                                                        .Build();
 
-            _historyItems = new List<History.History>();
+            _historyItems = new List<EpisodeHistory>();
         }
 
         public void GivenEpisodes(int count)
@@ -39,12 +39,12 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
                                                .BuildList());
         }
 
-        public void GivenHistoryForEpisode(Episode episode, params HistoryEventType[] eventTypes)
+        public void GivenHistoryForEpisode(Episode episode, params EpisodeHistoryEventType[] eventTypes)
         {
             foreach (var eventType in eventTypes)
             {
                 _historyItems.Add(
-                    Builder<History.History>.CreateNew()
+                    Builder<EpisodeHistory>.CreateNew()
                                             .With(h => h.EpisodeId = episode.Id)
                                             .With(h => h.EventType = eventType)
                                             .Build()
@@ -67,7 +67,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
         {
             GivenEpisodes(1);
 
-            GivenHistoryForEpisode(_episodes[0], HistoryEventType.Grabbed);
+            GivenHistoryForEpisode(_episodes[0], EpisodeHistoryEventType.Grabbed);
 
             Subject.IsImported(_trackedDownload, _historyItems)
                    .Should()
@@ -79,8 +79,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
         {
             GivenEpisodes(2);
 
-            GivenHistoryForEpisode(_episodes[0], HistoryEventType.Grabbed);
-            GivenHistoryForEpisode(_episodes[1], HistoryEventType.Grabbed);
+            GivenHistoryForEpisode(_episodes[0], EpisodeHistoryEventType.Grabbed);
+            GivenHistoryForEpisode(_episodes[1], EpisodeHistoryEventType.Grabbed);
 
             Subject.IsImported(_trackedDownload, _historyItems)
                    .Should()
@@ -92,8 +92,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
         {
             GivenEpisodes(2);
 
-            GivenHistoryForEpisode(_episodes[0], HistoryEventType.DownloadFolderImported, HistoryEventType.Grabbed);
-            GivenHistoryForEpisode(_episodes[1], HistoryEventType.Grabbed);
+            GivenHistoryForEpisode(_episodes[0], EpisodeHistoryEventType.DownloadFolderImported, EpisodeHistoryEventType.Grabbed);
+            GivenHistoryForEpisode(_episodes[1], EpisodeHistoryEventType.Grabbed);
 
             Subject.IsImported(_trackedDownload, _historyItems)
                    .Should()
@@ -105,7 +105,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
         {
             GivenEpisodes(1);
 
-            GivenHistoryForEpisode(_episodes[0], HistoryEventType.DownloadFolderImported, HistoryEventType.Grabbed);
+            GivenHistoryForEpisode(_episodes[0], EpisodeHistoryEventType.DownloadFolderImported, EpisodeHistoryEventType.Grabbed);
 
             Subject.IsImported(_trackedDownload, _historyItems)
                    .Should()
@@ -117,8 +117,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
         {
             GivenEpisodes(2);
 
-            GivenHistoryForEpisode(_episodes[0], HistoryEventType.DownloadFolderImported, HistoryEventType.Grabbed);
-            GivenHistoryForEpisode(_episodes[1], HistoryEventType.DownloadFolderImported, HistoryEventType.Grabbed);
+            GivenHistoryForEpisode(_episodes[0], EpisodeHistoryEventType.DownloadFolderImported, EpisodeHistoryEventType.Grabbed);
+            GivenHistoryForEpisode(_episodes[1], EpisodeHistoryEventType.DownloadFolderImported, EpisodeHistoryEventType.Grabbed);
 
             Subject.IsImported(_trackedDownload, _historyItems)
                    .Should()
diff --git a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs
index 0488e916e..70f9bc16c 100644
--- a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs
@@ -21,8 +21,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
         {
             Mocker.GetMock<IHistoryService>()
                 .Setup(s => s.FindByDownloadId(It.Is<string>(sr => sr == "35238")))
-                .Returns(new List<History.History>(){
-                 new History.History(){
+                .Returns(new List<EpisodeHistory>(){
+                 new EpisodeHistory(){
                      DownloadId = "35238",
                      SourceTitle = "TV Series S01",
                      SeriesId = 5,
@@ -61,6 +61,12 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
             {
                 Title = "The torrent release folder",
                 DownloadId = "35238",
+                DownloadClientInfo = new DownloadClientItemClientInfo
+                {
+                    Protocol = client.Protocol,
+                    Id = client.Id,
+                    Name = client.Name
+                }
             };
 
             var trackedDownload = Subject.TrackDownload(client, item);
@@ -90,8 +96,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
 
             Mocker.GetMock<IHistoryService>()
                 .Setup(s => s.FindByDownloadId(It.Is<string>(sr => sr == "35238")))
-                .Returns(new List<History.History>(){
-                 new History.History(){
+                .Returns(new List<EpisodeHistory>(){
+                 new EpisodeHistory(){
                      DownloadId = "35238",
                      SourceTitle = "TV Series Special",
                      SeriesId = 5,
@@ -117,6 +123,12 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
             {
                 Title = "The torrent release folder",
                 DownloadId = "35238",
+                DownloadClientInfo = new DownloadClientItemClientInfo
+                {
+                    Protocol = client.Protocol,
+                    Id = client.Id,
+                    Name = client.Name
+                }
             };
 
             var trackedDownload = Subject.TrackDownload(client, item);
diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs
index 649c3d499..89b0aea1b 100644
--- a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs
+++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs
@@ -8,13 +8,13 @@ using NzbDrone.Core.Qualities;
 namespace NzbDrone.Core.Test.HistoryTests
 {
     [TestFixture]
-    public class HistoryRepositoryFixture : DbTest<HistoryRepository, History.History>
+    public class HistoryRepositoryFixture : DbTest<HistoryRepository, EpisodeHistory>
     {
 
         [Test]
         public void should_read_write_dictionary()
         {
-            var history = Builder<History.History>.CreateNew()
+            var history = Builder<EpisodeHistory>.CreateNew()
                 .With(c => c.Quality = new QualityModel())
                 .BuildNew();
 
@@ -30,16 +30,16 @@ namespace NzbDrone.Core.Test.HistoryTests
         [Test]
         public void should_get_download_history()
         {
-            var historyBluray = Builder<History.History>.CreateNew()
+            var historyBluray = Builder<EpisodeHistory>.CreateNew()
                 .With(c => c.Quality = new QualityModel(Quality.Bluray1080p))
                 .With(c => c.SeriesId = 12)
-                .With(c => c.EventType = HistoryEventType.Grabbed)
+                .With(c => c.EventType = EpisodeHistoryEventType.Grabbed)
                 .BuildNew();
 
-            var historyDvd = Builder<History.History>.CreateNew()
+            var historyDvd = Builder<EpisodeHistory>.CreateNew()
                 .With(c => c.Quality = new QualityModel(Quality.DVD))
                 .With(c => c.SeriesId = 12)
-                .With(c => c.EventType = HistoryEventType.Grabbed)
+                .With(c => c.EventType = EpisodeHistoryEventType.Grabbed)
              .BuildNew();
 
             Subject.Insert(historyBluray);
diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs
index 0eebc8cd5..0c74874df 100644
--- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs
@@ -13,6 +13,7 @@ using NzbDrone.Core.History;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Test.Qualities;
 using NzbDrone.Core.Download;
+using NzbDrone.Core.Indexers;
 using NzbDrone.Core.Tv;
 using NzbDrone.Core.Languages;
 using NzbDrone.Core.Profiles.Languages;
@@ -68,14 +69,19 @@ namespace NzbDrone.Core.Test.HistoryTests
 
             var downloadClientItem = new DownloadClientItem
                                      {
-                                         DownloadClient = "sab",
+                                         DownloadClientInfo = new DownloadClientItemClientInfo
+                                         {
+                                             Protocol = DownloadProtocol.Usenet,
+                                             Id = 1,
+                                             Name = "sab"
+                                         },
                                          DownloadId = "abcd"
                                      };
 
             Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, new List<EpisodeFile>(), true, downloadClientItem));
 
             Mocker.GetMock<IHistoryRepository>()
-                .Verify(v => v.Insert(It.Is<History.History>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
+                .Verify(v => v.Insert(It.Is<EpisodeHistory>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
         }
     }
 }
diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs
index 022248abd..3b9b9d8bb 100644
--- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs
+++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs
@@ -1,6 +1,7 @@
 using FizzWare.NBuilder;
 using FluentAssertions;
 using NUnit.Framework;
+using NzbDrone.Core.History;
 using NzbDrone.Core.Housekeeping.Housekeepers;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Test.Framework;
@@ -9,7 +10,7 @@ using NzbDrone.Core.Tv;
 namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
 {
     [TestFixture]
-    public class CleanupOrphanedHistoryItemsFixture : DbTest<CleanupOrphanedHistoryItems, History.History>
+    public class CleanupOrphanedHistoryItemsFixture : DbTest<CleanupOrphanedHistoryItems, EpisodeHistory>
     {
         private Series _series;
         private Episode _episode;
@@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
         {
             GivenEpisode();
 
-            var history = Builder<History.History>.CreateNew()
+            var history = Builder<EpisodeHistory>.CreateNew()
                                                   .With(h => h.Quality = new QualityModel())
                                                   .With(h => h.EpisodeId = _episode.Id)
                                                   .BuildNew();
@@ -54,7 +55,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
         {
             GivenSeries();
 
-            var history = Builder<History.History>.CreateNew()
+            var history = Builder<EpisodeHistory>.CreateNew()
                                                   .With(h => h.Quality = new QualityModel())
                                                   .With(h => h.SeriesId = _series.Id)
                                                   .BuildNew();
@@ -70,7 +71,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
             GivenSeries();
             GivenEpisode();
 
-            var history = Builder<History.History>.CreateListOfSize(2)
+            var history = Builder<EpisodeHistory>.CreateListOfSize(2)
                                                   .All()
                                                   .With(h => h.Quality = new QualityModel())
                                                   .With(h => h.EpisodeId = _episode.Id)
@@ -91,7 +92,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
             GivenSeries();
             GivenEpisode();
 
-            var history = Builder<History.History>.CreateListOfSize(2)
+            var history = Builder<EpisodeHistory>.CreateListOfSize(2)
                                                   .All()
                                                   .With(h => h.Quality = new QualityModel())
                                                   .With(h => h.SeriesId = _series.Id)
diff --git a/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs b/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs
index 81ca1e28d..f1ac1293b 100644
--- a/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs
@@ -7,6 +7,7 @@ using NzbDrone.Core.Queue;
 using NzbDrone.Core.Test.Framework;
 using FizzWare.NBuilder;
 using FluentAssertions;
+using NzbDrone.Core.Download;
 using NzbDrone.Core.Tv;
 using NzbDrone.Core.Parser.Model;
 
@@ -20,8 +21,11 @@ namespace NzbDrone.Core.Test.QueueTests
         [SetUp]
         public void SetUp()
         {
+            var downloadClientInfo = Builder<DownloadClientItemClientInfo>.CreateNew().Build();
+
             var downloadItem = Builder<NzbDrone.Core.Download.DownloadClientItem>.CreateNew()
                                         .With(v => v.RemainingTime = TimeSpan.FromSeconds(10))
+                                        .With(v => v.DownloadClientInfo = downloadClientInfo)
                                         .Build();
 
             var series = Builder<Series>.CreateNew()
diff --git a/src/NzbDrone.Core/Analytics/AnalyticsService.cs b/src/NzbDrone.Core/Analytics/AnalyticsService.cs
index bd1281999..e8735aece 100644
--- a/src/NzbDrone.Core/Analytics/AnalyticsService.cs
+++ b/src/NzbDrone.Core/Analytics/AnalyticsService.cs
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Analytics
         {
             get
             {
-                var lastRecord = _historyService.Paged(new PagingSpec<History.History>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending });
+                var lastRecord = _historyService.Paged(new PagingSpec<EpisodeHistory>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending });
                 var monthAgo = DateTime.UtcNow.AddMonths(-1);
 
                 return lastRecord.Records.Any(v => v.Date > monthAgo); 
diff --git a/src/NzbDrone.Core/Datastore/Migration/139_add_download_history.cs b/src/NzbDrone.Core/Datastore/Migration/139_add_download_history.cs
new file mode 100644
index 000000000..9294f5aa3
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/139_add_download_history.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using FluentMigrator;
+using NzbDrone.Common.Serializer;
+using NzbDrone.Core.Datastore.Migration.Framework;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+    [Migration(139)]
+    public class add_download_history : NzbDroneMigrationBase
+    {
+        protected override void MainDbUpgrade()
+        {
+            Create.TableForModel("DownloadHistory")
+                  .WithColumn("EventType").AsInt32().NotNullable()
+                  .WithColumn("SeriesId").AsInt32().NotNullable()
+                  .WithColumn("DownloadId").AsString().NotNullable()
+                  .WithColumn("SourceTitle").AsString().NotNullable()
+                  .WithColumn("Date").AsDateTime().NotNullable()
+                  .WithColumn("Protocol").AsInt32().Nullable()
+                  .WithColumn("IndexerId").AsInt32().Nullable()
+                  .WithColumn("DownloadClientId").AsInt32().Nullable()
+                  .WithColumn("Release").AsString().Nullable()
+                  .WithColumn("Data").AsString().Nullable();
+
+            Create.Index().OnTable("DownloadHistory").OnColumn("EventType");
+            Create.Index().OnTable("DownloadHistory").OnColumn("SeriesId");
+            Create.Index().OnTable("DownloadHistory").OnColumn("DownloadId");
+
+            Execute.WithConnection(InitialImportedDownloadHistory);
+        }
+
+        private static readonly Dictionary<int, int> EventTypeMap = new Dictionary<int, int>()
+        {
+            // EpisodeHistoryType.Grabbed -> DownloadHistoryType.Grabbed
+            {1, 1},
+            // EpisodeHistoryType.DownloadFolderImported -> DownloadHistoryType.DownloadImported
+            {3, 2},
+            // EpisodeHistoryType.DownloadFailed -> DownloadHistoryType.DownloadFailed
+            {4, 3},
+            // EpisodeHistoryType.DownloadIgnored -> DownloadHistoryType.DownloadIgnored
+            {7, 4}
+        };
+
+        private void InitialImportedDownloadHistory(IDbConnection conn, IDbTransaction tran)
+        {
+            using (var cmd = conn.CreateCommand())
+            {
+                cmd.Transaction = tran;
+                cmd.CommandText = "SELECT SeriesId, DownloadId, EventType, SourceTitle, Date, Data FROM History WHERE DownloadId IS NOT NULL AND EventType IN (1, 3, 4, 7) GROUP BY EventType, DownloadId";
+
+                using (var reader = cmd.ExecuteReader())
+                {
+                    while (reader.Read())
+                    {
+                        var seriesId = reader.GetInt32(0);
+                        var downloadId = reader.GetString(1);
+                        var eventType = reader.GetInt32(2);
+                        var sourceTitle = reader.GetString(3);
+                        var date = reader.GetDateTime(4);
+                        var rawData = reader.GetString(5);
+                        var data = Json.Deserialize<Dictionary<string, string>>(rawData);
+
+                        var downloadHistoryEventType = EventTypeMap[eventType];
+                        var protocol = data.ContainsKey("protocol") ? Convert.ToInt32(data["protocol"]) : (int?)null;
+                        var downloadHistoryData = new Dictionary<string, string>();
+
+                        if (data.ContainsKey("indexer"))
+                        {
+                            downloadHistoryData.Add("indexer", data["indexer"]);
+                        }
+
+                        if (data.ContainsKey("downloadClient"))
+                        {
+                            downloadHistoryData.Add("downloadClient", data["downloadClient"]);
+                        }
+
+                        using (var updateCmd = conn.CreateCommand())
+                        {
+                            updateCmd.Transaction = tran;
+                            updateCmd.CommandText = @"INSERT INTO DownloadHistory (EventType, SeriesId, DownloadId, SourceTitle, Date, Protocol, Data) VALUES (?, ?, ?, ?, ?, ?, ?)";
+                            updateCmd.AddParameter(downloadHistoryEventType);
+                            updateCmd.AddParameter(seriesId);
+                            updateCmd.AddParameter(downloadId);
+                            updateCmd.AddParameter(sourceTitle);
+                            updateCmd.AddParameter(date);
+                            updateCmd.AddParameter(protocol);
+                            updateCmd.AddParameter(downloadHistoryData.ToJson());
+
+                            updateCmd.ExecuteNonQuery();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs
index 58ef55be6..df9c97602 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -29,10 +29,12 @@ using NzbDrone.Core.Tv;
 using NzbDrone.Common.Disk;
 using NzbDrone.Core.Authentication;
 using NzbDrone.Core.CustomFilters;
+using NzbDrone.Core.Download.History;
 using NzbDrone.Core.Extras.Metadata;
 using NzbDrone.Core.Extras.Metadata.Files;
 using NzbDrone.Core.Extras.Others;
 using NzbDrone.Core.Extras.Subtitles;
+using NzbDrone.Core.History;
 using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.Languages;
 using NzbDrone.Core.Profiles.Languages;
@@ -80,7 +82,7 @@ namespace NzbDrone.Core.Datastore
 
             Mapper.Entity<SceneMapping>().RegisterModel("SceneMappings");
 
-            Mapper.Entity<History.History>().RegisterModel("History")
+            Mapper.Entity<EpisodeHistory>().RegisterModel("History")
                   .AutoMapChildModels();
 
             Mapper.Entity<Series>().RegisterModel("Series")
@@ -134,6 +136,9 @@ namespace NzbDrone.Core.Datastore
             Mapper.Entity<DownloadClientStatus>().RegisterModel("DownloadClientStatus");
 
             Mapper.Entity<CustomFilter>().RegisterModel("CustomFilters");
+
+            Mapper.Entity<DownloadHistory>().RegisterModel("DownloadHistory")
+                  .AutoMapChildModels();
         }
 
         private static void RegisterMappers()
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AlreadyImportedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AlreadyImportedSpecification.cs
index 53cbd9abe..2dcdeefee 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/AlreadyImportedSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AlreadyImportedSpecification.cs
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
                 }
 
                 var historyForEpisode = _historyService.FindByEpisodeId(episode.Id);
-                var lastGrabbed = historyForEpisode.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
+                var lastGrabbed = historyForEpisode.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.Grabbed);
 
                 if (lastGrabbed == null)
                 {
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
                 }
 
                 var imported = historyForEpisode.FirstOrDefault(h =>
-                    h.EventType == HistoryEventType.DownloadFolderImported &&
+                    h.EventType == EpisodeHistoryEventType.DownloadFolderImported &&
                     h.DownloadId == lastGrabbed.DownloadId);
 
                 if (imported == null)
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs
index 51ee3cb5b..a8e186ff9 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
                 _logger.Debug("Checking current status of episode [{0}] in history", episode.Id);
                 var mostRecent = _historyService.MostRecentForEpisode(episode.Id);
 
-                if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
+                if (mostRecent != null && mostRecent.EventType == EpisodeHistoryEventType.Grabbed)
                 {
                     var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12));
 
diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs
index 2bb9346a3..1d6395751 100644
--- a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs
+++ b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs
@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
             {
                 yield return new DownloadClientItem
                 {
-                    DownloadClient = Definition.Name,
+                    DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
                     DownloadId = Definition.Name + "_" + item.DownloadId,
                     Category = "sonarr",
                     Title = item.Title,
diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs
index 561b4901b..0d89b48a6 100644
--- a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs
+++ b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
             {
                 yield return new DownloadClientItem
                 {
-                    DownloadClient = Definition.Name,
+                    DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
                     DownloadId = Definition.Name + "_" + item.DownloadId,
                     Category = "sonarr",
                     Title = item.Title,
diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs
index 6eeeb7ccb..5907680a2 100644
--- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs
+++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs
@@ -129,7 +129,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
                 item.Title = torrent.Name;
                 item.Category = Settings.TvCategory;
 
-                item.DownloadClient = Definition.Name;
+                item.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
 
                 var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.DownloadPath));
                 item.OutputPath = outputPath + torrent.Name;
diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs
index 15508af90..5840288bc 100644
--- a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs
+++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs
@@ -85,7 +85,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
                 var item = new DownloadClientItem()
                 {
                     Category = Settings.TvCategory,
-                    DownloadClient = Definition.Name,
+                    DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
                     DownloadId = CreateDownloadId(torrent.Id, serialNumber),
                     Title = torrent.Title,
                     TotalSize = torrent.Size,
diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs
index fda9c339d..14ebc67a4 100644
--- a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs
+++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
                 var item = new DownloadClientItem()
                 {
                     Category = Settings.TvCategory,
-                    DownloadClient = Definition.Name,
+                    DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
                     DownloadId = CreateDownloadId(nzb.Id, serialNumber),
                     Title = nzb.Title,
                     TotalSize = nzb.Size,
diff --git a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs
index 700320779..1fb789fc9 100644
--- a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs
+++ b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
 
                 var item = new DownloadClientItem
                 {
-                    DownloadClient = Definition.Name,
+                    DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
                     DownloadId = torrent.InfoHash.ToUpper(),
                     OutputPath = outputPath + torrent.Name,
                     RemainingSize = torrent.TotalSize - torrent.DownloadedBytes,
diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs
index 0d38a29a0..7faeae119 100644
--- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs
+++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
             {
                 var queueItem = new DownloadClientItem();
 
-                queueItem.DownloadClient = Definition.Name;
+                queueItem.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
                 queueItem.DownloadId = vortexQueueItem.AddUUID ?? vortexQueueItem.Id.ToString();
                 queueItem.Category = vortexQueueItem.GroupName;
                 queueItem.Title = vortexQueueItem.UiTitle;
diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs
index 92a8a5dad..eec32ca19 100644
--- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs
+++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
                 queueItem.Title = item.NzbName;
                 queueItem.TotalSize = totalSize;
                 queueItem.Category = item.Category;
-                queueItem.DownloadClient = Definition.Name;
+                queueItem.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
                 queueItem.CanMoveFiles = true;
                 queueItem.CanBeRemoved = true;
 
@@ -118,7 +118,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
                 var historyItem = new DownloadClientItem();
                 var itemDir = item.FinalDir.IsNullOrWhiteSpace() ? item.DestDir : item.FinalDir;
 
-                historyItem.DownloadClient = Definition.Name;
+                historyItem.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
                 historyItem.DownloadId = droneParameter == null ? item.Id.ToString() : droneParameter.Value.ToString();
                 historyItem.Title = item.Name;
                 historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs
index 00695b11f..38c6a54aa 100644
--- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs
+++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
 
                 var historyItem = new DownloadClientItem
                 {
-                    DownloadClient = Definition.Name,
+                    DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
                     DownloadId = GetDownloadClientId(file),
                     Title = title,
 
diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs
index 232ee897f..18ad75777 100644
--- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs
+++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs
@@ -135,7 +135,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
                     Category = torrent.Category.IsNotNullOrWhiteSpace() ? torrent.Category : torrent.Label,
                     Title = torrent.Name,
                     TotalSize = torrent.Size,
-                    DownloadClient = Definition.Name,
+                    DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
                     RemainingSize = (long)(torrent.Size * (1.0 - torrent.Progress)),
                     RemainingTime = GetRemainingTime(torrent),
                     SeedRatio = torrent.Ratio,
diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs
index c7c7932fc..d5a7d2cbb 100644
--- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs
+++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
                 }
 
                 var queueItem = new DownloadClientItem();
-                queueItem.DownloadClient = Definition.Name;
+                queueItem.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
                 queueItem.DownloadId = sabQueueItem.Id;
                 queueItem.Category = sabQueueItem.Category;
                 queueItem.Title = sabQueueItem.Title;
@@ -117,7 +117,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
 
                 var historyItem = new DownloadClientItem
                 {
-                    DownloadClient = Definition.Name,
+                    DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
                     DownloadId = sabHistoryItem.Id,
                     Category = sabHistoryItem.Category,
                     Title = sabHistoryItem.Title,
diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs
index c1fd61180..3227e9404 100644
--- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs
+++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
                 item.Category = Settings.TvCategory;
                 item.Title = torrent.Name;
 
-                item.DownloadClient = Definition.Name;
+                item.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
 
                 item.OutputPath = GetOutputPath(outputPath, torrent);
                 item.TotalSize = torrent.TotalSize;
diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs
index f14d89f2d..d77f9d705 100644
--- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs
+++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
                 }
 
                 var item = new DownloadClientItem();
-                item.DownloadClient = Definition.Name;
+                item.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
                 item.Title = torrent.Name;
                 item.DownloadId = torrent.Hash;
                 item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.Path));
diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs
index 5540d8f63..13f6cc6fd 100644
--- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs
+++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs
@@ -118,7 +118,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
                 item.Title = torrent.Name;
                 item.TotalSize = torrent.Size;
                 item.Category = torrent.Label;
-                item.DownloadClient = Definition.Name;
+                item.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this);
                 item.RemainingSize = torrent.Remaining;
                 item.SeedRatio = torrent.Ratio;
 
diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
index 07171cefb..9bd762388 100644
--- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
@@ -122,6 +122,11 @@ namespace NzbDrone.Core.Download
             // file was imported. This will allow the decision engine to reject already imported
             // episode files and still mark the download complete when all files are imported.
 
+            // EDGE CASE: This process relies on EpisodeIds being consistent between executions, if a series is updated 
+            // and an episode is removed, but later comes back with a different ID then Sonarr will treat it as incomplete.
+            // Since imports should be relatively fast and these types of data changes are infrequent this should be quite
+            // safe, but commenting for future benefit.
+
             if (importResults.Any(c => c.Result == ImportResultType.Imported))
             {
                 var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
diff --git a/src/NzbDrone.Core/Download/DownloadClientItem.cs b/src/NzbDrone.Core/Download/DownloadClientItem.cs
index 3348be4a9..efc36bbe0 100644
--- a/src/NzbDrone.Core/Download/DownloadClientItem.cs
+++ b/src/NzbDrone.Core/Download/DownloadClientItem.cs
@@ -1,13 +1,15 @@
 using System;
 using System.Diagnostics;
 using NzbDrone.Common.Disk;
+using NzbDrone.Core.Indexers;
+using NzbDrone.Core.ThingiProvider;
 
 namespace NzbDrone.Core.Download
 {
-    [DebuggerDisplay("{DownloadClient}:{Title}")]
+    [DebuggerDisplay("{DownloadClientName}:{Title}")]
     public class DownloadClientItem
     {
-        public string DownloadClient { get; set; }
+        public DownloadClientItemClientInfo DownloadClientInfo { get; set; }
         public string DownloadId { get; set; }
         public string Category { get; set; }
         public string Title { get; set; }
@@ -16,16 +18,32 @@ namespace NzbDrone.Core.Download
         public long RemainingSize { get; set; }
         public TimeSpan? RemainingTime { get; set; }
         public double? SeedRatio { get; set; }
-
         public OsPath OutputPath { get; set; }
         public string Message { get; set; }
-
         public DownloadItemStatus Status { get; set; }
         public bool IsEncrypted { get; set; }
-
         public bool CanMoveFiles { get; set; }
         public bool CanBeRemoved { get; set; }
-
         public bool Removed { get; set; }
     }
+
+    public class DownloadClientItemClientInfo
+    {
+        public DownloadProtocol Protocol { get; set; }
+        public string Type { get; set; }
+        public int Id { get; set; }
+        public string Name { get; set; }
+
+        public static DownloadClientItemClientInfo FromDownloadClient<TSettings>(
+            DownloadClientBase<TSettings> downloadClient) where TSettings : IProviderConfig, new()
+        {
+            return new DownloadClientItemClientInfo
+            {
+                Protocol = downloadClient.Protocol,
+                Type = downloadClient.Name,
+                Id = downloadClient.Definition.Id,
+                Name = downloadClient.Definition.Name
+            };
+        }
+    }
 }
diff --git a/src/NzbDrone.Core/Download/DownloadEventHub.cs b/src/NzbDrone.Core/Download/DownloadEventHub.cs
index 8d0f50bc1..0e0f64962 100644
--- a/src/NzbDrone.Core/Download/DownloadEventHub.cs
+++ b/src/NzbDrone.Core/Download/DownloadEventHub.cs
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Download
             var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
             try
             {
-                _logger.Debug("[{0}] Removing download from {1} history", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClient);
+                _logger.Debug("[{0}] Removing download from {1} history", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClientInfo.Name);
                 downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
                 trackedDownload.DownloadItem.Removed = true;
             }
@@ -85,7 +85,7 @@ namespace NzbDrone.Core.Download
             var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
             try
             {
-                _logger.Debug("[{0}] Marking download as imported from {1}", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClient);
+                _logger.Debug("[{0}] Marking download as imported from {1}", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClientInfo.Name);
                 downloadClient.MarkItemAsImported(trackedDownload.DownloadItem);
             }
             catch (NotSupportedException e)
diff --git a/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs b/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs
index 7637caaf3..e2e85348d 100644
--- a/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs
+++ b/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs
@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Download
         public Language Language { get; set; }
         public QualityModel Quality { get; set; }
         public string SourceTitle { get; set; }
-        public string DownloadClient { get; set; }
+        public DownloadClientItemClientInfo DownloadClientInfo { get; set; }
         public string DownloadId { get; set; }
         public string Message { get; set; }
     }
diff --git a/src/NzbDrone.Core/Download/DownloadService.cs b/src/NzbDrone.Core/Download/DownloadService.cs
index 0997b42c5..2f03337e4 100644
--- a/src/NzbDrone.Core/Download/DownloadService.cs
+++ b/src/NzbDrone.Core/Download/DownloadService.cs
@@ -5,7 +5,6 @@ using NzbDrone.Common.Extensions;
 using NzbDrone.Common.Http;
 using NzbDrone.Common.Instrumentation.Extensions;
 using NzbDrone.Common.TPL;
-using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Download.Clients;
 using NzbDrone.Core.Exceptions;
 using NzbDrone.Core.Indexers;
@@ -98,6 +97,8 @@ namespace NzbDrone.Core.Download
 
             var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode);
             episodeGrabbedEvent.DownloadClient = downloadClient.Name;
+            episodeGrabbedEvent.DownloadClientId = downloadClient.Definition.Id;
+            episodeGrabbedEvent.DownloadClientName = downloadClient.Definition.Name;
 
             if (!string.IsNullOrWhiteSpace(downloadClientId))
             {
diff --git a/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs b/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs
index b7861b8d7..27d030844 100644
--- a/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs
+++ b/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs
@@ -6,7 +6,9 @@ namespace NzbDrone.Core.Download
     public class EpisodeGrabbedEvent : IEvent
     {
         public RemoteEpisode Episode { get; private set; }
+        public int DownloadClientId { get; set; }
         public string DownloadClient { get; set; }
+        public string DownloadClientName { get; set; }
         public string DownloadId { get; set; }
 
         public EpisodeGrabbedEvent(RemoteEpisode episode)
diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs
index c04abeae6..e15a6b758 100644
--- a/src/NzbDrone.Core/Download/FailedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs
@@ -18,12 +18,15 @@ namespace NzbDrone.Core.Download
     public class FailedDownloadService : IFailedDownloadService
     {
         private readonly IHistoryService _historyService;
+        private readonly ITrackedDownloadService _trackedDownloadService;
         private readonly IEventAggregator _eventAggregator;
 
         public FailedDownloadService(IHistoryService historyService,
+                                     ITrackedDownloadService trackedDownloadService,
                                      IEventAggregator eventAggregator)
         {
             _historyService = historyService;
+            _trackedDownloadService = trackedDownloadService;
             _eventAggregator = eventAggregator;
         }
 
@@ -34,22 +37,24 @@ namespace NzbDrone.Core.Download
             var downloadId = history.DownloadId;
             if (downloadId.IsNullOrWhiteSpace())
             {
-                PublishDownloadFailedEvent(new List<History.History> { history }, "Manually marked as failed");
+                PublishDownloadFailedEvent(new List<EpisodeHistory> { history }, "Manually marked as failed");
             }
             else
             {
-                var grabbedHistory = _historyService.Find(downloadId, HistoryEventType.Grabbed).ToList();
+                var grabbedHistory = _historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed).ToList();
                 PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed");
             }
         }
 
         public void MarkAsFailed(string downloadId)
         {
-            var history = _historyService.Find(downloadId, HistoryEventType.Grabbed);
+            var history = _historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed);
 
             if (history.Any())
             {
-                PublishDownloadFailedEvent(history, "Manually marked as failed");
+                var trackedDownload = _trackedDownloadService.Find(downloadId);
+
+                PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload);
             }
         }
 
@@ -65,7 +70,7 @@ namespace NzbDrone.Core.Download
                 trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
             {
                 var grabbedItems = _historyService
-                                   .Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
+                                   .Find(trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed)
                                    .ToList();
 
                 if (grabbedItems.Empty())
@@ -86,7 +91,7 @@ namespace NzbDrone.Core.Download
             }
 
             var grabbedItems = _historyService
-                               .Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
+                               .Find(trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed)
                                .ToList();
 
             if (grabbedItems.Empty())
@@ -109,7 +114,7 @@ namespace NzbDrone.Core.Download
             PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
         }
 
-        private void PublishDownloadFailedEvent(List<History.History> historyItems, string message, TrackedDownload trackedDownload = null)
+        private void PublishDownloadFailedEvent(List<EpisodeHistory> historyItems, string message, TrackedDownload trackedDownload = null)
         {
             var historyItem = historyItems.First();
 
@@ -119,7 +124,7 @@ namespace NzbDrone.Core.Download
                 EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
                 Quality = historyItem.Quality,
                 SourceTitle = historyItem.SourceTitle,
-                DownloadClient = historyItem.Data.GetValueOrDefault(History.History.DOWNLOAD_CLIENT),
+                DownloadClient = historyItem.Data.GetValueOrDefault(EpisodeHistory.DOWNLOAD_CLIENT),
                 DownloadId = historyItem.DownloadId,
                 Message = message,
                 Data = historyItem.Data,
diff --git a/src/NzbDrone.Core/Download/History/DownloadHistory.cs b/src/NzbDrone.Core/Download/History/DownloadHistory.cs
new file mode 100644
index 000000000..8eb38576b
--- /dev/null
+++ b/src/NzbDrone.Core/Download/History/DownloadHistory.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Indexers;
+using NzbDrone.Core.Parser.Model;
+
+namespace NzbDrone.Core.Download.History
+{
+    public class DownloadHistory : ModelBase
+    {
+        public DownloadHistoryEventType EventType { get; set; }
+        public int SeriesId { get; set; }
+        public string DownloadId { get; set; }
+        public string SourceTitle { get; set; }
+        public DateTime Date { get; set; }
+        public DownloadProtocol Protocol { get; set; }
+        public int IndexerId { get; set; }
+        public int DownloadClientId { get; set; }
+        public ReleaseInfo Release { get; set; }
+        public Dictionary<string, string> Data { get; set; }
+        public DownloadHistory()
+        {
+            Data = new Dictionary<string, string>();
+        }
+    }
+
+    public enum DownloadHistoryEventType
+    {
+        DownloadGrabbed = 1,
+        DownloadImported = 2,
+        DownloadFailed = 3,
+        DownloadIgnored = 4,
+        FileImported = 5
+    }
+}
diff --git a/src/NzbDrone.Core/Download/History/DownloadHistoryRepository.cs b/src/NzbDrone.Core/Download/History/DownloadHistoryRepository.cs
new file mode 100644
index 000000000..136a37cc4
--- /dev/null
+++ b/src/NzbDrone.Core/Download/History/DownloadHistoryRepository.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging.Events;
+
+namespace NzbDrone.Core.Download.History
+{
+    public interface IDownloadHistoryRepository : IBasicRepository<DownloadHistory>
+    {
+        List<DownloadHistory> FindByDownloadId(string downloadId);
+        void DeleteBySeriesId(int seriesId);
+    }
+
+    public class DownloadHistoryRepository : BasicRepository<DownloadHistory>, IDownloadHistoryRepository
+    {
+        public DownloadHistoryRepository(IMainDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
+        {
+        }
+
+        public List<DownloadHistory> FindByDownloadId(string downloadId)
+        {
+            return Query.Where(h => h.DownloadId == downloadId)
+                        .OrderByDescending(h => h.Date);
+        }
+
+        public void DeleteBySeriesId(int seriesId)
+        {
+            Delete(r => r.SeriesId == seriesId);
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Download/History/DownloadHistoryService.cs b/src/NzbDrone.Core/Download/History/DownloadHistoryService.cs
new file mode 100644
index 000000000..6d7b6218f
--- /dev/null
+++ b/src/NzbDrone.Core/Download/History/DownloadHistoryService.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Core.History;
+using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Tv.Events;
+
+namespace NzbDrone.Core.Download.History
+{
+    public interface IDownloadHistoryService
+    {
+        bool DownloadAlreadyImported(string downloadId);
+        DownloadHistory GetLatestDownloadHistoryItem(string downloadId);
+    }
+
+    public class DownloadHistoryService : IDownloadHistoryService,
+                                          IHandle<EpisodeGrabbedEvent>,
+                                          IHandle<EpisodeImportedEvent>,
+                                          IHandle<DownloadCompletedEvent>,
+                                          IHandle<DownloadFailedEvent>,
+                                          IHandle<DownloadIgnoredEvent>,
+                                          IHandle<SeriesDeletedEvent>
+
+    {
+        private readonly IDownloadHistoryRepository _repository;
+        private readonly IHistoryService _historyService;
+
+        public DownloadHistoryService(IDownloadHistoryRepository repository, IHistoryService historyService)
+        {
+            _repository = repository;
+            _historyService = historyService;
+        }
+
+        public bool DownloadAlreadyImported(string downloadId)
+        {
+            var events = _repository.FindByDownloadId(downloadId);
+
+            // Events are ordered by date descending, if a grabbed event comes before an imported event then it was never imported
+            // or grabbed again after importing and should be reprocessed.
+            foreach (var e in events)
+            {
+                if (e.EventType == DownloadHistoryEventType.DownloadGrabbed)
+                {
+                    return false;
+                }
+
+                if (e.EventType == DownloadHistoryEventType.DownloadImported)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public DownloadHistory GetLatestDownloadHistoryItem(string downloadId)
+        {
+            var events = _repository.FindByDownloadId(downloadId);
+
+            // Events are ordered by date descending. We'll return the most recent expected event.
+            foreach (var e in events)
+            {
+                if (e.EventType == DownloadHistoryEventType.DownloadGrabbed)
+                {
+                    return e;
+                }
+
+                if (e.EventType == DownloadHistoryEventType.DownloadImported)
+                {
+                    return e;
+                }
+
+                if (e.EventType == DownloadHistoryEventType.DownloadFailed)
+                {
+                    return e;
+                }
+            }
+
+            return null;
+        }
+
+        public void Handle(EpisodeGrabbedEvent message)
+        {
+            var history = new DownloadHistory
+            {
+                EventType = DownloadHistoryEventType.DownloadGrabbed,
+                SeriesId = message.Episode.Series.Id,
+                DownloadId = message.DownloadId,
+                SourceTitle = message.Episode.Release.Title,
+                Date = DateTime.UtcNow,
+                Protocol = message.Episode.Release.DownloadProtocol,
+                IndexerId = message.Episode.Release.IndexerId,
+                DownloadClientId = message.DownloadClientId,
+                Release =  message.Episode.Release
+            };
+
+            history.Data.Add("Indexer", message.Episode.Release.Indexer);
+            history.Data.Add("DownloadClient", message.DownloadClient);
+            history.Data.Add("DownloadClientName", message.DownloadClientName);
+            history.Data.Add("PreferredWordScore", message.Episode.PreferredWordScore.ToString());
+
+            _repository.Insert(history);
+        }
+
+        public void Handle(EpisodeImportedEvent message)
+        {
+            if (!message.NewDownload)
+            {
+                return;
+            }
+
+            var downloadId = message.DownloadId;
+
+            // Try to find the downloadId if the user used manual import (from wanted: missing) or the
+            // API to import and downloadId wasn't provided.
+
+            if (downloadId.IsNullOrWhiteSpace())
+            {
+                downloadId = _historyService.FindDownloadId(message);
+            }
+
+            if (downloadId.IsNullOrWhiteSpace())
+            {
+                return;
+            }
+
+            var history = new DownloadHistory
+            {
+                EventType = DownloadHistoryEventType.FileImported,
+                SeriesId = message.EpisodeInfo.Series.Id,
+                DownloadId = downloadId,
+                SourceTitle = message.EpisodeInfo.Path,
+                Date = DateTime.UtcNow,
+                Protocol = message.DownloadClientInfo.Protocol,
+                DownloadClientId = message.DownloadClientInfo.Id
+            };
+
+            history.Data.Add("DownloadClient", message.DownloadClientInfo.Type);
+            history.Data.Add("DownloadClientName", message.DownloadClientInfo.Name);
+            history.Data.Add("SourcePath", message.EpisodeInfo.Path);
+            history.Data.Add("DestinationPath", Path.Combine(message.EpisodeInfo.Series.Path, message.ImportedEpisode.RelativePath));
+
+            _repository.Insert(history);
+        }
+
+        public void Handle(DownloadCompletedEvent message)
+        {
+            var history = new DownloadHistory
+            {
+                EventType = DownloadHistoryEventType.DownloadImported,
+                SeriesId = message.TrackedDownload.RemoteEpisode.Series.Id,
+                DownloadId = message.TrackedDownload.DownloadItem.DownloadId,
+                SourceTitle = message.TrackedDownload.DownloadItem.OutputPath.ToString(),
+                Date = DateTime.UtcNow,
+                Protocol = message.TrackedDownload.Protocol,
+                DownloadClientId = message.TrackedDownload.DownloadClient
+            };
+
+            history.Data.Add("DownloadClient", message.TrackedDownload.DownloadItem.DownloadClientInfo.Type);
+            history.Data.Add("DownloadClientName", message.TrackedDownload.DownloadItem.DownloadClientInfo.Name);
+
+            _repository.Insert(history);
+        }
+
+        public void Handle(DownloadFailedEvent message)
+        {
+            // Don't track failed download for an unknown download
+            if (message.TrackedDownload == null)
+            {
+                return;
+            }
+
+            var history = new DownloadHistory
+            {
+                EventType = DownloadHistoryEventType.DownloadFailed,
+                SeriesId = message.SeriesId,
+                DownloadId = message.DownloadId,
+                SourceTitle = message.SourceTitle,
+                Date = DateTime.UtcNow,
+                Protocol = message.TrackedDownload.Protocol,
+                DownloadClientId = message.TrackedDownload.DownloadClient
+            };
+
+            history.Data.Add("DownloadClient", message.TrackedDownload.DownloadItem.DownloadClientInfo.Type);
+            history.Data.Add("DownloadClientName", message.TrackedDownload.DownloadItem.DownloadClientInfo.Name);
+
+            _repository.Insert(history);
+        }
+
+        public void Handle(DownloadIgnoredEvent message)
+        {
+            var history = new DownloadHistory
+            {
+                EventType = DownloadHistoryEventType.DownloadIgnored,
+                SeriesId = message.SeriesId,
+                DownloadId = message.DownloadId,
+                SourceTitle = message.SourceTitle,
+                Date = DateTime.UtcNow,
+                Protocol = message.DownloadClientInfo.Protocol,
+                DownloadClientId = message.DownloadClientInfo.Id
+            };
+
+            history.Data.Add("DownloadClient", message.DownloadClientInfo.Type);
+            history.Data.Add("DownloadClientName", message.DownloadClientInfo.Name);
+
+            _repository.Insert(history);
+        }
+
+        public void Handle(SeriesDeletedEvent message)
+        {
+            _repository.DeleteBySeriesId(message.Series.Id);
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Download/IDownloadClient.cs b/src/NzbDrone.Core/Download/IDownloadClient.cs
index e5b75b3c2..f396b7c34 100644
--- a/src/NzbDrone.Core/Download/IDownloadClient.cs
+++ b/src/NzbDrone.Core/Download/IDownloadClient.cs
@@ -8,7 +8,6 @@ namespace NzbDrone.Core.Download
     public interface IDownloadClient : IProvider
     {
         DownloadProtocol Protocol { get; }
-
         string Download(RemoteEpisode remoteEpisode);
         IEnumerable<DownloadClientItem> GetItems();
         void RemoveItem(string downloadId, bool deleteData);
diff --git a/src/NzbDrone.Core/Download/IgnoredDownloadService.cs b/src/NzbDrone.Core/Download/IgnoredDownloadService.cs
index 76bc1c69c..b06b1637b 100644
--- a/src/NzbDrone.Core/Download/IgnoredDownloadService.cs
+++ b/src/NzbDrone.Core/Download/IgnoredDownloadService.cs
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Download
                                           Language = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Language,
                                           Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
                                           SourceTitle = trackedDownload.DownloadItem.Title,
-                                          DownloadClient = trackedDownload.DownloadItem.DownloadClient,
+                                          DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo,
                                           DownloadId = trackedDownload.DownloadItem.DownloadId,
                                           Message = "Manually ignored"
                                       };
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs
index 6375d064e..c5bc47107 100644
--- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs
+++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs
@@ -7,12 +7,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads
 {
     public interface ITrackedDownloadAlreadyImported
     {
-        bool IsImported(TrackedDownload trackedDownload, List<History.History> historyItems);
+        bool IsImported(TrackedDownload trackedDownload, List<EpisodeHistory> historyItems);
     }
 
     public class TrackedDownloadAlreadyImported : ITrackedDownloadAlreadyImported
     {
-        public bool IsImported(TrackedDownload trackedDownload, List<History.History> historyItems)
+        public bool IsImported(TrackedDownload trackedDownload, List<EpisodeHistory> historyItems)
         {
             if (historyItems.Empty())
             {
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
                     return false;
                 }
 
-                return lastHistoryItem.EventType == HistoryEventType.DownloadFolderImported;
+                return lastHistoryItem.EventType == EpisodeHistoryEventType.DownloadFolderImported;
             });
 
             return allEpisodesImportedInHistory;
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
index 92d6a60fa..5065c5f68 100644
--- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
@@ -4,6 +4,7 @@ using System.Linq;
 using NLog;
 using NzbDrone.Common.Cache;
 using NzbDrone.Common.Extensions;
+using NzbDrone.Core.Download.History;
 using NzbDrone.Core.History;
 using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Parser;
@@ -25,7 +26,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
         private readonly IParsingService _parsingService;
         private readonly IHistoryService _historyService;
         private readonly IEventAggregator _eventAggregator;
-        private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
+        private readonly IDownloadHistoryService _downloadHistoryService;
         private readonly Logger _logger;
         private readonly ICached<TrackedDownload> _cache;
 
@@ -33,13 +34,13 @@ namespace NzbDrone.Core.Download.TrackedDownloads
                                       ICacheManager cacheManager,
                                       IHistoryService historyService,
                                       IEventAggregator eventAggregator,
-                                      ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
+                                      IDownloadHistoryService downloadHistoryService,
                                       Logger logger)
         {
             _parsingService = parsingService;
             _historyService = historyService;
             _eventAggregator = eventAggregator;
-            _trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
+            _downloadHistoryService = downloadHistoryService;
             _cache = cacheManager.GetCache<TrackedDownload>(GetType());
             _logger = logger;
         }
@@ -105,34 +106,19 @@ namespace NzbDrone.Core.Download.TrackedDownloads
                     trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0);
                 }
 
+                var downloadHistory = _downloadHistoryService.GetLatestDownloadHistoryItem(downloadItem.DownloadId);
+
+                if (downloadHistory != null)
+                {
+                    var state = GetStateFromHistory(downloadHistory.EventType);
+                    trackedDownload.State = state;
+                }
+
                 if (historyItems.Any())
                 {
                     var firstHistoryItem = historyItems.First();
-                    var state = GetStateFromHistory(firstHistoryItem.EventType);
-
-                    trackedDownload.State = state;
+                    var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == EpisodeHistoryEventType.Grabbed);
 
-                    // TODO: Restore check to confirm all files were imported
-                    // This will treat partially imported downloads as imported (as it was before), which means a partially imported download after a
-                    // restart will get marked as imported without importing the restart of the files.
-
-                    // One potential issue here is if the latest is imported, but other episodes are ignored or never imported.
-                    // It's unlikely that will happen, but could happen if additional episodes are added to season after it's already imported.
-
-//                    if (state == TrackedDownloadState.Imported)
-//                    {
-//                        trackedDownload.State = TrackedDownloadState.Imported;
-//
-//                        var allImported = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);
-//
-//                        trackedDownload.State = allImported ? TrackedDownloadState.Imported : TrackedDownloadState.Downloading;
-//                    }
-//                    else
-//                    {
-//                        trackedDownload.State = state;
-//                    }
-
-                    var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == HistoryEventType.Grabbed);
                     trackedDownload.Indexer = grabbedEvent?.Data["indexer"];
 
                     if (parsedEpisodeInfo == null ||
@@ -146,7 +132,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
 
                         if (parsedEpisodeInfo != null)
                         {
-                            trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, firstHistoryItem.SeriesId, historyItems.Where(v => v.EventType == HistoryEventType.Grabbed).Select(h => h.EpisodeId).Distinct());
+                            trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, firstHistoryItem.SeriesId, historyItems.Where(v => v.EventType == EpisodeHistoryEventType.Grabbed).Select(h => h.EpisodeId).Distinct());
                         }
                     }
                 }
@@ -192,7 +178,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
                 existingItem.CanMoveFiles != downloadItem.CanMoveFiles)
             {
                 _logger.Debug("Tracking '{0}:{1}': ClientState={2}{3} SonarrStage={4} Episode='{5}' OutputPath={6}.",
-                    downloadItem.DownloadClient, downloadItem.Title,
+                    downloadItem.DownloadClientInfo.Name, downloadItem.Title,
                     downloadItem.Status, downloadItem.CanBeRemoved ? "" :
                                          downloadItem.CanMoveFiles ? " (busy)" : " (readonly)",
                     trackedDownload.State,
@@ -201,15 +187,15 @@ namespace NzbDrone.Core.Download.TrackedDownloads
             }
         }
 
-        private static TrackedDownloadState GetStateFromHistory(HistoryEventType eventType)
+        private static TrackedDownloadState GetStateFromHistory(DownloadHistoryEventType eventType)
         {
             switch (eventType)
             {
-                case HistoryEventType.DownloadFolderImported:
+                case DownloadHistoryEventType.DownloadImported:
                     return TrackedDownloadState.Imported;
-                case HistoryEventType.DownloadFailed:
+                case DownloadHistoryEventType.DownloadFailed:
                     return TrackedDownloadState.Failed;
-                case HistoryEventType.DownloadIgnored:
+                case DownloadHistoryEventType.DownloadIgnored:
                     return TrackedDownloadState.Ignored;
                 default:
                     return TrackedDownloadState.Downloading;
diff --git a/src/NzbDrone.Core/History/History.cs b/src/NzbDrone.Core/History/EpisodeHistory.cs
similarity index 85%
rename from src/NzbDrone.Core/History/History.cs
rename to src/NzbDrone.Core/History/EpisodeHistory.cs
index 3cd630130..933e05d7e 100644
--- a/src/NzbDrone.Core/History/History.cs
+++ b/src/NzbDrone.Core/History/EpisodeHistory.cs
@@ -7,11 +7,11 @@ using NzbDrone.Core.Languages;
 
 namespace NzbDrone.Core.History
 {
-    public class History : ModelBase
+    public class EpisodeHistory : ModelBase
     {
         public const string DOWNLOAD_CLIENT = "downloadClient";
 
-        public History()
+        public EpisodeHistory()
         {
             Data = new Dictionary<string, string>();
         }
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.History
         public DateTime Date { get; set; }
         public Episode Episode { get; set; }
         public Series Series { get; set; }
-        public HistoryEventType EventType { get; set; }
+        public EpisodeHistoryEventType EventType { get; set; }
         public Dictionary<string, string> Data { get; set; }
         public Language Language { get; set; }
 
@@ -31,7 +31,7 @@ namespace NzbDrone.Core.History
 
     }
 
-    public enum HistoryEventType
+    public enum EpisodeHistoryEventType
     {
         Unknown = 0,
         Grabbed = 1,
diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs
index a7ca651e6..11b6c9153 100644
--- a/src/NzbDrone.Core/History/HistoryRepository.cs
+++ b/src/NzbDrone.Core/History/HistoryRepository.cs
@@ -9,20 +9,20 @@ using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.History
 {
-    public interface IHistoryRepository : IBasicRepository<History>
+    public interface IHistoryRepository : IBasicRepository<EpisodeHistory>
     {
-        History MostRecentForEpisode(int episodeId);
-        List<History> FindByEpisodeId(int episodeId);
-        History MostRecentForDownloadId(string downloadId);
-        List<History> FindByDownloadId(string downloadId);
-        List<History> GetBySeries(int seriesId, HistoryEventType? eventType);
-        List<History> GetBySeason(int seriesId, int seasonNumber, HistoryEventType? eventType);
-        List<History> FindDownloadHistory(int idSeriesId, QualityModel quality);
+        EpisodeHistory MostRecentForEpisode(int episodeId);
+        List<EpisodeHistory> FindByEpisodeId(int episodeId);
+        EpisodeHistory MostRecentForDownloadId(string downloadId);
+        List<EpisodeHistory> FindByDownloadId(string downloadId);
+        List<EpisodeHistory> GetBySeries(int seriesId, EpisodeHistoryEventType? eventType);
+        List<EpisodeHistory> GetBySeason(int seriesId, int seasonNumber, EpisodeHistoryEventType? eventType);
+        List<EpisodeHistory> FindDownloadHistory(int idSeriesId, QualityModel quality);
         void DeleteForSeries(int seriesId);
-        List<History> Since(DateTime date, HistoryEventType? eventType);
+        List<EpisodeHistory> Since(DateTime date, EpisodeHistoryEventType? eventType);
     }
 
-    public class HistoryRepository : BasicRepository<History>, IHistoryRepository
+    public class HistoryRepository : BasicRepository<EpisodeHistory>, IHistoryRepository
     {
 
         public HistoryRepository(IMainDatabase database, IEventAggregator eventAggregator)
@@ -30,33 +30,33 @@ namespace NzbDrone.Core.History
         {
         }
 
-        public History MostRecentForEpisode(int episodeId)
+        public EpisodeHistory MostRecentForEpisode(int episodeId)
         {
             return Query.Where(h => h.EpisodeId == episodeId)
                         .OrderByDescending(h => h.Date)
                         .FirstOrDefault();
         }
 
-        public List<History> FindByEpisodeId(int episodeId)
+        public List<EpisodeHistory> FindByEpisodeId(int episodeId)
         {
             return Query.Where(h => h.EpisodeId == episodeId)
                         .OrderByDescending(h => h.Date)
                         .ToList();
         }
 
-        public History MostRecentForDownloadId(string downloadId)
+        public EpisodeHistory MostRecentForDownloadId(string downloadId)
         {
             return Query.Where(h => h.DownloadId == downloadId)
              .OrderByDescending(h => h.Date)
              .FirstOrDefault();
         }
 
-        public List<History> FindByDownloadId(string downloadId)
+        public List<EpisodeHistory> FindByDownloadId(string downloadId)
         {
             return Query.Where(h => h.DownloadId == downloadId);
         }
 
-        public List<History> GetBySeries(int seriesId, HistoryEventType? eventType)
+        public List<EpisodeHistory> GetBySeries(int seriesId, EpisodeHistoryEventType? eventType)
         {
             var query = Query.Where(h => h.SeriesId == seriesId);
 
@@ -68,9 +68,9 @@ namespace NzbDrone.Core.History
             return query.OrderByDescending(h => h.Date).ToList();
         }
 
-        public List<History> GetBySeason(int seriesId, int seasonNumber, HistoryEventType? eventType)
+        public List<EpisodeHistory> GetBySeason(int seriesId, int seasonNumber, EpisodeHistoryEventType? eventType)
         {
-            var query = Query.Join<History, Episode>(JoinType.Inner, h => h.Episode, (h, e) => h.EpisodeId == e.Id)
+            var query = Query.Join<EpisodeHistory, Episode>(JoinType.Inner, h => h.Episode, (h, e) => h.EpisodeId == e.Id)
                              .Where(h => h.SeriesId == seriesId)
                              .AndWhere(h => h.Episode.SeasonNumber == seasonNumber);
 
@@ -84,14 +84,14 @@ namespace NzbDrone.Core.History
             return query;
         }
 
-        public List<History> FindDownloadHistory(int idSeriesId, QualityModel quality)
+        public List<EpisodeHistory> FindDownloadHistory(int idSeriesId, QualityModel quality)
         {
             return Query.Where(h =>
                  h.SeriesId == idSeriesId &&
                  h.Quality == quality &&
-                 (h.EventType == HistoryEventType.Grabbed ||
-                 h.EventType == HistoryEventType.DownloadFailed ||
-                 h.EventType == HistoryEventType.DownloadFolderImported)
+                 (h.EventType == EpisodeHistoryEventType.Grabbed ||
+                 h.EventType == EpisodeHistoryEventType.DownloadFailed ||
+                 h.EventType == EpisodeHistoryEventType.DownloadFolderImported)
                  ).ToList();
         }
 
@@ -100,15 +100,15 @@ namespace NzbDrone.Core.History
             Delete(c => c.SeriesId == seriesId);
         }
 
-        protected override SortBuilder<History> GetPagedQuery(QueryBuilder<History> query, PagingSpec<History> pagingSpec)
+        protected override SortBuilder<EpisodeHistory> GetPagedQuery(QueryBuilder<EpisodeHistory> query, PagingSpec<EpisodeHistory> pagingSpec)
         {
-            var baseQuery = query.Join<History, Series>(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id)
-                                 .Join<History, Episode>(JoinType.Inner, h => h.Episode, (h, e) => h.EpisodeId == e.Id);
+            var baseQuery = query.Join<EpisodeHistory, Series>(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id)
+                                 .Join<EpisodeHistory, Episode>(JoinType.Inner, h => h.Episode, (h, e) => h.EpisodeId == e.Id);
 
             return base.GetPagedQuery(baseQuery, pagingSpec);
         }
 
-        public List<History> Since(DateTime date, HistoryEventType? eventType)
+        public List<EpisodeHistory> Since(DateTime date, EpisodeHistoryEventType? eventType)
         {
             var query = Query.Where(h => h.Date >= date);
 
diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs
index dd80a139e..933fb309f 100644
--- a/src/NzbDrone.Core/History/HistoryService.cs
+++ b/src/NzbDrone.Core/History/HistoryService.cs
@@ -16,16 +16,17 @@ namespace NzbDrone.Core.History
 {
     public interface IHistoryService
     {
-        PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
-        History MostRecentForEpisode(int episodeId);
-        List<History> FindByEpisodeId(int episodeId);
-        History MostRecentForDownloadId(string downloadId);
-        History Get(int historyId);
-        List<History> GetBySeries(int seriesId, HistoryEventType? eventType);
-        List<History> GetBySeason(int seriesId, int seasonNumber, HistoryEventType? eventType);
-        List<History> Find(string downloadId, HistoryEventType eventType);
-        List<History> FindByDownloadId(string downloadId);
-        List<History> Since(DateTime date, HistoryEventType? eventType);
+        PagingSpec<EpisodeHistory> Paged(PagingSpec<EpisodeHistory> pagingSpec);
+        EpisodeHistory MostRecentForEpisode(int episodeId);
+        List<EpisodeHistory> FindByEpisodeId(int episodeId);
+        EpisodeHistory MostRecentForDownloadId(string downloadId);
+        EpisodeHistory Get(int historyId);
+        List<EpisodeHistory> GetBySeries(int seriesId, EpisodeHistoryEventType? eventType);
+        List<EpisodeHistory> GetBySeason(int seriesId, int seasonNumber, EpisodeHistoryEventType? eventType);
+        List<EpisodeHistory> Find(string downloadId, EpisodeHistoryEventType eventType);
+        List<EpisodeHistory> FindByDownloadId(string downloadId);
+        string FindDownloadId(EpisodeImportedEvent trackedDownload);
+        List<EpisodeHistory> Since(DateTime date, EpisodeHistoryEventType? eventType);
     }
 
     public class HistoryService : IHistoryService,
@@ -46,68 +47,66 @@ namespace NzbDrone.Core.History
             _logger = logger;
         }
 
-        public PagingSpec<History> Paged(PagingSpec<History> pagingSpec)
+        public PagingSpec<EpisodeHistory> Paged(PagingSpec<EpisodeHistory> pagingSpec)
         {
             return _historyRepository.GetPaged(pagingSpec);
         }
 
-        public History MostRecentForEpisode(int episodeId)
+        public EpisodeHistory MostRecentForEpisode(int episodeId)
         {
             return _historyRepository.MostRecentForEpisode(episodeId);
         }
 
-        public List<History> FindByEpisodeId(int episodeId)
+        public List<EpisodeHistory> FindByEpisodeId(int episodeId)
         {
             return _historyRepository.FindByEpisodeId(episodeId);
         }
 
-        public History MostRecentForDownloadId(string downloadId)
+        public EpisodeHistory MostRecentForDownloadId(string downloadId)
         {
             return _historyRepository.MostRecentForDownloadId(downloadId);
         }
 
-        public History Get(int historyId)
+        public EpisodeHistory Get(int historyId)
         {
             return _historyRepository.Get(historyId);
         }
 
-        public List<History> GetBySeries(int seriesId, HistoryEventType? eventType)
+        public List<EpisodeHistory> GetBySeries(int seriesId, EpisodeHistoryEventType? eventType)
         {
             return _historyRepository.GetBySeries(seriesId, eventType);
         }
 
-        public List<History> GetBySeason(int seriesId, int seasonNumber, HistoryEventType? eventType)
+        public List<EpisodeHistory> GetBySeason(int seriesId, int seasonNumber, EpisodeHistoryEventType? eventType)
         {
             return _historyRepository.GetBySeason(seriesId, seasonNumber, eventType);
         }
 
-        public List<History> Find(string downloadId, HistoryEventType eventType)
+        public List<EpisodeHistory> Find(string downloadId, EpisodeHistoryEventType eventType)
         {
             return _historyRepository.FindByDownloadId(downloadId).Where(c => c.EventType == eventType).ToList();
         }
 
-        public List<History> FindByDownloadId(string downloadId)
+        public List<EpisodeHistory> FindByDownloadId(string downloadId)
         {
             return _historyRepository.FindByDownloadId(downloadId);
         }
 
-        private string FindDownloadId(EpisodeImportedEvent trackedDownload)
+        public string FindDownloadId(EpisodeImportedEvent trackedDownload)
         {
             _logger.Debug("Trying to find downloadId for {0} from history", trackedDownload.ImportedEpisode.Path);
 
             var episodeIds = trackedDownload.EpisodeInfo.Episodes.Select(c => c.Id).ToList();
-
             var allHistory = _historyRepository.FindDownloadHistory(trackedDownload.EpisodeInfo.Series.Id, trackedDownload.ImportedEpisode.Quality);
 
-
-            //Find download related items for these episdoes
+            //Find download related items for these episodes
             var episodesHistory = allHistory.Where(h => episodeIds.Contains(h.EpisodeId)).ToList();
 
             var processedDownloadId = episodesHistory
-                .Where(c => c.EventType != HistoryEventType.Grabbed && c.DownloadId != null)
+                .Where(c => c.EventType != EpisodeHistoryEventType.Grabbed && c.DownloadId != null)
                 .Select(c => c.DownloadId);
 
-            var stillDownloading = episodesHistory.Where(c => c.EventType == HistoryEventType.Grabbed && !processedDownloadId.Contains(c.DownloadId)).ToList();
+            var stillDownloading = episodesHistory.Where(c => c.EventType == EpisodeHistoryEventType.Grabbed && !processedDownloadId.Contains(c.DownloadId)).ToList();
 
             string downloadId = null;
 
@@ -140,9 +139,9 @@ namespace NzbDrone.Core.History
         {
             foreach (var episode in message.Episode.Episodes)
             {
-                var history = new History
+                var history = new EpisodeHistory
                 {
-                    EventType = HistoryEventType.Grabbed,
+                    EventType = EpisodeHistoryEventType.Grabbed,
                     Date = DateTime.UtcNow,
                     Quality = message.Episode.ParsedEpisodeInfo.Quality,
                     SourceTitle = message.Episode.Release.Title,
@@ -160,6 +159,7 @@ namespace NzbDrone.Core.History
                 history.Data.Add("AgeMinutes", message.Episode.Release.AgeMinutes.ToString());
                 history.Data.Add("PublishedDate", message.Episode.Release.PublishDate.ToString("s") + "Z");
                 history.Data.Add("DownloadClient", message.DownloadClient);
+                history.Data.Add("DownloadClientName", message.DownloadClientName);
                 history.Data.Add("Size", message.Episode.Release.Size.ToString());
                 history.Data.Add("DownloadUrl", message.Episode.Release.DownloadUrl);
                 history.Data.Add("Guid", message.Episode.Release.Guid);
@@ -199,9 +199,9 @@ namespace NzbDrone.Core.History
 
             foreach (var episode in message.EpisodeInfo.Episodes)
             {
-                var history = new History
+                var history = new EpisodeHistory
                     {
-                        EventType = HistoryEventType.DownloadFolderImported,
+                        EventType = EpisodeHistoryEventType.DownloadFolderImported,
                         Date = DateTime.UtcNow,
                         Quality = message.EpisodeInfo.Quality,
                         SourceTitle = message.ImportedEpisode.SceneName ?? Path.GetFileNameWithoutExtension(message.EpisodeInfo.Path),
@@ -215,7 +215,8 @@ namespace NzbDrone.Core.History
                 //history.Data.Add("FileId", message.ImportedEpisode.Id.ToString());
                 history.Data.Add("DroppedPath", message.EpisodeInfo.Path);
                 history.Data.Add("ImportedPath", Path.Combine(message.EpisodeInfo.Series.Path, message.ImportedEpisode.RelativePath));
-                history.Data.Add("DownloadClient", message.DownloadClient);
+                history.Data.Add("DownloadClient", message.DownloadClientInfo?.Type);
+                history.Data.Add("DownloadClientName", message.DownloadClientInfo?.Name);
 
                 _historyRepository.Insert(history);
             }
@@ -225,9 +226,9 @@ namespace NzbDrone.Core.History
         {
             foreach (var episodeId in message.EpisodeIds)
             {
-                var history = new History
+                var history = new EpisodeHistory
                 {
-                    EventType = HistoryEventType.DownloadFailed,
+                    EventType = EpisodeHistoryEventType.DownloadFailed,
                     Date = DateTime.UtcNow,
                     Quality = message.Quality,
                     SourceTitle = message.SourceTitle,
@@ -238,6 +239,7 @@ namespace NzbDrone.Core.History
                 };
 
                 history.Data.Add("DownloadClient", message.DownloadClient);
+                history.Data.Add("DownloadClientName", message.TrackedDownload?.DownloadItem.DownloadClientInfo.Name);
                 history.Data.Add("Message", message.Message);
 
                 _historyRepository.Insert(history);
@@ -259,9 +261,9 @@ namespace NzbDrone.Core.History
 
             foreach (var episode in message.EpisodeFile.Episodes.Value)
             {
-                var history = new History
+                var history = new EpisodeHistory
                 {
-                    EventType = HistoryEventType.EpisodeFileDeleted,
+                    EventType = EpisodeHistoryEventType.EpisodeFileDeleted,
                     Date = DateTime.UtcNow,
                     Quality = message.EpisodeFile.Quality,
                     SourceTitle = message.EpisodeFile.Path,
@@ -284,9 +286,9 @@ namespace NzbDrone.Core.History
 
             foreach (var episode in message.EpisodeFile.Episodes.Value)
             {
-                var history = new History
+                var history = new EpisodeHistory
                 {
-                    EventType = HistoryEventType.EpisodeFileRenamed,
+                    EventType = EpisodeHistoryEventType.EpisodeFileRenamed,
                     Date = DateTime.UtcNow,
                     Quality = message.EpisodeFile.Quality,
                     SourceTitle = message.OriginalPath,
@@ -305,13 +307,13 @@ namespace NzbDrone.Core.History
 
         public void Handle(DownloadIgnoredEvent message)
         {
-            var historyToAdd = new List<History>();
+            var historyToAdd = new List<EpisodeHistory>();
 
             foreach (var episodeId in message.EpisodeIds)
             {
-                var history = new History
+                var history = new EpisodeHistory
                               {
-                                  EventType = HistoryEventType.DownloadIgnored,
+                                  EventType = EpisodeHistoryEventType.DownloadIgnored,
                                   Date = DateTime.UtcNow,
                                   Quality = message.Quality,
                                   SourceTitle = message.SourceTitle,
@@ -321,7 +323,8 @@ namespace NzbDrone.Core.History
                                   Language = message.Language
                               };
 
-                history.Data.Add("DownloadClient", message.DownloadClient);
+                history.Data.Add("DownloadClient", message.DownloadClientInfo.Type);
+                history.Data.Add("DownloadClientName", message.DownloadClientInfo.Name);
                 history.Data.Add("Message", message.Message);
 
                 historyToAdd.Add(history);
@@ -335,7 +338,7 @@ namespace NzbDrone.Core.History
             _historyRepository.DeleteForSeries(message.Series.Id);
         }
 
-        public List<History> Since(DateTime date, HistoryEventType? eventType)
+        public List<EpisodeHistory> Since(DateTime date, EpisodeHistoryEventType? eventType)
         {
             return _historyRepository.Since(date, eventType);
         }
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecification.cs
index 0641e8cab..3333174e0 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecification.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecification.cs
@@ -39,8 +39,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
                 }
 
                 var episodeHistory = _historyService.FindByEpisodeId(episode.Id);
-                var lastImported = episodeHistory.FirstOrDefault(h => h.EventType == HistoryEventType.DownloadFolderImported);
-                var lastGrabbed = episodeHistory.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
+                var lastImported = episodeHistory.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.DownloadFolderImported);
+                var lastGrabbed = episodeHistory.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.Grabbed);
 
                 if (lastImported == null)
                 {
diff --git a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportFailedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportFailedEvent.cs
index 7a85ff891..06d2e4baf 100644
--- a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportFailedEvent.cs
+++ b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportFailedEvent.cs
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.MediaFiles.Events
         public Exception Exception { get; set; }
         public LocalEpisode EpisodeInfo { get; }
         public bool NewDownload { get; }
-        public string DownloadClient { get;  }
+        public DownloadClientItemClientInfo DownloadClientInfo { get;  }
         public string DownloadId { get; }
 
         public EpisodeImportFailedEvent(Exception exception, LocalEpisode episodeInfo, bool newDownload, DownloadClientItem downloadClientItem)
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.MediaFiles.Events
 
             if (downloadClientItem != null)
             {
-                DownloadClient = downloadClientItem.DownloadClient;
+                DownloadClientInfo = downloadClientItem.DownloadClientInfo;
                 DownloadId = downloadClientItem.DownloadId;
             }
         }
diff --git a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs
index 7c870666b..c4de50f9a 100644
--- a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs
+++ b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs
@@ -11,7 +11,7 @@ namespace NzbDrone.Core.MediaFiles.Events
         public EpisodeFile ImportedEpisode { get; private set; }
         public List<EpisodeFile> OldFiles { get; private set; }
         public bool NewDownload { get; private set; }
-        public string DownloadClient { get; private set; }
+        public DownloadClientItemClientInfo DownloadClientInfo { get; set; }
         public string DownloadId { get; private set; }
 
         public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, List<EpisodeFile> oldFiles, bool newDownload, DownloadClientItem downloadClientItem)
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.MediaFiles.Events
 
             if (downloadClientItem != null)
             {
-                DownloadClient = downloadClientItem.DownloadClient;
+                DownloadClientInfo = downloadClientItem.DownloadClientInfo;
                 DownloadId = downloadClientItem.DownloadId;
             }
         }
diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs
index 3e6cfc354..64d44261a 100644
--- a/src/NzbDrone.Core/Notifications/NotificationService.cs
+++ b/src/NzbDrone.Core/Notifications/NotificationService.cs
@@ -143,7 +143,7 @@ namespace NzbDrone.Core.Notifications
                 EpisodeFile = message.ImportedEpisode,
                 OldFiles = message.OldFiles,
                 SourcePath = message.EpisodeInfo.Path,
-                DownloadClient = message.DownloadClient,
+                DownloadClient = message.DownloadClientInfo?.Name,
                 DownloadId = message.DownloadId
             };
 
diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs
index 66edf83dc..5b3342e2a 100644
--- a/src/NzbDrone.Core/Queue/QueueService.cs
+++ b/src/NzbDrone.Core/Queue/QueueService.cs
@@ -77,7 +77,7 @@ namespace NzbDrone.Core.Queue
                 RemoteEpisode = trackedDownload.RemoteEpisode,
                 DownloadId = trackedDownload.DownloadItem.DownloadId,
                 Protocol = trackedDownload.Protocol,
-                DownloadClient = trackedDownload.DownloadItem.DownloadClient,
+                DownloadClient = trackedDownload.DownloadItem.DownloadClientInfo.Name,
                 Indexer = trackedDownload.Indexer,
                 OutputPath = trackedDownload.DownloadItem.OutputPath.ToString()
             };
diff --git a/src/Sonarr.Api.V3/History/HistoryModule.cs b/src/Sonarr.Api.V3/History/HistoryModule.cs
index 6c8495806..59e5b5389 100644
--- a/src/Sonarr.Api.V3/History/HistoryModule.cs
+++ b/src/Sonarr.Api.V3/History/HistoryModule.cs
@@ -35,7 +35,7 @@ namespace Sonarr.Api.V3.History
             Post("/failed",  x => MarkAsFailed());
         }
 
-        protected HistoryResource MapToResource(NzbDrone.Core.History.History model, bool includeSeries, bool includeEpisode)
+        protected HistoryResource MapToResource(EpisodeHistory model, bool includeSeries, bool includeEpisode)
         {
             var resource = model.ToResource();
 
@@ -60,7 +60,7 @@ namespace Sonarr.Api.V3.History
 
         private PagingResource<HistoryResource> GetHistory(PagingResource<HistoryResource> pagingResource)
         {
-            var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, NzbDrone.Core.History.History>("date", SortDirection.Descending);
+            var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, EpisodeHistory>("date", SortDirection.Descending);
             var includeSeries = Request.GetBooleanQueryParameter("includeSeries");
             var includeEpisode = Request.GetBooleanQueryParameter("includeEpisode");
 
@@ -70,7 +70,7 @@ namespace Sonarr.Api.V3.History
 
             if (eventTypeFilter != null)
             {
-                var filterValue = (HistoryEventType)Convert.ToInt32(eventTypeFilter.Value);
+                var filterValue = (EpisodeHistoryEventType)Convert.ToInt32(eventTypeFilter.Value);
                 pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue);
             }
 
@@ -100,13 +100,13 @@ namespace Sonarr.Api.V3.History
             }
 
             DateTime date = DateTime.Parse(queryDate.Value);
-            HistoryEventType? eventType = null;
+            EpisodeHistoryEventType? eventType = null;
             var includeSeries = Request.GetBooleanQueryParameter("includeSeries");
             var includeEpisode = Request.GetBooleanQueryParameter("includeEpisode");
 
             if (queryEventType.HasValue)
             {
-                eventType = (HistoryEventType)Convert.ToInt32(queryEventType.Value);
+                eventType = (EpisodeHistoryEventType)Convert.ToInt32(queryEventType.Value);
             }
 
             return _historyService.Since(date, eventType).Select(h => MapToResource(h, includeSeries, includeEpisode)).ToList();
@@ -124,13 +124,13 @@ namespace Sonarr.Api.V3.History
             }
 
             int seriesId = Convert.ToInt32(querySeriesId.Value);
-            HistoryEventType? eventType = null;
+            EpisodeHistoryEventType? eventType = null;
             var includeSeries = Request.GetBooleanQueryParameter("includeSeries");
             var includeEpisode = Request.GetBooleanQueryParameter("includeEpisode");
 
             if (queryEventType.HasValue)
             {
-                eventType = (HistoryEventType)Convert.ToInt32(queryEventType.Value);
+                eventType = (EpisodeHistoryEventType)Convert.ToInt32(queryEventType.Value);
             }
 
             if (querySeasonNumber.HasValue)
diff --git a/src/Sonarr.Api.V3/History/HistoryResource.cs b/src/Sonarr.Api.V3/History/HistoryResource.cs
index 3bfae7090..467ab4b20 100644
--- a/src/Sonarr.Api.V3/History/HistoryResource.cs
+++ b/src/Sonarr.Api.V3/History/HistoryResource.cs
@@ -21,7 +21,7 @@ namespace Sonarr.Api.V3.History
         public DateTime Date { get; set; }
         public string DownloadId { get; set; }
 
-        public HistoryEventType EventType { get; set; }
+        public EpisodeHistoryEventType EventType { get; set; }
 
         public Dictionary<string, string> Data { get; set; }
 
@@ -31,7 +31,7 @@ namespace Sonarr.Api.V3.History
 
     public static class HistoryResourceMapper
     {
-        public static HistoryResource ToResource(this NzbDrone.Core.History.History model)
+        public static HistoryResource ToResource(this EpisodeHistory model)
         {
             if (model == null) return null;