diff --git a/Gruntfile.js b/Gruntfile.js
index f78680b09..d78fac4da 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -52,9 +52,10 @@ module.exports = function (grunt) {
 
             options:{
                 dumpLineNumbers : 'false',
-                compress        : false,
+                compress        : true,
                 yuicompress     : false,
-                ieCompat        : false
+                ieCompat        : true,
+                strictImports   : true
             },
 
             bootstrap: {
diff --git a/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs b/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs
index f68bcb2ef..bf3d72d11 100644
--- a/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs
+++ b/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs
@@ -28,8 +28,8 @@ namespace NzbDrone.Api.Test.ClientSchemaTests
 
             var schema = SchemaBuilder.GenerateSchema(model);
 
-            schema.Should().Contain(c => c.Order == 1 && c.Name == "LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && c.Value == "Poop");
-            schema.Should().Contain(c => c.Order == 0 && c.Name == "FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && c.Value == "Bob");
+            schema.Should().Contain(c => c.Order == 1 && c.Name == "LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string) c.Value == "Poop");
+            schema.Should().Contain(c => c.Order == 0 && c.Name == "FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string) c.Value == "Bob");
         }
 
     }
diff --git a/NzbDrone.Api.Test/DirectoryLookupServiceFixture.cs b/NzbDrone.Api.Test/DirectoryLookupServiceFixture.cs
index 7399a8f82..849773ddb 100644
--- a/NzbDrone.Api.Test/DirectoryLookupServiceFixture.cs
+++ b/NzbDrone.Api.Test/DirectoryLookupServiceFixture.cs
@@ -37,9 +37,6 @@ namespace NzbDrone.Api.Test
                 "Windows"
             };
 
-            Mocker.GetMock<IDiskProvider>()
-                .SetupGet(s => s.SpecialFolders)
-                .Returns(new HashSet<string> { "$recycle.bin", "system volume information", "recycler" });
         }
 
         private void SetupFolders(string root)
diff --git a/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs b/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs
index 2aaf97b79..82fb9425a 100644
--- a/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs
+++ b/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs
@@ -4,6 +4,7 @@ using FizzWare.NBuilder;
 using FluentAssertions;
 using Marr.Data;
 using NUnit.Framework;
+using NzbDrone.Api.Commands;
 using NzbDrone.Api.Config;
 using NzbDrone.Api.Episodes;
 using NzbDrone.Api.History;
@@ -13,16 +14,16 @@ using NzbDrone.Api.Mapping;
 using NzbDrone.Api.Qualities;
 using NzbDrone.Api.RootFolders;
 using NzbDrone.Api.Series;
-using NzbDrone.Api.Update;
-using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.DecisionEngine.Specifications;
 using NzbDrone.Core.Indexers;
 using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.Organizer;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Core.RootFolders;
 using NzbDrone.Core.Tv;
-using NzbDrone.Core.Update;
+using NzbDrone.Core.Update.Commands;
 using NzbDrone.Test.Common;
 using System.Linq;
 
@@ -36,13 +37,13 @@ namespace NzbDrone.Api.Test.MappingTests
         [TestCase(typeof(RootFolder), typeof(RootFolderResource))]
         [TestCase(typeof(NamingConfig), typeof(NamingConfigResource))]
         [TestCase(typeof(Indexer), typeof(IndexerResource))]
-        [TestCase(typeof(ReportInfo), typeof(ReleaseResource))]
+        [TestCase(typeof(ReleaseInfo), typeof(ReleaseResource))]
         [TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))]
         [TestCase(typeof(DownloadDecision), typeof(ReleaseResource))]
         [TestCase(typeof(Core.History.History), typeof(HistoryResource))]
-        [TestCase(typeof(UpdatePackage), typeof(UpdateResource))]
         [TestCase(typeof(Quality), typeof(QualityResource))]
         [TestCase(typeof(Log), typeof(LogResource))]
+        [TestCase(typeof(Command), typeof(CommandResource))]
         public void matching_fields(Type modelType, Type resourceType)
         {
             MappingValidation.ValidateMapping(modelType, resourceType);
@@ -116,6 +117,15 @@ namespace NzbDrone.Api.Test.MappingTests
             profileResource.InjectTo<QualityProfile>();
 
         }
+
+
+
+        [Test]
+        public void should_map_tracked_command()
+        {
+            var profileResource = new ApplicationUpdateCommand();
+            profileResource.InjectTo<CommandResource>();
+        }
     }
 
     public class ModelWithLazy
diff --git a/NzbDrone.Api/Commands/CommandModule.cs b/NzbDrone.Api/Commands/CommandModule.cs
index 371483cc3..231089734 100644
--- a/NzbDrone.Api/Commands/CommandModule.cs
+++ b/NzbDrone.Api/Commands/CommandModule.cs
@@ -1,37 +1,69 @@
 using System;
+using System.Collections.Generic;
 using System.Linq;
-using Nancy;
 using NzbDrone.Api.Extensions;
+using NzbDrone.Api.Mapping;
+using NzbDrone.Api.Validation;
 using NzbDrone.Common.Composition;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Datastore.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Commands.Tracking;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.ProgressMessaging;
+
 
 namespace NzbDrone.Api.Commands
 {
-    public class CommandModule : NzbDroneRestModule<CommandResource>
+    public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, Command>, IHandle<CommandUpdatedEvent>
     {
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly ICommandExecutor _commandExecutor;
         private readonly IContainer _container;
+        private readonly ITrackCommands _trackCommands;
 
-        public CommandModule(IMessageAggregator messageAggregator, IContainer container)
+        public CommandModule(ICommandExecutor commandExecutor, IContainer container, ITrackCommands trackCommands)
+            : base(commandExecutor)
         {
-            _messageAggregator = messageAggregator;
+            _commandExecutor = commandExecutor;
             _container = container;
+            _trackCommands = trackCommands;
 
-            Post["/"] = x => RunCommand(ReadResourceFromRequest());
+            GetResourceById = GetCommand;
+            CreateResource = StartCommand;
+            GetResourceAll = GetAllCommands;
 
+            PostValidator.RuleFor(c => c.Name).NotBlank();
         }
 
-        private Response RunCommand(CommandResource resource)
+        private CommandResource GetCommand(int id)
+        {
+            return _trackCommands.GetById(id).InjectTo<CommandResource>();
+        }
+
+        private int StartCommand(CommandResource commandResource)
         {
             var commandType =
-                _container.GetImplementations(typeof(ICommand))
-                          .Single(c => c.Name.Replace("Command", "")
-                          .Equals(resource.Command, StringComparison.InvariantCultureIgnoreCase));
+              _container.GetImplementations(typeof(Command))
+                        .Single(c => c.Name.Replace("Command", "")
+                        .Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
 
             dynamic command = Request.Body.FromJson(commandType);
-            _messageAggregator.PublishCommand(command);
 
-            return resource.AsResponse(HttpStatusCode.Created);
+            var trackedCommand = (Command)_commandExecutor.PublishCommandAsync(command);
+            return trackedCommand.Id;
+        }
+
+        private List<CommandResource> GetAllCommands()
+        {
+            return ToListResource(_trackCommands.RunningCommands);
+        }
+
+        public void Handle(CommandUpdatedEvent message)
+        {
+            if (message.Command.SendUpdatesToClient)
+            {
+                BroadcastResourceChange(ModelAction.Updated, message.Command.Id);
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Api/Commands/CommandResource.cs b/NzbDrone.Api/Commands/CommandResource.cs
index e71a0d08f..86c1f4b15 100644
--- a/NzbDrone.Api/Commands/CommandResource.cs
+++ b/NzbDrone.Api/Commands/CommandResource.cs
@@ -1,9 +1,16 @@
-using NzbDrone.Api.REST;
+using System;
+using NzbDrone.Api.REST;
+using NzbDrone.Core.Messaging.Commands.Tracking;
 
 namespace NzbDrone.Api.Commands
 {
     public class CommandResource : RestResource
     {
-        public string Command { get; set; }
+        public String Name { get; set; }
+        public String Message { get; set; }
+        public DateTime StartedOn { get; set; }
+        public DateTime StateChangeTime { get; set; }
+        public Boolean SendUpdatesToClient { get; set; }
+        public CommandStatus State { get; set; }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Api/Episodes/EpisodeConnection.cs b/NzbDrone.Api/Episodes/EpisodeConnection.cs
deleted file mode 100644
index 814c6dc84..000000000
--- a/NzbDrone.Api/Episodes/EpisodeConnection.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using Microsoft.AspNet.SignalR;
-using Microsoft.AspNet.SignalR.Infrastructure;
-using NzbDrone.Api.SignalR;
-using NzbDrone.Common.Messaging;
-using NzbDrone.Core.Download;
-using NzbDrone.Core.Tv;
-
-namespace NzbDrone.Api.Episodes
-{
-    public class EpisodeConnection : BasicResourceConnection<Episode>, IHandleAsync<EpisodeGrabbedEvent>
-    {
-        public override string Resource
-        {
-            get { return "/Episodes"; }
-        }
-
-        public void HandleAsync(EpisodeGrabbedEvent message)
-        {
-            var context = ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType());
-            context.Connection.Broadcast(message);
-        }
-    }
-}
diff --git a/NzbDrone.Api/ErrorManagement/ApiException.cs b/NzbDrone.Api/ErrorManagement/ApiException.cs
index 0d5842d19..2a9f2678f 100644
--- a/NzbDrone.Api/ErrorManagement/ApiException.cs
+++ b/NzbDrone.Api/ErrorManagement/ApiException.cs
@@ -9,7 +9,6 @@ namespace NzbDrone.Api.ErrorManagement
     {
         public object Content { get; private set; }
 
-
         public HttpStatusCode StatusCode { get; private set; }
 
         protected ApiException(HttpStatusCode statusCode, object content = null)
diff --git a/NzbDrone.Api/ErrorManagement/NzbDroneErrorPipeline.cs b/NzbDrone.Api/ErrorManagement/NzbDroneErrorPipeline.cs
index 9db64b37d..47d79c849 100644
--- a/NzbDrone.Api/ErrorManagement/NzbDroneErrorPipeline.cs
+++ b/NzbDrone.Api/ErrorManagement/NzbDroneErrorPipeline.cs
@@ -3,6 +3,7 @@ using FluentValidation;
 using NLog;
 using Nancy;
 using NzbDrone.Api.Extensions;
+using NzbDrone.Core;
 using HttpStatusCode = Nancy.HttpStatusCode;
 
 namespace NzbDrone.Api.ErrorManagement
@@ -35,9 +36,20 @@ namespace NzbDrone.Api.ErrorManagement
                 return validationException.Errors.AsResponse(HttpStatusCode.BadRequest);
             }
 
+            var clientException = exception as NzbDroneClientException;
+
+            if (clientException != null)
+            {
+                return new ErrorModel
+                {
+                    Message = exception.Message,
+                    Description = exception.ToString()
+                }.AsResponse((HttpStatusCode)clientException.StatusCode);
+            }
+
             _logger.FatalException("Request Failed", exception);
 
-            return new ErrorModel()
+            return new ErrorModel
                 {
                     Message = exception.Message,
                     Description = exception.ToString()
diff --git a/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs b/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs
index 70f7fc0bf..183326415 100644
--- a/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs
+++ b/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs
@@ -1,5 +1,4 @@
-using System;
-using Nancy;
+using Nancy;
 using Nancy.Bootstrapper;
 using NzbDrone.Api.Frontend;
 
diff --git a/NzbDrone.Api/Extensions/ReqResExtensions.cs b/NzbDrone.Api/Extensions/ReqResExtensions.cs
index fd9980347..1f1d89180 100644
--- a/NzbDrone.Api/Extensions/ReqResExtensions.cs
+++ b/NzbDrone.Api/Extensions/ReqResExtensions.cs
@@ -12,7 +12,6 @@ namespace NzbDrone.Api.Extensions
     {
         private static readonly NancyJsonSerializer NancySerializer = new NancyJsonSerializer();
 
-
         public static readonly string LastModified = BuildInfo.BuildDateTime.ToString("r");
 
         public static T FromJson<T>(this Stream body) where T : class, new()
@@ -25,7 +24,6 @@ namespace NzbDrone.Api.Extensions
             return (T)FromJson(body, type);
         }
 
-
         public static object FromJson(this Stream body, Type type)
         {
             var reader = new StreamReader(body, true);
diff --git a/NzbDrone.Api/Indexers/IndexerModule.cs b/NzbDrone.Api/Indexers/IndexerModule.cs
index 8a4439b87..02215d3ed 100644
--- a/NzbDrone.Api/Indexers/IndexerModule.cs
+++ b/NzbDrone.Api/Indexers/IndexerModule.cs
@@ -6,7 +6,6 @@ using NzbDrone.Api.REST;
 using NzbDrone.Core.Indexers;
 using Omu.ValueInjecter;
 using FluentValidation;
-using NzbDrone.Api.Extensions;
 using NzbDrone.Api.Mapping;
 
 namespace NzbDrone.Api.Indexers
diff --git a/NzbDrone.Api/Indexers/ReleaseModule.cs b/NzbDrone.Api/Indexers/ReleaseModule.cs
index aaf452261..f00d30542 100644
--- a/NzbDrone.Api/Indexers/ReleaseModule.cs
+++ b/NzbDrone.Api/Indexers/ReleaseModule.cs
@@ -1,7 +1,9 @@
 using System.Collections.Generic;
 using Nancy;
+using NLog;
 using NzbDrone.Api.Mapping;
 using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.DecisionEngine.Specifications;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.IndexerSearch;
 using NzbDrone.Core.Indexers;
@@ -23,10 +25,10 @@ namespace NzbDrone.Api.Indexers
         private readonly IParsingService _parsingService;
 
         public ReleaseModule(IFetchAndParseRss rssFetcherAndParser,
-            ISearchForNzb nzbSearchService,
-            IMakeDownloadDecision downloadDecisionMaker,
-            IDownloadService downloadService,
-            IParsingService parsingService)
+                             ISearchForNzb nzbSearchService,
+                             IMakeDownloadDecision downloadDecisionMaker,
+                             IDownloadService downloadService,
+                             IParsingService parsingService)
         {
             _rssFetcherAndParser = rssFetcherAndParser;
             _nzbSearchService = nzbSearchService;
@@ -40,7 +42,7 @@ namespace NzbDrone.Api.Indexers
         private Response DownloadRelease(ReleaseResource release)
         {
             var remoteEpisode = _parsingService.Map(release.InjectTo<ParsedEpisodeInfo>(), 0);
-            remoteEpisode.Report = release.InjectTo<ReportInfo>();
+            remoteEpisode.Release = release.InjectTo<ReleaseInfo>();
 
             _downloadService.DownloadReport(remoteEpisode);
 
@@ -60,6 +62,7 @@ namespace NzbDrone.Api.Indexers
         private List<ReleaseResource> GetEpisodeReleases(int episodeId)
         {
             var decisions = _nzbSearchService.EpisodeSearch(episodeId);
+
             return MapDecisions(decisions);
         }
 
@@ -79,7 +82,7 @@ namespace NzbDrone.Api.Indexers
             {
                 var release = new ReleaseResource();
 
-                release.InjectFrom(downloadDecision.RemoteEpisode.Report);
+                release.InjectFrom(downloadDecision.RemoteEpisode.Release);
                 release.InjectFrom(downloadDecision.RemoteEpisode.ParsedEpisodeInfo);
                 release.InjectFrom(downloadDecision);
                 release.Rejections = downloadDecision.Rejections.ToList();
diff --git a/NzbDrone.Api/Indexers/ReleaseResource.cs b/NzbDrone.Api/Indexers/ReleaseResource.cs
index 3db66d39f..3cc4a054a 100644
--- a/NzbDrone.Api/Indexers/ReleaseResource.cs
+++ b/NzbDrone.Api/Indexers/ReleaseResource.cs
@@ -12,8 +12,6 @@ namespace NzbDrone.Api.Indexers
         public Int32 Age { get; set; }
         public Int64 Size { get; set; }
         public String Indexer { get; set; }
-        public String NzbInfoUrl { get; set; }
-        public String NzbUrl { get; set; }
         public String ReleaseGroup { get; set; }
         public String Title { get; set; }
         public Boolean FullSeason { get; set; }
@@ -26,5 +24,9 @@ namespace NzbDrone.Api.Indexers
         public Boolean Approved { get; set; }
         public Int32 TvRageId { get; set; }
         public List<string> Rejections { get; set; }
+        public DateTime PublishDate { get; set; }
+        public String CommentUrl { get; set; }
+        public String DownloadUrl { get; set; }
+        public String InfoUrl { get; set; }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Api/Logs/LogModule.cs b/NzbDrone.Api/Logs/LogModule.cs
index e1c39d92b..59ea4975d 100644
--- a/NzbDrone.Api/Logs/LogModule.cs
+++ b/NzbDrone.Api/Logs/LogModule.cs
@@ -17,6 +17,12 @@ namespace NzbDrone.Api.Logs
         private PagingResource<LogResource> GetLogs(PagingResource<LogResource> pagingResource)
         {
             var pageSpec = pagingResource.InjectTo<PagingSpec<Log>>();
+
+            if (pageSpec.SortKey == "time")
+            {
+                pageSpec.SortKey = "id";
+            }
+
             return ApplyToPage(_logService.Paged, pageSpec);
         }
     }
diff --git a/NzbDrone.Api/Mapping/CloneInjection.cs b/NzbDrone.Api/Mapping/CloneInjection.cs
index 71bbd4bbe..1266ebd0b 100644
--- a/NzbDrone.Api/Mapping/CloneInjection.cs
+++ b/NzbDrone.Api/Mapping/CloneInjection.cs
@@ -17,9 +17,10 @@ namespace NzbDrone.Api.Mapping
 
         protected override object SetValue(ConventionInfo conventionInfo)
         {
-            if (conventionInfo.SourceProp.Type.IsValueType || conventionInfo.SourceProp.Type == typeof(string))
+            if (conventionInfo.SourceProp.Type == conventionInfo.TargetProp.Type)
                 return conventionInfo.SourceProp.Value;
 
+
             if (conventionInfo.SourceProp.Type.IsArray)
             {
                 var array = (Array)conventionInfo.SourceProp.Value;
diff --git a/NzbDrone.Api/NancyBootstrapper.cs b/NzbDrone.Api/NancyBootstrapper.cs
index 5f65c80e2..263c4f265 100644
--- a/NzbDrone.Api/NancyBootstrapper.cs
+++ b/NzbDrone.Api/NancyBootstrapper.cs
@@ -1,14 +1,14 @@
-using System;
-using NLog;
+using NLog;
 using Nancy.Bootstrapper;
 using Nancy.Diagnostics;
 using NzbDrone.Api.Authentication;
 using NzbDrone.Api.ErrorManagement;
-using NzbDrone.Api.Extensions;
 using NzbDrone.Api.Extensions.Pipelines;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Instrumentation;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using TinyIoC;
 
 namespace NzbDrone.Api
@@ -21,20 +21,18 @@ namespace NzbDrone.Api
         public NancyBootstrapper(TinyIoCContainer tinyIoCContainer)
         {
             _tinyIoCContainer = tinyIoCContainer;
-            _logger = LogManager.GetCurrentClassLogger();
+            _logger =  NzbDroneLogger.GetLogger();
         }
 
         protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
         {
             _logger.Info("Starting NzbDrone API");
 
-
             RegisterPipelines(pipelines);
 
             container.Resolve<DatabaseTarget>().Register();
             container.Resolve<IEnableBasicAuthInNancy>().Register(pipelines);
-            container.Resolve<IMessageAggregator>().PublishEvent(new ApplicationStartedEvent());
-
+            container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
 
             ApplicationPipelines.OnError.AddItemToEndOfPipeline(container.Resolve<NzbDroneErrorPipeline>().HandleException);
         }
@@ -47,10 +45,8 @@ namespace NzbDrone.Api
             {
                 registerNancyPipeline.Register(pipelines);
             }
-
         }
 
-
         protected override TinyIoCContainer GetApplicationContainer()
         {
             return _tinyIoCContainer;
diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj
index d0a03bd17..a4fb4ee50 100644
--- a/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/NzbDrone.Api/NzbDrone.Api.csproj
@@ -91,7 +91,6 @@
     <Compile Include="Directories\DirectoryModule.cs" />
     <Compile Include="Episodes\EpisodeModule.cs" />
     <Compile Include="Episodes\EpisodeResource.cs" />
-    <Compile Include="Episodes\EpisodeConnection.cs" />
     <Compile Include="Extensions\Pipelines\CacheHeaderPipeline.cs" />
     <Compile Include="Extensions\Pipelines\GZipPipeline.cs" />
     <Compile Include="Extensions\Pipelines\IfModifiedPipeline.cs" />
@@ -123,6 +122,8 @@
     <Compile Include="Mapping\ValueInjectorExtensions.cs" />
     <Compile Include="Missing\MissingModule.cs" />
     <Compile Include="Config\NamingSampleResource.cs" />
+    <Compile Include="NzbDroneRestModuleWithSignalR.cs" />
+    <Compile Include="ResourceChangeMessage.cs" />
     <Compile Include="Notifications\NotificationSchemaModule.cs" />
     <Compile Include="Notifications\NotificationModule.cs" />
     <Compile Include="Notifications\NotificationResource.cs" />
@@ -135,10 +136,6 @@
     <Compile Include="REST\RestResource.cs" />
     <Compile Include="RootFolders\RootFolderModule.cs" />
     <Compile Include="RootFolders\RootFolderResource.cs" />
-    <Compile Include="RootFolders\RootFolderConnection.cs" />
-    <Compile Include="Seasons\SeasonModule.cs" />
-    <Compile Include="Seasons\SeasonResource.cs" />
-    <Compile Include="Series\SeriesConnection.cs" />
     <Compile Include="Series\SeriesResource.cs" />
     <Compile Include="Series\SeriesModule.cs" />
     <Compile Include="Series\SeriesLookupModule.cs" />
@@ -156,11 +153,6 @@
     <Compile Include="Qualities\QualitySizeModule.cs" />
     <Compile Include="Extensions\ReqResExtensions.cs" />
     <Compile Include="Config\SettingsModule.cs" />
-    <Compile Include="SignalR\BasicResourceConnection.cs" />
-    <Compile Include="SignalR\NoOpPerformanceCounterManager.cs" />
-    <Compile Include="SignalR\Serializer.cs" />
-    <Compile Include="SignalR\SignalrDependencyResolver.cs" />
-    <Compile Include="SignalR\NzbDronePersistentConnection.cs" />
     <Compile Include="System\SystemModule.cs" />
     <Compile Include="TinyIoCNancyBootstrapper.cs" />
     <Compile Include="Update\UpdateModule.cs" />
@@ -183,6 +175,10 @@
       <Project>{ff5ee3b6-913b-47ce-9ceb-11c51b4e1205}</Project>
       <Name>NzbDrone.Core</Name>
     </ProjectReference>
+    <ProjectReference Include="..\NzbDrone.SignalR\NzbDrone.SignalR.csproj">
+      <Project>{7c2cc69f-5ca0-4e5c-85cb-983f9f6c3b36}</Project>
+      <Name>NzbDrone.SignalR</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
@@ -194,4 +190,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
\ No newline at end of file
+</Project>
diff --git a/NzbDrone.Api/NzbDrone.Api.ncrunchproject b/NzbDrone.Api/NzbDrone.Api.ncrunchproject
index 575717ac8..1a2228e7f 100644
--- a/NzbDrone.Api/NzbDrone.Api.ncrunchproject
+++ b/NzbDrone.Api/NzbDrone.Api.ncrunchproject
@@ -1,8 +1,8 @@
 <ProjectConfiguration>
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
-  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,9 +12,11 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration />
-  <UseBuildPlatform />
-  <ProxyProcessPath />
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.Api/NzbDroneRestModule.cs b/NzbDrone.Api/NzbDroneRestModule.cs
index b4b01aee5..5c297e331 100644
--- a/NzbDrone.Api/NzbDroneRestModule.cs
+++ b/NzbDrone.Api/NzbDroneRestModule.cs
@@ -9,6 +9,8 @@ namespace NzbDrone.Api
 {
     public abstract class NzbDroneRestModule<TResource> : RestModule<TResource> where TResource : RestResource, new()
     {
+        protected string Resource { get; private set; }
+
         protected NzbDroneRestModule()
             : this(new TResource().ResourceName)
         {
@@ -17,6 +19,7 @@ namespace NzbDrone.Api
         protected NzbDroneRestModule(string resource)
             : base("/api/" + resource.Trim('/'))
         {
+            Resource = resource;
             PostValidator.RuleFor(r => r.Id).IsZero();
             PutValidator.RuleFor(r => r.Id).ValidId();
         }
@@ -28,7 +31,7 @@ namespace NzbDrone.Api
             return model.Id;
         }
 
-        protected List<TResource> ToListResource<TModel>(Func<IEnumerable<TModel>> function) where TModel : ModelBase, new()
+        protected List<TResource> ToListResource<TModel>(Func<IEnumerable<TModel>> function) where TModel : class
         {
             var modelList = function();
             return modelList.InjectTo<List<TResource>>();
diff --git a/NzbDrone.Api/NzbDroneRestModuleWithSignalR.cs b/NzbDrone.Api/NzbDroneRestModuleWithSignalR.cs
new file mode 100644
index 000000000..d8350b8ae
--- /dev/null
+++ b/NzbDrone.Api/NzbDroneRestModuleWithSignalR.cs
@@ -0,0 +1,56 @@
+using NzbDrone.Api.REST;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Datastore.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.SignalR;
+
+namespace NzbDrone.Api
+{
+    public abstract class NzbDroneRestModuleWithSignalR<TResource, TModel> : NzbDroneRestModule<TResource>, IHandle<ModelEvent<TModel>>
+        where TResource : RestResource, new()
+        where TModel : ModelBase
+    {
+        private readonly ICommandExecutor _commandExecutor;
+
+        protected NzbDroneRestModuleWithSignalR(ICommandExecutor commandExecutor)
+        {
+            _commandExecutor = commandExecutor;
+        }
+
+        public void Handle(ModelEvent<TModel> message)
+        {
+            if (message.Action == ModelAction.Deleted || message.Action == ModelAction.Sync)
+            {
+                BroadcastResourceChange(message.Action);
+            }
+
+            BroadcastResourceChange(message.Action, message.Model.Id);
+        }
+
+        protected void BroadcastResourceChange(ModelAction action, int id)
+        {
+            var resource = GetResourceById(id);
+
+            var signalRMessage = new SignalRMessage
+            {
+                Name = Resource,
+                Body = new ResourceChangeMessage<TResource>(resource, action)
+            };
+
+            _commandExecutor.PublishCommand(new BroadcastSignalRMessage(signalRMessage));
+        }
+
+        protected void BroadcastResourceChange(ModelAction action)
+        {
+            var signalRMessage = new SignalRMessage
+            {
+                Name = Resource,
+                Body = new ResourceChangeMessage<TResource>(action)
+            };
+
+            _commandExecutor.PublishCommand(new BroadcastSignalRMessage(signalRMessage));
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Api/REST/RestModule.cs b/NzbDrone.Api/REST/RestModule.cs
index 2b19bcc17..9a576d360 100644
--- a/NzbDrone.Api/REST/RestModule.cs
+++ b/NzbDrone.Api/REST/RestModule.cs
@@ -61,7 +61,7 @@ namespace NzbDrone.Api.REST
 
         protected Func<int, TResource> GetResourceById
         {
-            private get { return _getResourceById; }
+            get { return _getResourceById; }
             set
             {
                 _getResourceById = value;
diff --git a/NzbDrone.Api/ResourceChangeMessage.cs b/NzbDrone.Api/ResourceChangeMessage.cs
new file mode 100644
index 000000000..6b7efb50a
--- /dev/null
+++ b/NzbDrone.Api/ResourceChangeMessage.cs
@@ -0,0 +1,29 @@
+using System;
+using NzbDrone.Api.REST;
+using NzbDrone.Core.Datastore.Events;
+
+namespace NzbDrone.Api
+{
+    public class ResourceChangeMessage<TResource> where TResource : RestResource
+    {
+        public TResource Resource { get; private set; }
+        public ModelAction Action { get; private set; }
+
+        public ResourceChangeMessage(ModelAction action)
+        {
+            if (action != ModelAction.Deleted || action != ModelAction.Sync)
+            {
+                throw new InvalidOperationException("Resource message without a resource needs to have Delete or Sync as action");
+            }
+
+            Action = action;
+        }
+
+        public ResourceChangeMessage(TResource resource, ModelAction action)
+        {
+            Resource = resource;
+            Action = action;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/NzbDrone.Api/RootFolders/RootFolderConnection.cs b/NzbDrone.Api/RootFolders/RootFolderConnection.cs
deleted file mode 100644
index 0068cc972..000000000
--- a/NzbDrone.Api/RootFolders/RootFolderConnection.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using NzbDrone.Api.SignalR;
-using NzbDrone.Core.RootFolders;
-
-namespace NzbDrone.Api.RootFolders
-{
-    public class RootFolderConnection : BasicResourceConnection<RootFolder>
-    {
-        public override string Resource
-        {
-            get { return "RootFolder"; }
-        }
-    }
-}
diff --git a/NzbDrone.Api/RootFolders/RootFolderModule.cs b/NzbDrone.Api/RootFolders/RootFolderModule.cs
index 7bcc4aa52..dd346d5de 100644
--- a/NzbDrone.Api/RootFolders/RootFolderModule.cs
+++ b/NzbDrone.Api/RootFolders/RootFolderModule.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using NzbDrone.Core.RootFolders;
 using NzbDrone.Api.Mapping;
 using NzbDrone.Api.Validation;
diff --git a/NzbDrone.Api/Seasons/SeasonModule.cs b/NzbDrone.Api/Seasons/SeasonModule.cs
deleted file mode 100644
index 0521b8518..000000000
--- a/NzbDrone.Api/Seasons/SeasonModule.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System.Collections.Generic;
-using NzbDrone.Api.Mapping;
-using NzbDrone.Core.Tv;
-
-namespace NzbDrone.Api.Seasons
-{
-    public class SeasonModule : NzbDroneRestModule<SeasonResource>
-    {
-        private readonly ISeasonService _seasonService;
-
-        public SeasonModule(ISeasonService seasonService)
-            : base("/season")
-        {
-            _seasonService = seasonService;
-
-            GetResourceAll = GetSeasons;
-            GetResourceById = GetSeason;
-            UpdateResource = Update;
-
-            Post["/pass"] = x => SetSeasonPass();
-        }
-
-        private List<SeasonResource> GetSeasons()
-        {
-            var seriesId = Request.Query.SeriesId;
-
-            if (seriesId.HasValue)
-            {
-                return ToListResource<Season>(() => _seasonService.GetSeasonsBySeries(seriesId));
-            }
-
-            return ToListResource(() => _seasonService.GetAllSeasons());
-        }
-
-        private SeasonResource GetSeason(int id)
-        {
-            return _seasonService.Get(id).InjectTo<SeasonResource>();
-        }
-
-        private void Update(SeasonResource seasonResource)
-        {
-            _seasonService.SetMonitored(seasonResource.SeriesId, seasonResource.SeasonNumber, seasonResource.Monitored);
-        }
-
-        private List<SeasonResource> SetSeasonPass()
-        {
-            var seriesId = Request.Form.SeriesId;
-            var seasonNumber = Request.Form.SeasonNumber;
-
-            return ToListResource<Season>(() => _seasonService.SetSeasonPass(seriesId, seasonNumber));
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Api/Seasons/SeasonResource.cs b/NzbDrone.Api/Seasons/SeasonResource.cs
deleted file mode 100644
index 46e14be9d..000000000
--- a/NzbDrone.Api/Seasons/SeasonResource.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using NzbDrone.Api.REST;
-
-namespace NzbDrone.Api.Seasons
-{
-    public class SeasonResource : RestResource
-    {
-        public int SeriesId { get; set; }
-        public int SeasonNumber { get; set; }
-        public Boolean Monitored { get; set; }
-    }
-}
diff --git a/NzbDrone.Api/Series/SeriesConnection.cs b/NzbDrone.Api/Series/SeriesConnection.cs
deleted file mode 100644
index 4de76e7e6..000000000
--- a/NzbDrone.Api/Series/SeriesConnection.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using NzbDrone.Api.SignalR;
-
-namespace NzbDrone.Api.Series
-{
-    public class SeriesConnection : BasicResourceConnection<Core.Tv.Series>
-    {
-        public override string Resource
-        {
-            get { return "/Series"; }
-        }
-    }
-}
diff --git a/NzbDrone.Api/Series/SeriesModule.cs b/NzbDrone.Api/Series/SeriesModule.cs
index fe7b820a6..ea01b1a06 100644
--- a/NzbDrone.Api/Series/SeriesModule.cs
+++ b/NzbDrone.Api/Series/SeriesModule.cs
@@ -117,7 +117,6 @@ namespace NzbDrone.Api.Series
         {
             resource.EpisodeCount = seriesStatistics.EpisodeCount;
             resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount;
-            resource.SeasonCount = seriesStatistics.SeasonCount;
             resource.NextAiring = seriesStatistics.NextAiring;
         }
     }
diff --git a/NzbDrone.Api/Series/SeriesResource.cs b/NzbDrone.Api/Series/SeriesResource.cs
index 34f7dcead..170fa8301 100644
--- a/NzbDrone.Api/Series/SeriesResource.cs
+++ b/NzbDrone.Api/Series/SeriesResource.cs
@@ -14,7 +14,17 @@ namespace NzbDrone.Api.Series
 
         //View Only
         public String Title { get; set; }
-        public Int32 SeasonCount { get; set; }
+
+        public Int32 SeasonCount
+        {
+            get
+            {
+                if (Seasons != null) return Seasons.Count;
+
+                return 0;
+            }
+        }
+
         public Int32 EpisodeCount { get; set; }
         public Int32 EpisodeFileCount { get; set; }
         public SeriesStatusType Status { get; set; }
@@ -26,7 +36,8 @@ namespace NzbDrone.Api.Series
         public List<MediaCover> Images { get; set; }
 
         public String RemotePoster { get; set; }
-
+        public List<Season> Seasons { get; set; }
+        public Int32 Year { get; set; }
 
         //View & Edit
         public String Path { get; set; }
diff --git a/NzbDrone.Api/SignalR/BasicResourceConnection.cs b/NzbDrone.Api/SignalR/BasicResourceConnection.cs
deleted file mode 100644
index e56ae4fd2..000000000
--- a/NzbDrone.Api/SignalR/BasicResourceConnection.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Threading.Tasks;
-using Microsoft.AspNet.SignalR;
-using Microsoft.AspNet.SignalR.Infrastructure;
-using NLog;
-using NzbDrone.Common.Messaging;
-using NzbDrone.Core.Datastore;
-using NzbDrone.Core.Datastore.Events;
-
-namespace NzbDrone.Api.SignalR
-{
-    public abstract class BasicResourceConnection<T> :
-        NzbDronePersistentConnection,
-        IHandleAsync<ModelEvent<T>>
-        where T : ModelBase
-    {
-        private readonly Logger _logger;
-
-
-        public BasicResourceConnection()
-        {
-            _logger = LogManager.GetCurrentClassLogger();
-        }
-
-        protected override Task OnConnected(IRequest request, string connectionId)
-        {
-            _logger.Trace("SignalR client connected. ID:{0}", connectionId);
-            return base.OnConnected(request, connectionId);
-        }
-
-        public void HandleAsync(ModelEvent<T> message)
-        {
-            var context = ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType());
-            context.Connection.Broadcast(message);
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Api/SignalR/NzbDronePersistentConnection.cs b/NzbDrone.Api/SignalR/NzbDronePersistentConnection.cs
deleted file mode 100644
index 2e4c8444d..000000000
--- a/NzbDrone.Api/SignalR/NzbDronePersistentConnection.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Microsoft.AspNet.SignalR;
-
-namespace NzbDrone.Api.SignalR
-{
-    public abstract class NzbDronePersistentConnection : PersistentConnection
-    {
-        public abstract string Resource { get; }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Api/System/SystemModule.cs b/NzbDrone.Api/System/SystemModule.cs
index e0e9b9177..a013b15af 100644
--- a/NzbDrone.Api/System/SystemModule.cs
+++ b/NzbDrone.Api/System/SystemModule.cs
@@ -40,6 +40,7 @@ namespace NzbDrone.Api.System
                     OsVersion = OsInfo.Version.ToString(),
                     IsMono = OsInfo.IsMono,
                     IsLinux = OsInfo.IsLinux,
+                    IsWindows = OsInfo.IsWindows,
                     Branch = _configFileProvider.Branch,
                     Authentication = _configFileProvider.AuthenticationEnabled
                 }.AsResponse();
diff --git a/NzbDrone.Api/Update/UpdateModule.cs b/NzbDrone.Api/Update/UpdateModule.cs
index 8a95efa4b..fbe13bd8e 100644
--- a/NzbDrone.Api/Update/UpdateModule.cs
+++ b/NzbDrone.Api/Update/UpdateModule.cs
@@ -33,8 +33,6 @@ namespace NzbDrone.Api.Update
 
     public class UpdateResource : RestResource
     {
-        public String Id { get; set; }
-
         [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))]
         public Version Version { get; set; }
 
diff --git a/NzbDrone.Api/Validation/RuleBuilderExtensions.cs b/NzbDrone.Api/Validation/RuleBuilderExtensions.cs
index b142f5a56..a13cbf52a 100644
--- a/NzbDrone.Api/Validation/RuleBuilderExtensions.cs
+++ b/NzbDrone.Api/Validation/RuleBuilderExtensions.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Linq.Expressions;
-using System.Text.RegularExpressions;
+using System.Text.RegularExpressions;
 using FluentValidation;
 using FluentValidation.Validators;
 
@@ -27,5 +25,10 @@ namespace NzbDrone.Api.Validation
         {
             return ruleBuilder.SetValidator(new PathValidator());
         }
+
+        public static IRuleBuilderOptions<T, string> NotBlank<T>(this IRuleBuilder<T, string> ruleBuilder)
+        {
+            return ruleBuilder.SetValidator(new NotNullValidator()).SetValidator(new NotEmptyValidator(""));
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.App.Test/ContainerFixture.cs b/NzbDrone.App.Test/ContainerFixture.cs
index f4bf6af6c..244901c0a 100644
--- a/NzbDrone.App.Test/ContainerFixture.cs
+++ b/NzbDrone.App.Test/ContainerFixture.cs
@@ -2,12 +2,13 @@
 using NUnit.Framework;
 using NzbDrone.Common;
 using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Messaging;
-using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.Indexers;
 using NzbDrone.Core.Jobs;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Host;
 using NzbDrone.Test.Common;
 using FluentAssertions;
@@ -45,7 +46,6 @@ namespace NzbDrone.App.Test
         {
             var genericExecutor = typeof(IExecute<>).MakeGenericType(typeof(RssSyncCommand));
             var container = MainAppContainerBuilder.BuildContainer(args);
-            DbFactory.RegisterDatabase(container);
 
             var executor = container.Resolve(genericExecutor);
 
diff --git a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs
index 4a1cf9021..1d91558a6 100644
--- a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs
+++ b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs
@@ -48,6 +48,23 @@ namespace NzbDrone.Common.Test.CacheTests
             _cachedString.Find("Key").Should().Be("New");
         }
 
+
+        [Test]
+        public void should_be_able_to_remove_key()
+        {
+            _cachedString.Set("Key", "Value");
+
+            _cachedString.Remove("Key");
+
+            _cachedString.Find("Key").Should().BeNull();
+        }
+
+        [Test]
+        public void should_be_able_to_remove_non_existing_key()
+        {
+            _cachedString.Remove("Key");
+        }
+
         [Test]
         public void should_store_null()
         {
diff --git a/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixture.cs b/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixture.cs
index 1bcb330f1..4de222365 100644
--- a/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixture.cs
+++ b/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixture.cs
@@ -23,6 +23,10 @@ namespace NzbDrone.Common.Test.DiskProviderTests
 
             if (_binFolderCopy.Exists)
             {
+                foreach (var file in _binFolderCopy.GetFiles("*", SearchOption.AllDirectories))
+                {
+                    file.Attributes = FileAttributes.Normal;
+                }
                 _binFolderCopy.Delete(true);
             }
 
@@ -83,11 +87,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
         [Test]
         public void CopyFolder_should_copy_folder()
         {
-
-
             Subject.CopyFolder(_binFolder.FullName, _binFolderCopy.FullName);
-
-
             VerifyCopy();
         }
 
@@ -127,6 +127,22 @@ namespace NzbDrone.Common.Test.DiskProviderTests
         }
 
 
+        [Test]
+        public void move_read_only_file()
+        {
+            var source = GetTestFilePath();
+            var destination = GetTestFilePath();
+
+            Subject.WriteAllText(source, "SourceFile");
+            Subject.WriteAllText(destination, "DestinationFile");
+
+            File.SetAttributes(source, FileAttributes.ReadOnly);
+            File.SetAttributes(destination, FileAttributes.ReadOnly);
+
+            Subject.MoveFile(source, destination);
+        }
+
+
 
 
         [Test]
@@ -139,16 +155,60 @@ namespace NzbDrone.Common.Test.DiskProviderTests
         [Test]
         public void folder_should_return_correct_value_for_last_write()
         {
-            var testFile = Path.Combine(SandboxFolder, "newfile.txt");
+            var testFile = GetTestFilePath();
 
             TestLogger.Info("Path is: {0}", testFile);
 
-            Subject.WriteAllText(testFile, "");
+            Subject.WriteAllText(testFile, "Test");
 
             Subject.GetLastFolderWrite(SandboxFolder).Should().BeOnOrAfter(DateTime.UtcNow.AddMinutes(-1));
             Subject.GetLastFolderWrite(SandboxFolder).Should().BeBefore(DateTime.UtcNow);
         }
 
+        [Test]
+        public void should_return_false_for_unlocked_file()
+        {
+            var testFile = GetTestFilePath();
+            Subject.WriteAllText(testFile, new Guid().ToString());
+
+            Subject.IsFileLocked(testFile).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_false_for_unlocked_and_readonly_file()
+        {
+            var testFile = GetTestFilePath();
+            Subject.WriteAllText(testFile, new Guid().ToString());
+
+            File.SetAttributes(testFile, FileAttributes.ReadOnly);
+
+            Subject.IsFileLocked(testFile).Should().BeFalse();
+        }
+
+
+        [Test]
+        public void should_return_true_for_unlocked_file()
+        {
+            var testFile = GetTestFilePath();
+            Subject.WriteAllText(testFile, new Guid().ToString());
+
+            using (var file = File.OpenWrite(testFile))
+            {
+                Subject.IsFileLocked(testFile).Should().BeTrue();
+            }
+        }
+
+
+        [Test]
+        public void should_be_able_to_set_permission_from_parrent()
+        {
+            var testFile = GetTestFilePath();
+            Subject.WriteAllText(testFile, new Guid().ToString());
+
+            Subject.InheritFolderPermissions(testFile);
+        }
+
+
         [Test]
         [Explicit]
         public void check_last_write()
diff --git a/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs b/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs
index 8359ad481..f3ede03eb 100644
--- a/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs
+++ b/NzbDrone.Common.Test/DiskProviderTests/FreeSpaceFixture.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using FluentAssertions;
+using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Test.Common;
 
diff --git a/NzbDrone.Common.Test/EnvironmentProviderTest.cs b/NzbDrone.Common.Test/EnvironmentProviderTest.cs
index 8dfaf87b5..588fb06ad 100644
--- a/NzbDrone.Common.Test/EnvironmentProviderTest.cs
+++ b/NzbDrone.Common.Test/EnvironmentProviderTest.cs
@@ -29,7 +29,7 @@ namespace NzbDrone.Common.Test
         [Test]
         public void IsProduction_should_return_false_when_run_within_nunit()
         {
-            RuntimeInfo.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName);
+            RuntimeInfo.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName + " Folder is " + Directory.GetCurrentDirectory());
         }
 
         [Test]
diff --git a/NzbDrone.Common.Test/EventingTests/MessageAggregatorEventTests.cs b/NzbDrone.Common.Test/MessagingTests/MessageAggregatorEventTests.cs
similarity index 94%
rename from NzbDrone.Common.Test/EventingTests/MessageAggregatorEventTests.cs
rename to NzbDrone.Common.Test/MessagingTests/MessageAggregatorEventTests.cs
index b41f81124..30a177c81 100644
--- a/NzbDrone.Common.Test/EventingTests/MessageAggregatorEventTests.cs
+++ b/NzbDrone.Common.Test/MessagingTests/MessageAggregatorEventTests.cs
@@ -1,16 +1,18 @@
 using System;
 using System.Collections.Generic;
 using System.Threading;
+using FluentAssertions;
 using Moq;
 using NUnit.Framework;
 using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Test.Common;
-using FluentAssertions;
 
-namespace NzbDrone.Common.Test.EventingTests
+namespace NzbDrone.Common.Test.MessagingTests
 {
     [TestFixture]
-    public class MessageAggregatorEventTests : TestBase<MessageAggregator>
+    public class MessageAggregatorEventTests : TestBase<EventAggregator>
     {
         private Mock<IHandle<EventA>> HandlerA1;
         private Mock<IHandle<EventA>> HandlerA2;
@@ -127,7 +129,7 @@ namespace NzbDrone.Common.Test.EventingTests
 
             counter.WaitForAllItems();
 
-            counter.MaxThreads.Should().Be(2);
+            counter.MaxThreads.Should().Be(3);
         }
     }
 
diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
index 4c22a7aeb..2347a37bf 100644
--- a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
+++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
@@ -67,8 +67,7 @@
     <Compile Include="EnsureTest\PathExtensionFixture.cs" />
     <Compile Include="EnvironmentTests\StartupArgumentsFixture.cs" />
     <Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
-    <Compile Include="EventingTests\MessageAggregatorCommandTests.cs" />
-    <Compile Include="EventingTests\MessageAggregatorEventTests.cs" />
+    <Compile Include="MessagingTests\MessageAggregatorEventTests.cs" />
     <Compile Include="ReflectionExtensions.cs" />
     <Compile Include="PathExtensionFixture.cs" />
     <Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.ncrunchproject b/NzbDrone.Common.Test/NzbDrone.Common.Test.ncrunchproject
index 12a3cf5f9..21e25ddd5 100644
--- a/NzbDrone.Common.Test/NzbDrone.Common.Test.ncrunchproject
+++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.ncrunchproject
@@ -2,7 +2,7 @@
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
   <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,11 +12,11 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration></UseBuildConfiguration>
-  <UseBuildPlatform></UseBuildPlatform>
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
   <ProxyProcessPath></ProxyProcessPath>
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>
   <IgnoredTests>
@@ -27,10 +27,34 @@
       <RegularExpression>NzbDrone\.Common\.Test\.EventingTests\.ServiceNameFixture\..*</RegularExpression>
     </RegexTestSelector>
     <RegexTestSelector>
-      <RegularExpression>NzbDrone\.Common\.Test\.ProcessProviderTests\..*</RegularExpression>
+      <RegularExpression>NzbDrone\.Common\.Test\.ServiceFactoryFixture\..*</RegularExpression>
+    </RegexTestSelector>
+    <NamedTestSelector>
+      <TestName>NzbDrone.Common.Test.ProcessProviderTests.ToString_on_new_processInfo</TestName>
+    </NamedTestSelector>
+    <NamedTestSelector>
+      <TestName>NzbDrone.Common.Test.ProcessProviderTests.Should_be_able_to_start_process</TestName>
+    </NamedTestSelector>
+    <NamedTestSelector>
+      <TestName>NzbDrone.Common.Test.ProcessProviderTests.kill_all_should_kill_all_process_with_name</TestName>
+    </NamedTestSelector>
+    <NamedTestSelector>
+      <TestName>NzbDrone.Common.Test.ProcessProviderTests.GetProcessById_should_return_null_for_invalid_process(9999)</TestName>
+    </NamedTestSelector>
+    <NamedTestSelector>
+      <TestName>NzbDrone.Common.Test.ProcessProviderTests.GetProcessById_should_return_null_for_invalid_process(-1)</TestName>
+    </NamedTestSelector>
+    <NamedTestSelector>
+      <TestName>NzbDrone.Common.Test.ProcessProviderTests.GetProcessById_should_return_null_for_invalid_process(0)</TestName>
+    </NamedTestSelector>
+    <RegexTestSelector>
+      <RegularExpression>NzbDrone\.Common\.Test\.ServiceProviderTests\..*</RegularExpression>
     </RegexTestSelector>
+    <NamedTestSelector>
+      <TestName>NzbDrone.Common.Test.DiskProviderTests.DiskProviderFixture.folder_should_return_correct_value_for_last_write</TestName>
+    </NamedTestSelector>
     <RegexTestSelector>
-      <RegularExpression>NzbDrone\.Common\.Test\.ServiceFactoryFixture\..*</RegularExpression>
+      <RegularExpression>NzbDrone\.Common\.Test\.DiskProviderTests\.DiskProviderFixture\..*</RegularExpression>
     </RegexTestSelector>
   </IgnoredTests>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.Common.Test/PathExtensionFixture.cs b/NzbDrone.Common.Test/PathExtensionFixture.cs
index 907d13d4c..fbc2fb6b6 100644
--- a/NzbDrone.Common.Test/PathExtensionFixture.cs
+++ b/NzbDrone.Common.Test/PathExtensionFixture.cs
@@ -126,17 +126,16 @@ namespace NzbDrone.Common.Test
         }
 
 
-
         [Test]
         public void get_actual_casing_should_return_actual_casing_for_local_dir_in_windows()
         {
             WindowsOnly();
-            var path = Directory.GetCurrentDirectory();
+            var path = Directory.GetCurrentDirectory().Replace("c:\\","C:\\");
+
             path.ToUpper().GetActualCasing().Should().Be(path);
             path.ToLower().GetActualCasing().Should().Be(path);
         }
 
-
         [Test]
         public void get_actual_casing_should_return_original_value_in_linux()
         {
diff --git a/NzbDrone.Common.Test/ProcessProviderTests.cs b/NzbDrone.Common.Test/ProcessProviderTests.cs
index dab923782..4155ab4d0 100644
--- a/NzbDrone.Common.Test/ProcessProviderTests.cs
+++ b/NzbDrone.Common.Test/ProcessProviderTests.cs
@@ -73,7 +73,7 @@ namespace NzbDrone.Common.Test
             var dummy1 = StartDummyProcess();
             var dummy2 = StartDummyProcess();
 
-            Subject.KillAll(dummy1.ProcessName);
+            Subject.KillAll(DummyApp.DUMMY_PROCCESS_NAME);
 
             dummy1.HasExited.Should().BeTrue();
             dummy2.HasExited.Should().BeTrue();
diff --git a/NzbDrone.Common.Test/ServiceFactoryFixture.cs b/NzbDrone.Common.Test/ServiceFactoryFixture.cs
index 4d83d0b3c..7777476ff 100644
--- a/NzbDrone.Common.Test/ServiceFactoryFixture.cs
+++ b/NzbDrone.Common.Test/ServiceFactoryFixture.cs
@@ -2,8 +2,9 @@
 using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Host;
 using NzbDrone.Test.Common;
 
diff --git a/NzbDrone.Common/Cache/CacheManger.cs b/NzbDrone.Common/Cache/CacheManger.cs
index d264eeb40..b5da3047c 100644
--- a/NzbDrone.Common/Cache/CacheManger.cs
+++ b/NzbDrone.Common/Cache/CacheManger.cs
@@ -28,7 +28,6 @@ namespace NzbDrone.Common.Cache
             return GetCache<T>(host, host.FullName);
         }
 
-
         public void Clear()
         {
             _cache.Clear();
diff --git a/NzbDrone.Common/Cache/Cached.cs b/NzbDrone.Common/Cache/Cached.cs
index eef89f317..7269a9a53 100644
--- a/NzbDrone.Common/Cache/Cached.cs
+++ b/NzbDrone.Common/Cache/Cached.cs
@@ -61,6 +61,12 @@ namespace NzbDrone.Common.Cache
             return value.Object;
         }
 
+        public void Remove(string key)
+        {
+            CacheItem value;
+            _store.TryRemove(key, out value);
+        }
+
         public T Get(string key, Func<T> function, TimeSpan? lifeTime = null)
         {
             Ensure.That(() => key).IsNotNullOrWhiteSpace();
@@ -81,7 +87,6 @@ namespace NzbDrone.Common.Cache
             return value;
         }
 
-
         public void Clear()
         {
             _store.Clear();
diff --git a/NzbDrone.Common/Cache/ICached.cs b/NzbDrone.Common/Cache/ICached.cs
index 3708b72af..1c9e50812 100644
--- a/NzbDrone.Common/Cache/ICached.cs
+++ b/NzbDrone.Common/Cache/ICached.cs
@@ -13,6 +13,7 @@ namespace NzbDrone.Common.Cache
         void Set(string key, T value, TimeSpan? lifetime = null);
         T Get(string key, Func<T> function, TimeSpan? lifeTime = null);
         T Find(string key);
+        void Remove(string key);
 
         ICollection<T> Values { get; }
     }
diff --git a/NzbDrone.Common/DiskProvider.cs b/NzbDrone.Common/DiskProvider.cs
index 851ba4550..56180cdda 100644
--- a/NzbDrone.Common/DiskProvider.cs
+++ b/NzbDrone.Common/DiskProvider.cs
@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
@@ -8,12 +7,12 @@ using System.Security.Principal;
 using NLog;
 using NzbDrone.Common.EnsureThat;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Common
 {
     public interface IDiskProvider
     {
-        HashSet<string> SpecialFolders { get; }
         DateTime GetLastFolderWrite(string path);
         DateTime GetLastFileWrite(string path);
         void EnsureFolder(string path);
@@ -24,8 +23,8 @@ namespace NzbDrone.Common
         string[] GetFiles(string path, SearchOption searchOption);
         long GetFolderSize(string path);
         long GetFileSize(string path);
-        String CreateFolder(string path);
-        void CopyFolder(string source, string target);
+        void CreateFolder(string path);
+        void CopyFolder(string source, string destination);
         void MoveFolder(string source, string destination);
         void DeleteFile(string path);
         void MoveFile(string source, string destination);
@@ -36,11 +35,12 @@ namespace NzbDrone.Common
         void WriteAllText(string filename, string contents);
         void FileSetLastWriteTimeUtc(string path, DateTime dateTime);
         void FolderSetLastWriteTimeUtc(string path, DateTime dateTime);
-        bool IsFileLocked(FileInfo file);
+        bool IsFileLocked(string path);
         string GetPathRoot(string path);
         void SetPermissions(string filename, WellKnownSidType accountSid, FileSystemRights rights, AccessControlType controlType);
         bool IsParent(string parentPath, string childPath);
         FileAttributes GetFileAttributes(string path);
+        void EmptyFolder(string path);
     }
 
     public class DiskProvider : IDiskProvider
@@ -58,15 +58,7 @@ namespace NzbDrone.Common
         out ulong lpTotalNumberOfBytes,
         out ulong lpTotalNumberOfFreeBytes);
 
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
-
-        public HashSet<string> SpecialFolders
-        {
-            get
-            {
-                return new HashSet<string> { "$recycle.bin", "system volume information", "recycler" };
-            }
-        }
+        private static readonly Logger Logger = NzbDroneLogger.GetLogger();
 
         public DateTime GetLastFolderWrite(string path)
         {
@@ -93,7 +85,9 @@ namespace NzbDrone.Common
             Ensure.That(() => path).IsValidPath();
 
             if (!FileExists(path))
+            {
                 throw new FileNotFoundException("File doesn't exist: " + path);
+            }
 
             return new FileInfo(path).LastWriteTimeUtc;
         }
@@ -154,25 +148,26 @@ namespace NzbDrone.Common
             Ensure.That(() => path).IsValidPath();
 
             if (!FileExists(path))
+            {
                 throw new FileNotFoundException("File doesn't exist: " + path);
+            }
 
             var fi = new FileInfo(path);
             return fi.Length;
         }
 
-        public String CreateFolder(string path)
+        public void CreateFolder(string path)
         {
             Ensure.That(() => path).IsValidPath();
-
-            return Directory.CreateDirectory(path).FullName;
+            Directory.CreateDirectory(path);
         }
 
-        public void CopyFolder(string source, string target)
+        public void CopyFolder(string source, string destination)
         {
             Ensure.That(() => source).IsValidPath();
-            Ensure.That(() => target).IsValidPath();
+            Ensure.That(() => destination).IsValidPath();
 
-            TransferFolder(source, target, TransferAction.Copy);
+            TransferFolder(source, destination, TransferAction.Copy);
         }
 
         public void MoveFolder(string source, string destination)
@@ -183,7 +178,7 @@ namespace NzbDrone.Common
             try
             {
                 TransferFolder(source, destination, TransferAction.Move);
-                Directory.Delete(source, true);
+                DeleteFolder(source, true);
             }
             catch (Exception e)
             {
@@ -238,8 +233,10 @@ namespace NzbDrone.Common
         public void DeleteFile(string path)
         {
             Ensure.That(() => path).IsValidPath();
-
             Logger.Trace("Deleting file: {0}", path);
+
+            RemoveReadOnly(path);
+
             File.Delete(path);
         }
 
@@ -259,6 +256,7 @@ namespace NzbDrone.Common
                 DeleteFile(destination);
             }
 
+            RemoveReadOnly(source);
             File.Move(source, destination);
         }
 
@@ -273,9 +271,19 @@ namespace NzbDrone.Common
         {
             Ensure.That(() => filename).IsValidPath();
 
-            var fs = File.GetAccessControl(filename);
-            fs.SetAccessRuleProtection(false, false);
-            File.SetAccessControl(filename, fs);
+            try
+            {
+                var fs = File.GetAccessControl(filename);
+                fs.SetAccessRuleProtection(false, false);
+                File.SetAccessControl(filename, fs);
+            }
+            catch (NotImplementedException)
+            {
+                if (!OsInfo.IsLinux)
+                {
+                    throw;
+                }
+            }
         }
 
         public long? GetAvailableSpace(string path)
@@ -347,11 +355,10 @@ namespace NzbDrone.Common
         public void WriteAllText(string filename, string contents)
         {
             Ensure.That(() => filename).IsValidPath();
-
+            RemoveReadOnly(filename);
             File.WriteAllText(filename, contents);
         }
 
-
         public void FileSetLastWriteTimeUtc(string path, DateTime dateTime)
         {
             Ensure.That(() => path).IsValidPath();
@@ -366,26 +373,19 @@ namespace NzbDrone.Common
             Directory.SetLastWriteTimeUtc(path, dateTime);
         }
 
-        public bool IsFileLocked(FileInfo file)
+        public bool IsFileLocked(string file)
         {
-            FileStream stream = null;
-
             try
             {
-                stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
+                using (File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None))
+                {
+                    return false;
+                }
             }
             catch (IOException)
             {
                 return true;
             }
-            finally
-            {
-                if (stream != null)
-                    stream.Close();
-            }
-
-            //file is not locked
-            return false;
         }
 
         public string GetPathRoot(string path)
@@ -441,9 +441,34 @@ namespace NzbDrone.Common
             return false;
         }
 
+
+        private static void RemoveReadOnly(string path)
+        {
+            if (File.Exists(path))
+            {
+                var newAttributes = File.GetAttributes(path) & ~(FileAttributes.ReadOnly);
+                File.SetAttributes(path, newAttributes);
+            }
+        }
+
         public FileAttributes GetFileAttributes(string path)
         {
             return File.GetAttributes(path);
         }
+
+        public void EmptyFolder(string path)
+        {
+            Ensure.That(() => path).IsValidPath();
+
+            foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
+            {
+                DeleteFile(file);
+            }
+
+            foreach (var directory in GetDirectories(path))
+            {
+                DeleteFolder(directory, true);
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs b/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs
index 0cd595464..b3b840491 100644
--- a/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs
+++ b/NzbDrone.Common/EnsureThat/EnsureStringExtensions.cs
@@ -1,5 +1,4 @@
 using System.Diagnostics;
-using System.IO;
 using System.Text.RegularExpressions;
 using NzbDrone.Common.EnsureThat.Resources;
 using NzbDrone.Common.EnvironmentInfo;
diff --git a/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs b/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs
index e93395756..04f7b29ab 100644
--- a/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs
+++ b/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs
@@ -4,6 +4,7 @@ using System.Reflection;
 using System.Security.AccessControl;
 using System.Security.Principal;
 using NLog;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Common.EnvironmentInfo
 {
@@ -30,7 +31,7 @@ namespace NzbDrone.Common.EnvironmentInfo
                 DATA_SPECIAL_FOLDER = Environment.SpecialFolder.ApplicationData;
             }
 
-            _logger = LogManager.GetCurrentClassLogger();
+            _logger =  NzbDroneLogger.GetLogger(this);
 
             if (startupArguments.Args.ContainsKey(StartupArguments.APPDATA))
             {
diff --git a/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs b/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
index 7bd6e8d3c..858db57b0 100644
--- a/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
+++ b/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
@@ -2,7 +2,6 @@
 using System.Diagnostics;
 using System.IO;
 using System.Security.Principal;
-using System.ServiceProcess;
 using NLog;
 
 namespace NzbDrone.Common.EnvironmentInfo
@@ -65,7 +64,9 @@ namespace NzbDrone.Common.EnvironmentInfo
             if (lowerProcessName.Contains("jetbrain")) return false;
             if (lowerProcessName.Contains("resharper")) return false;
 
-            if (Directory.GetCurrentDirectory().ToLower().Contains("teamcity")) return false;
+            string lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
+            if (lowerCurrentDir.Contains("teamcity")) return false;
+            if (lowerCurrentDir.StartsWith("/run/")) return false;
 
             return true;
         }
diff --git a/NzbDrone.Common/EnvironmentInfo/StartupArguments.cs b/NzbDrone.Common/EnvironmentInfo/StartupArguments.cs
index 9075d64e0..51c587bd0 100644
--- a/NzbDrone.Common/EnvironmentInfo/StartupArguments.cs
+++ b/NzbDrone.Common/EnvironmentInfo/StartupArguments.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 
 namespace NzbDrone.Common.EnvironmentInfo
 {
@@ -17,14 +16,6 @@ namespace NzbDrone.Common.EnvironmentInfo
         public const string UNINSTALL_SERVICE = "u";
         public const string HELP = "?";
 
-        static StartupArguments()
-        {
-            if (RuntimeInfo.IsProduction)
-            {
-                Instance = new StartupArguments("");
-            }
-        }
-
         public StartupArguments(params string[] args)
         {
             Flags = new HashSet<string>();
@@ -45,13 +36,9 @@ namespace NzbDrone.Common.EnvironmentInfo
                     Flags.Add(flag);
                 }
             }
-
-            Instance = this;
         }
 
         public HashSet<string> Flags { get; private set; }
         public Dictionary<string, string> Args { get; private set; }
-
-        public static IStartupArguments Instance { get; private set; }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Common/Exceptions/NzbDroneException.cs b/NzbDrone.Common/Exceptions/NzbDroneException.cs
index 09b431fc6..7e0be7312 100644
--- a/NzbDrone.Common/Exceptions/NzbDroneException.cs
+++ b/NzbDrone.Common/Exceptions/NzbDroneException.cs
@@ -2,8 +2,6 @@
 
 namespace NzbDrone.Common.Exceptions
 {
-
-
     public abstract class NzbDroneException : ApplicationException
     {
         protected NzbDroneException(string message, params object[] args)
@@ -17,5 +15,16 @@ namespace NzbDrone.Common.Exceptions
         {
 
         }
+
+        protected NzbDroneException(string message, Exception innerException, params object[] args)
+            : base(string.Format(message, args), innerException)
+        {
+
+        }
+
+        protected NzbDroneException(string message, Exception innerException)
+            : base(message, innerException)
+        {
+        }
     }
 }
diff --git a/NzbDrone.Common/HashUtil.cs b/NzbDrone.Common/HashUtil.cs
index 0a127ee63..abeb9496d 100644
--- a/NzbDrone.Common/HashUtil.cs
+++ b/NzbDrone.Common/HashUtil.cs
@@ -1,5 +1,4 @@
 using System;
-using System.Security.Cryptography;
 using System.Text;
 using System.Threading;
 
@@ -34,31 +33,9 @@ namespace NzbDrone.Common
             return String.Format("{0:x8}", mCrc);
         }
 
-        public static string GenerateUserId()
+        public static string GenerateCommandId()
         {
-            return GenerateId("u");
-        }
-
-        public static string GenerateAppId()
-        {
-            return GenerateId("a");
-        }
-
-        public static string GenerateApiToken()
-        {
-            return Guid.NewGuid().ToString().Replace("-", "");
-        }
-
-        public static string GenerateSecurityToken(int length)
-        {
-            var byteSize = (length / 4) * 3;
-
-            var linkBytes = new byte[byteSize];
-            var rngCrypto = new RNGCryptoServiceProvider();
-            rngCrypto.GetBytes(linkBytes);
-            var base64String = Convert.ToBase64String(linkBytes);
-
-            return base64String;
+            return GenerateId("c");
         }
 
         private static string GenerateId(string prefix)
diff --git a/NzbDrone.Common/Instrumentation/ApplicationLogLayoutRenderer.cs b/NzbDrone.Common/Instrumentation/ApplicationLogLayoutRenderer.cs
deleted file mode 100644
index 2a2082d18..000000000
--- a/NzbDrone.Common/Instrumentation/ApplicationLogLayoutRenderer.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.IO;
-using System.Text;
-using NLog;
-using NLog.Config;
-using NLog.LayoutRenderers;
-using NzbDrone.Common.EnvironmentInfo;
-
-namespace NzbDrone.Common.Instrumentation
-{
-    [ThreadAgnostic]
-    [LayoutRenderer("appLog")]
-    public class ApplicationLogLayoutRenderer : LayoutRenderer
-    {
-        private readonly string _appData;
-
-        public ApplicationLogLayoutRenderer()
-        {
-            _appData = Path.Combine(new AppFolderInfo(new DiskProvider(), StartupArguments.Instance ).GetLogFolder(), "nzbdrone.txt");
-        }
-
-        protected override void Append(StringBuilder builder, LogEventInfo logEvent)
-        {
-            builder.Append(_appData);
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Instrumentation/DirSeparatorLayoutRenderer.cs b/NzbDrone.Common/Instrumentation/DirSeparatorLayoutRenderer.cs
deleted file mode 100644
index bb5658326..000000000
--- a/NzbDrone.Common/Instrumentation/DirSeparatorLayoutRenderer.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.IO;
-using System.Text;
-using NLog;
-using NLog.Config;
-using NLog.LayoutRenderers;
-
-namespace NzbDrone.Common.Instrumentation
-{
-    [ThreadAgnostic]
-    [LayoutRenderer("dirSeparator")]
-    public class DirSeparatorLayoutRenderer : LayoutRenderer
-    {
-
-        protected override void Append(StringBuilder builder, LogEventInfo logEvent)
-        {
-            builder.Append(Path.DirectorySeparatorChar);
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Instrumentation/ExceptronTarget.cs b/NzbDrone.Common/Instrumentation/ExceptronTarget.cs
index 59ff5e918..f824a5ac4 100644
--- a/NzbDrone.Common/Instrumentation/ExceptronTarget.cs
+++ b/NzbDrone.Common/Instrumentation/ExceptronTarget.cs
@@ -4,7 +4,6 @@ using Exceptron.Client;
 using Exceptron.Client.Configuration;
 using NLog;
 using NLog.Common;
-using NLog.Config;
 using NLog.Layouts;
 using NLog.Targets;
 using NzbDrone.Common.EnvironmentInfo;
@@ -23,19 +22,6 @@ namespace NzbDrone.Common.Instrumentation
         /// </summary>
         public IExceptronClient ExceptronClient { get; internal set; }
 
-
-        private static ExceptronTarget _instance = new ExceptronTarget();
-
-        public static void Register()
-        {
-            var rule = new LoggingRule("*", LogLevel.Warn, _instance);
-
-            LogManager.Configuration.AddTarget("ExceptronTarget", _instance);
-            LogManager.Configuration.LoggingRules.Add(rule);
-            LogManager.ConfigurationReloaded += (sender, args) => Register();
-            LogManager.ReconfigExistingLoggers();
-        }
-
         protected override void InitializeTarget()
         {
             var config = new ExceptronConfiguration
diff --git a/NzbDrone.Common/Instrumentation/GlobalExceptionHandlers.cs b/NzbDrone.Common/Instrumentation/GlobalExceptionHandlers.cs
index 24fb64c61..ed32997eb 100644
--- a/NzbDrone.Common/Instrumentation/GlobalExceptionHandlers.cs
+++ b/NzbDrone.Common/Instrumentation/GlobalExceptionHandlers.cs
@@ -6,12 +6,9 @@ namespace NzbDrone.Common.Instrumentation
 {
     public static class GlobalExceptionHandlers
     {
-        private static readonly Logger Logger = LogManager.GetLogger("Global");
-
+        private static readonly Logger Logger = NzbDroneLogger.GetLogger();
         public static void Register()
         {
-            ExceptronTarget.Register();
-
             AppDomain.CurrentDomain.UnhandledException += ((s, e) => AppDomainException(e.ExceptionObject as Exception));
             TaskScheduler.UnobservedTaskException += ((s, e) => TaskException(e.Exception));
         }
@@ -24,6 +21,15 @@ namespace NzbDrone.Common.Instrumentation
 
         private static void AppDomainException(Exception exception)
         {
+            if (exception == null) return;
+
+            if (exception is NullReferenceException &&
+                exception.ToString().Contains("Microsoft.AspNet.SignalR.Transports.TransportHeartbeat.ProcessServerCommand"))
+            {
+                Logger.Warn("SignalR Heartbeat error.");
+                return;
+            }
+
             Console.WriteLine("EPIC FAIL: {0}", exception);
             Logger.FatalException("EPIC FAIL: " + exception.Message, exception);
         }
diff --git a/NzbDrone.Common/Instrumentation/LogEventExtensions.cs b/NzbDrone.Common/Instrumentation/LogEventExtensions.cs
index cb2fe44f2..373aa9201 100644
--- a/NzbDrone.Common/Instrumentation/LogEventExtensions.cs
+++ b/NzbDrone.Common/Instrumentation/LogEventExtensions.cs
@@ -13,7 +13,6 @@ namespace NzbDrone.Common.Instrumentation
             return HashUtil.CalculateCrc(hashSeed);
         }
 
-
         public static string GetFormattedMessage(this LogEventInfo logEvent)
         {
             var message = logEvent.FormattedMessage;
diff --git a/NzbDrone.Common/Instrumentation/LogTargets.cs b/NzbDrone.Common/Instrumentation/LogTargets.cs
new file mode 100644
index 000000000..d63520e80
--- /dev/null
+++ b/NzbDrone.Common/Instrumentation/LogTargets.cs
@@ -0,0 +1,127 @@
+using System;
+using System.IO;
+using NLog;
+using NLog.Config;
+using NLog.Targets;
+using NzbDrone.Common.EnvironmentInfo;
+
+namespace NzbDrone.Common.Instrumentation
+{
+    public static class LogTargets
+    {
+        public static void Register(IStartupArguments startupArguments, bool updateApp, bool inConsole)
+        {
+            var appFolderInfo = new AppFolderInfo(new DiskProvider(), startupArguments);
+
+            LogManager.Configuration = new LoggingConfiguration();
+
+            RegisterExceptron();
+
+            if (updateApp)
+            {
+                RegisterLoggly();
+                RegisterUpdateFile(appFolderInfo);
+            }
+            else
+            {
+                if (inConsole && (OsInfo.IsLinux || new RuntimeInfo(null).IsUserInteractive))
+                {
+                    RegisterConsole();
+                }
+
+                RegisterAppFile(appFolderInfo);
+            }
+
+            LogManager.ReconfigExistingLoggers();
+        }
+
+        private static void RegisterConsole()
+        {
+            var level = LogLevel.Trace;
+
+            if (RuntimeInfo.IsProduction)
+            {
+                level = LogLevel.Info;
+            }
+
+            var coloredConsoleTarget = new ColoredConsoleTarget();
+
+            coloredConsoleTarget.Name = "consoleLogger";
+            coloredConsoleTarget.Layout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}";
+
+            var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
+
+            LogManager.Configuration.AddTarget("console", coloredConsoleTarget);
+            LogManager.Configuration.LoggingRules.Add(loggingRule);
+        }
+
+
+        const string FileLogLayout = @"${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}";
+
+        private static void RegisterAppFile(IAppFolderInfo appFolderInfo)
+        {
+            var fileTarget = new FileTarget();
+
+            fileTarget.Name = "rollingFileLogger";
+            fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), "nzbdrone.txt");
+            fileTarget.AutoFlush = true;
+            fileTarget.KeepFileOpen = false;
+            fileTarget.ConcurrentWrites = false;
+            fileTarget.ConcurrentWriteAttemptDelay = 50;
+            fileTarget.ConcurrentWriteAttempts = 10;
+            fileTarget.ArchiveAboveSize = 1024000;
+            fileTarget.MaxArchiveFiles = 5;
+            fileTarget.EnableFileDelete = true;
+            fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
+            fileTarget.Layout = FileLogLayout;
+
+            var loggingRule = new LoggingRule("*", LogLevel.Info, fileTarget);
+
+            LogManager.Configuration.AddTarget("appfile", fileTarget);
+            LogManager.Configuration.LoggingRules.Add(loggingRule);
+        }
+
+
+
+        private static void RegisterUpdateFile(IAppFolderInfo appFolderInfo)
+        {
+            var fileTarget = new FileTarget();
+
+            fileTarget.Name = "updateFileLogger";
+            fileTarget.FileName = Path.Combine(appFolderInfo.GetUpdateLogFolder(), DateTime.Now.ToString("yy.MM.d-HH.mm") + ".txt");
+            fileTarget.AutoFlush = true;
+            fileTarget.KeepFileOpen = false;
+            fileTarget.ConcurrentWrites = false;
+            fileTarget.ConcurrentWriteAttemptDelay = 50;
+            fileTarget.ConcurrentWriteAttempts = 100;
+            fileTarget.Layout = FileLogLayout;
+
+            var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget);
+
+            LogManager.Configuration.AddTarget("updateFile", fileTarget);
+            LogManager.Configuration.LoggingRules.Add(loggingRule);
+        }
+
+        private static void RegisterExceptron()
+        {
+
+            var exceptronTarget = new ExceptronTarget();
+            var rule = new LoggingRule("*", LogLevel.Warn, exceptronTarget);
+
+            LogManager.Configuration.AddTarget("ExceptronTarget", exceptronTarget);
+            LogManager.Configuration.LoggingRules.Add(rule);
+        }
+
+
+        private static void RegisterLoggly()
+        {
+            var logglyTarger = new LogglyTarget();
+
+            var rule = new LoggingRule("*", LogLevel.Trace, logglyTarger);
+
+            LogManager.Configuration.AddTarget("LogglyLogger", logglyTarger);
+            LogManager.Configuration.LoggingRules.Add(rule);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Common/Instrumentation/LogglyTarget.cs b/NzbDrone.Common/Instrumentation/LogglyTarget.cs
index 33d953390..8f2817581 100644
--- a/NzbDrone.Common/Instrumentation/LogglyTarget.cs
+++ b/NzbDrone.Common/Instrumentation/LogglyTarget.cs
@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using NLog;
-using NLog.Config;
 using NLog.Layouts;
 using NLog.Targets;
 using NzbDrone.Common.EnvironmentInfo;
@@ -13,16 +12,10 @@ namespace NzbDrone.Common.Instrumentation
     {
         private Logger _logger;
 
-        public void Register(LogLevel minLevel)
+        public LogglyTarget()
         {
             Layout = new SimpleLayout("${callsite:className=false:fileName=false:includeSourcePath=false:methodName=true}");
-
-            var rule = new LoggingRule("*", minLevel, this);
-
-            LogManager.Configuration.AddTarget("LogglyLogger", this);
-            LogManager.Configuration.LoggingRules.Add(rule);
-            LogManager.ConfigurationReloaded += (sender, args) => Register(minLevel);
-            LogManager.ReconfigExistingLoggers();
+            
         }
 
         protected override void InitializeTarget()
diff --git a/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
new file mode 100644
index 000000000..c722d5160
--- /dev/null
+++ b/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Diagnostics;
+using NLog;
+
+namespace NzbDrone.Common.Instrumentation
+{
+    public static class NzbDroneLogger
+    {
+        public static Logger GetLogger()
+        {
+            string loggerName;
+            Type declaringType;
+            int framesToSkip = 1;
+            do
+            {
+                var frame = new StackFrame(framesToSkip, false);
+                var method = frame.GetMethod();
+                declaringType = method.DeclaringType;
+                if (declaringType == null)
+                {
+                    loggerName = method.Name;
+                    break;
+                }
+
+                framesToSkip++;
+                loggerName = declaringType.Name;
+            } while (declaringType.Module.Name.Equals("mscorlib.dll", StringComparison.OrdinalIgnoreCase));
+
+            return LogManager.GetLogger(loggerName);
+        }
+
+        public static Logger GetLogger(object obj)
+        {
+            return LogManager.GetLogger(obj.GetType().Name);
+        }
+
+        public static Logger GetLogger<T>()
+        {
+            return LogManager.GetLogger(typeof(T).Name);
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Common/Instrumentation/UpdateLogLayoutRenderer.cs b/NzbDrone.Common/Instrumentation/UpdateLogLayoutRenderer.cs
deleted file mode 100644
index 2f55d36de..000000000
--- a/NzbDrone.Common/Instrumentation/UpdateLogLayoutRenderer.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using System.IO;
-using System.Text;
-using NLog;
-using NLog.Config;
-using NLog.LayoutRenderers;
-using NzbDrone.Common.EnvironmentInfo;
-
-namespace NzbDrone.Common.Instrumentation
-{
-    [ThreadAgnostic]
-    [LayoutRenderer("updateLog")]
-    public class UpdateLogLayoutRenderer : LayoutRenderer
-    {
-        private readonly string _appData;
-
-        public UpdateLogLayoutRenderer()
-        {
-            _appData = Path.Combine(new AppFolderInfo(new DiskProvider(), StartupArguments.Instance).GetUpdateLogFolder(), DateTime.Now.ToString("yy.MM.d-HH.mm") + ".txt");
-
-        }
-
-        protected override void Append(StringBuilder builder, LogEventInfo logEvent)
-        {
-            builder.Append(_appData);
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/CommandCompletedEvent.cs b/NzbDrone.Common/Messaging/CommandCompletedEvent.cs
deleted file mode 100644
index 613800ae0..000000000
--- a/NzbDrone.Common/Messaging/CommandCompletedEvent.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace NzbDrone.Common.Messaging
-{
-    public class CommandCompletedEvent : IEvent
-    {
-        public ICommand Command { get; private set; }
-
-        public CommandCompletedEvent(ICommand command)
-        {
-            Command = command;
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/CommandFailedEvent.cs b/NzbDrone.Common/Messaging/CommandFailedEvent.cs
deleted file mode 100644
index d33ab79f8..000000000
--- a/NzbDrone.Common/Messaging/CommandFailedEvent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-
-namespace NzbDrone.Common.Messaging
-{
-    public class CommandFailedEvent : IEvent
-    {
-        public ICommand Command { get; private set; }
-        public Exception Exception { get; private set; }
-
-        public CommandFailedEvent(ICommand command, Exception exception)
-        {
-            Command = command;
-            Exception = exception;
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/CommandStartedEvent.cs b/NzbDrone.Common/Messaging/CommandStartedEvent.cs
deleted file mode 100644
index 3cb4e7f55..000000000
--- a/NzbDrone.Common/Messaging/CommandStartedEvent.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace NzbDrone.Common.Messaging
-{
-    public class CommandExecutedEvent : IEvent
-    {
-        public ICommand Command { get; private set; }
-
-        public CommandExecutedEvent(ICommand command)
-        {
-            Command = command;
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/ICommand.cs b/NzbDrone.Common/Messaging/ICommand.cs
deleted file mode 100644
index d9f8049ba..000000000
--- a/NzbDrone.Common/Messaging/ICommand.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace NzbDrone.Common.Messaging
-{
-    public interface ICommand : IMessage
-    {
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/IEvent.cs b/NzbDrone.Common/Messaging/IEvent.cs
index 953709c13..00f40b449 100644
--- a/NzbDrone.Common/Messaging/IEvent.cs
+++ b/NzbDrone.Common/Messaging/IEvent.cs
@@ -1,4 +1,4 @@
-namespace NzbDrone.Common.Messaging
+namespace NzbDrone.Common.Messaging
 {
     public interface IEvent : IMessage
     {
diff --git a/NzbDrone.Common/Messaging/IMessageAggregator.cs b/NzbDrone.Common/Messaging/IMessageAggregator.cs
deleted file mode 100644
index 6de5ac3c8..000000000
--- a/NzbDrone.Common/Messaging/IMessageAggregator.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace NzbDrone.Common.Messaging
-{
-    /// <summary>
-    ///   Enables loosely-coupled publication of events.
-    /// </summary>
-    public interface IMessageAggregator
-    {
-        void PublishEvent<TEvent>(TEvent @event) where TEvent : class,  IEvent;
-        void PublishCommand<TCommand>(TCommand command) where TCommand : class, ICommand;
-        void PublishCommand(string commandType);
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/MessageExtensions.cs b/NzbDrone.Common/Messaging/MessageExtensions.cs
deleted file mode 100644
index 302aad869..000000000
--- a/NzbDrone.Common/Messaging/MessageExtensions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace NzbDrone.Common.Messaging
-{
-    public static class MessageExtensions
-    {
-        public static string GetExecutorName(this Type commandType)
-        {
-            if (!typeof(ICommand).IsAssignableFrom(commandType))
-            {
-                throw new ArgumentException("commandType must implement ICommand");
-            }
-
-            return string.Format("I{0}Executor", commandType.Name);
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/TestCommand.cs b/NzbDrone.Common/Messaging/TestCommand.cs
deleted file mode 100644
index 1a54d5764..000000000
--- a/NzbDrone.Common/Messaging/TestCommand.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace NzbDrone.Common.Messaging
-{
-    public class TestCommand : ICommand
-    {
-        public TestCommand()
-        {
-            Duration = 4000;
-        }
-
-        public int Duration { get; set; }
-
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/TestCommandExecutor.cs b/NzbDrone.Common/Messaging/TestCommandExecutor.cs
deleted file mode 100644
index be0ea4ea4..000000000
--- a/NzbDrone.Common/Messaging/TestCommandExecutor.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Threading;
-
-namespace NzbDrone.Common.Messaging
-{
-    public class TestCommandExecutor : IExecute<TestCommand>
-    {
-        public void Execute(TestCommand message)
-        {
-            Thread.Sleep(message.Duration);
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj
index c1b17236f..d55670a61 100644
--- a/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/NzbDrone.Common/NzbDrone.Common.csproj
@@ -41,7 +41,7 @@
     </Reference>
     <Reference Include="Loggly, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\loggly-csharp.2.2\lib\Loggly.dll</HintPath>
+      <HintPath>..\packages\loggly-csharp.2.3\lib\net35\Loggly.dll</HintPath>
     </Reference>
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
@@ -92,6 +92,11 @@
     <Compile Include="IEnumerableExtensions.cs" />
     <Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
     <Compile Include="Instrumentation\ExceptronTarget.cs" />
+    <Compile Include="Instrumentation\LogEventExtensions.cs" />
+    <Compile Include="Instrumentation\NzbDroneLogger.cs" />
+    <Compile Include="Instrumentation\LogTargets.cs" />
+    <Compile Include="Messaging\IEvent.cs" />
+    <Compile Include="Messaging\IMessage.cs" />
     <Compile Include="PathEqualityComparer.cs" />
     <Compile Include="Services.cs" />
     <Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
@@ -99,23 +104,8 @@
     <Compile Include="StringExtensions.cs" />
     <Compile Include="EnsureThat\TypeParam.cs" />
     <Compile Include="HashUtil.cs" />
-    <Compile Include="Instrumentation\ApplicationLogLayoutRenderer.cs" />
-    <Compile Include="Instrumentation\DirSeparatorLayoutRenderer.cs" />
-    <Compile Include="Instrumentation\LogEventExtensions.cs" />
     <Compile Include="Instrumentation\LogglyTarget.cs" />
-    <Compile Include="Instrumentation\UpdateLogLayoutRenderer.cs" />
     <Compile Include="Serializer\Json.cs" />
-    <Compile Include="Messaging\CommandCompletedEvent.cs" />
-    <Compile Include="Messaging\CommandStartedEvent.cs" />
-    <Compile Include="Messaging\CommandFailedEvent.cs" />
-    <Compile Include="Messaging\IExecute.cs" />
-    <Compile Include="Messaging\ICommand.cs" />
-    <Compile Include="Messaging\IMessage.cs" />
-    <Compile Include="Messaging\IProcessMessage.cs" />
-    <Compile Include="Messaging\MessageAggregator.cs" />
-    <Compile Include="Messaging\IEvent.cs" />
-    <Compile Include="Messaging\IMessageAggregator.cs" />
-    <Compile Include="Messaging\IHandle.cs" />
     <Compile Include="Expansive\CircularReferenceException.cs" />
     <Compile Include="Expansive\Expansive.cs" />
     <Compile Include="Expansive\PatternStyle.cs" />
@@ -123,9 +113,6 @@
     <Compile Include="Expansive\TreeNode.cs" />
     <Compile Include="Expansive\TreeNodeList.cs" />
     <Compile Include="Instrumentation\VersionLayoutRenderer.cs" />
-    <Compile Include="Messaging\MessageExtensions.cs" />
-    <Compile Include="Messaging\TestCommand.cs" />
-    <Compile Include="Messaging\TestCommandExecutor.cs" />
     <Compile Include="Reflection\ReflectionExtensions.cs" />
     <Compile Include="ServiceFactory.cs" />
     <Compile Include="HttpProvider.cs" />
diff --git a/NzbDrone.Common/NzbDrone.Common.ncrunchproject b/NzbDrone.Common/NzbDrone.Common.ncrunchproject
index 8641d3614..1a2228e7f 100644
--- a/NzbDrone.Common/NzbDrone.Common.ncrunchproject
+++ b/NzbDrone.Common/NzbDrone.Common.ncrunchproject
@@ -1,8 +1,8 @@
 <ProjectConfiguration>
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
-  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,8 +12,11 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration />
-  <ProxyProcessPath />
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.Common/PathEqualityComparer.cs b/NzbDrone.Common/PathEqualityComparer.cs
index 2bf854727..32d6bf07c 100644
--- a/NzbDrone.Common/PathEqualityComparer.cs
+++ b/NzbDrone.Common/PathEqualityComparer.cs
@@ -1,7 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Linq;
-using System.Text;
 using NzbDrone.Common.EnvironmentInfo;
 
 namespace NzbDrone.Common
diff --git a/NzbDrone.Common/ProcessProvider.cs b/NzbDrone.Common/ProcessProvider.cs
index 8df4198e8..e93edd781 100644
--- a/NzbDrone.Common/ProcessProvider.cs
+++ b/NzbDrone.Common/ProcessProvider.cs
@@ -6,6 +6,7 @@ using System.IO;
 using System.Linq;
 using NLog;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Common.Model;
 
 namespace NzbDrone.Common
@@ -26,7 +27,7 @@ namespace NzbDrone.Common
 
     public class ProcessProvider : IProcessProvider
     {
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger = NzbDroneLogger.GetLogger();
 
         public const string NZB_DRONE_PROCESS_NAME = "NzbDrone";
         public const string NZB_DRONE_CONSOLE_PROCESS_NAME = "NzbDrone.Console";
@@ -212,7 +213,7 @@ namespace NzbDrone.Common
                 return new ProcessInfo
                 {
                     Id = process.Id,
-                    StartPath = process.MainModule.FileName,
+                    StartPath = GetExeFileName(process),
                     Name = process.ProcessName
                 };
             }
@@ -224,6 +225,17 @@ namespace NzbDrone.Common
             return null;
         }
 
+
+        private static string GetExeFileName(Process process)
+        {
+            if (process.MainModule.FileName != "mono.exe")
+            {
+                return process.MainModule.FileName;
+            }
+
+            return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
+        }
+
         private void Kill(int processId)
         {
             var process = Process.GetProcesses().FirstOrDefault(p => p.Id == processId);
diff --git a/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs b/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs
index 1be964380..809b0fe74 100644
--- a/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs
+++ b/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs
@@ -3,12 +3,13 @@ using System.Net.Security;
 using System.Security.Cryptography.X509Certificates;
 using NLog;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Common.Security
 {
     public static class IgnoreCertErrorPolicy
     {
-        private static readonly Logger Logger = LogManager.GetLogger("CertPolicy");
+        private static readonly Logger Logger = NzbDroneLogger.GetLogger();
 
         public static void Register()
         {
diff --git a/NzbDrone.Common/ServiceProvider.cs b/NzbDrone.Common/ServiceProvider.cs
index 7287e36df..8c2206a63 100644
--- a/NzbDrone.Common/ServiceProvider.cs
+++ b/NzbDrone.Common/ServiceProvider.cs
@@ -5,6 +5,7 @@ using System.Diagnostics;
 using System.Linq;
 using System.ServiceProcess;
 using NLog;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Common
 {
@@ -25,7 +26,7 @@ namespace NzbDrone.Common
     {
         public const string NZBDRONE_SERVICE_NAME = "NzbDrone";
 
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
 
         public virtual bool ServiceExist(string name)
         {
diff --git a/NzbDrone.Common/TPL/TaskExtensions.cs b/NzbDrone.Common/TPL/TaskExtensions.cs
index e655ac86f..855ae225c 100644
--- a/NzbDrone.Common/TPL/TaskExtensions.cs
+++ b/NzbDrone.Common/TPL/TaskExtensions.cs
@@ -1,11 +1,12 @@
 using System.Threading.Tasks;
 using NLog;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Common.TPL
 {
     public static class TaskExtensions
     {
-        private static readonly Logger Logger = LogManager.GetLogger("TaskExtensions");
+        private static readonly Logger Logger = NzbDroneLogger.GetLogger();
 
         public static Task LogExceptions(this Task task)
         {
diff --git a/NzbDrone.Common/TinyIoC.cs b/NzbDrone.Common/TinyIoC.cs
index 90fff373f..70ba36762 100644
--- a/NzbDrone.Common/TinyIoC.cs
+++ b/NzbDrone.Common/TinyIoC.cs
@@ -70,7 +70,6 @@ namespace TinyIoC
 {
     using System;
     using System.Collections.Generic;
-    using System.Collections.ObjectModel;
     using System.Linq;
     using System.Reflection;
 
diff --git a/NzbDrone.Common/packages.config b/NzbDrone.Common/packages.config
index c1ce89767..7252d18f7 100644
--- a/NzbDrone.Common/packages.config
+++ b/NzbDrone.Common/packages.config
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="loggly-csharp" version="2.2" targetFramework="net40" />
+  <package id="loggly-csharp" version="2.3" targetFramework="net40" />
   <package id="Newtonsoft.Json" version="5.0.6" targetFramework="net40" />
   <package id="NLog" version="2.0.1.2" targetFramework="net40" />
   <package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
diff --git a/NzbDrone.Console/ConsoleApp.cs b/NzbDrone.Console/ConsoleApp.cs
index 3119cc811..68de86f25 100644
--- a/NzbDrone.Console/ConsoleApp.cs
+++ b/NzbDrone.Console/ConsoleApp.cs
@@ -2,23 +2,29 @@
 using System.Threading;
 using NLog;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Host;
 
 namespace NzbDrone.Console
 {
     public static class ConsoleApp
     {
+        private static readonly Logger Logger = NzbDroneLogger.GetLogger();
+
         public static void Main(string[] args)
         {
             try
             {
-                Bootstrap.Start(new StartupArguments(args), new ConsoleAlerts());
+                var startupArgs = new StartupArguments(args);
+                LogTargets.Register(startupArgs, false, true);
+                Bootstrap.Start(startupArgs, new ConsoleAlerts());
             }
             catch (TerminateApplicationException)
             {
             }
             catch (Exception e)
             {
+                Logger.FatalException("EPIC FAIL!", e);
                 System.Console.ReadLine();
             }
 
@@ -28,4 +34,4 @@ namespace NzbDrone.Console
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs b/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs
index 98ad8b504..587e70697 100644
--- a/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs
+++ b/NzbDrone.Core.Test/DataAugmentationFixture/Scene/SceneMappingProxyFixture.cs
@@ -3,7 +3,6 @@ using FluentAssertions;
 using NUnit.Framework;
 using Newtonsoft.Json;
 using NzbDrone.Common;
-using NzbDrone.Core.Configuration;
 using NzbDrone.Core.DataAugmentation.Scene;
 using NzbDrone.Core.Test.Framework;
 
@@ -13,7 +12,7 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
 
     public class SceneMappingProxyFixture : CoreTest<SceneMappingProxy>
     {
-        private const string SCENE_MAPPING_URL = "http://services.nzbdrone.com/SceneMapping/Active";
+        private const string SCENE_MAPPING_URL = "http://services.nzbdrone.com/v1/SceneMapping";
 
         [Test]
         public void fetch_should_return_list_of_mappings()
@@ -31,7 +30,6 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
             mappings.Should().NotContain(c => c.TvdbId == 0);
         }
 
-
         [Test]
         public void should_throw_on_server_error()
         {
diff --git a/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperFixture.cs b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperFixture.cs
deleted file mode 100644
index c940ac7b2..000000000
--- a/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperFixture.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using FizzWare.NBuilder;
-using FluentAssertions;
-using NUnit.Framework;
-using NzbDrone.Core.Datastore.Migration.Framework;
-using NzbDrone.Core.Test.Framework;
-using NzbDrone.Core.Tv;
-
-namespace NzbDrone.Core.Test.Datastore
-{
-    [TestFixture]
-    public class SQLiteMigrationHelperFixture : DbTest
-    {
-        private SQLiteMigrationHelper _subject;
-
-        [SetUp]
-        public void SetUp()
-        {
-            _subject = Mocker.Resolve<SQLiteMigrationHelper>();
-        }
-
-
-
-        [Test]
-        public void should_parse_existing_columns()
-        {
-            var columns = _subject.GetColumns("Series");
-
-            columns.Should().NotBeEmpty();
-
-            columns.Values.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name));
-            columns.Values.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Schema));
-        }
-
-        [Test]
-        public void should_create_table_from_column_list()
-        {
-            var columns = _subject.GetColumns("Series");
-            columns.Remove("Title");
-
-            _subject.CreateTable("Series_New", columns.Values);
-
-            var newColumns = _subject.GetColumns("Series_New");
-
-            newColumns.Values.Should().HaveSameCount(columns.Values);
-            newColumns.Should().NotContainKey("Title");
-        }
-
-        [Test]
-        public void should_get_zero_count_on_empty_table()
-        {
-            _subject.GetRowCount("Series").Should().Be(0);
-        }
-
-
-        [Test]
-        public void should_be_able_to_transfer_empty_tables()
-        {
-            var columns = _subject.GetColumns("Series");
-            columns.Remove("Title");
-
-            _subject.CreateTable("Series_New", columns.Values);
-
-
-            _subject.CopyData("Series", "Series_New", columns.Values);
-        }
-
-        [Test]
-        public void should_transfer_table_with_data()
-        {
-            var originalEpisodes = Builder<Episode>.CreateListOfSize(10).BuildListOfNew();
-
-            Mocker.Resolve<EpisodeRepository>().InsertMany(originalEpisodes);
-
-            var columns = _subject.GetColumns("Episodes");
-            columns.Remove("Title");
-
-            _subject.CreateTable("Episodes_New", columns.Values);
-
-            _subject.CopyData("Episodes", "Episodes_New", columns.Values);
-
-            _subject.GetRowCount("Episodes_New").Should().Be(originalEpisodes.Count);
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs
new file mode 100644
index 000000000..7e1510259
--- /dev/null
+++ b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs
@@ -0,0 +1,127 @@
+using System.Collections.Generic;
+using FizzWare.NBuilder;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Datastore.Migration.Framework;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Tv;
+using System.Linq;
+
+namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests
+{
+    [TestFixture]
+    public class AlterFixture : DbTest
+    {
+        private SqLiteMigrationHelper _subject;
+
+        [SetUp]
+        public void SetUp()
+        {
+            _subject = Mocker.Resolve<SqLiteMigrationHelper>();
+        }
+
+        [Test]
+        public void should_parse_existing_columns()
+        {
+            var columns = _subject.GetColumns("Series");
+
+            columns.Should().NotBeEmpty();
+
+            columns.Values.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name));
+            columns.Values.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Schema));
+        }
+
+        [Test]
+        public void should_create_table_from_column_list()
+        {
+            var columns = _subject.GetColumns("Series");
+            columns.Remove("Title");
+
+            _subject.CreateTable("Series_New", columns.Values, new List<SQLiteIndex>());
+
+            var newColumns = _subject.GetColumns("Series_New");
+
+            newColumns.Values.Should().HaveSameCount(columns.Values);
+            newColumns.Should().NotContainKey("Title");
+        }
+
+
+        [Test]
+        public void should_be_able_to_transfer_empty_tables()
+        {
+            var columns = _subject.GetColumns("Series");
+            var indexes = _subject.GetIndexes("Series");
+            columns.Remove("Title");
+
+            _subject.CreateTable("Series_New", columns.Values, indexes);
+
+
+            _subject.CopyData("Series", "Series_New", columns.Values);
+        }
+
+        [Test]
+        public void should_transfer_table_with_data()
+        {
+            var originalEpisodes = Builder<Episode>.CreateListOfSize(10).BuildListOfNew();
+
+            Mocker.Resolve<EpisodeRepository>().InsertMany(originalEpisodes);
+
+            var columns = _subject.GetColumns("Episodes");
+            var indexes = _subject.GetIndexes("Episodes");
+
+            columns.Remove("Title");
+
+            _subject.CreateTable("Episodes_New", columns.Values, indexes);
+
+            _subject.CopyData("Episodes", "Episodes_New", columns.Values);
+
+            _subject.GetRowCount("Episodes_New").Should().Be(originalEpisodes.Count);
+        }
+
+        [Test]
+        public void should_read_existing_indexes()
+        {
+            var indexes = _subject.GetIndexes("QualitySizes");
+
+            indexes.Should().NotBeEmpty();
+
+            indexes.Should().OnlyContain(c => c != null);
+            indexes.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.Column));
+            indexes.Should().OnlyContain(c => c.Table == "QualitySizes");
+            indexes.Should().OnlyContain(c => c.Unique);
+        }
+
+        [Test]
+        public void should_add_indexes_when_creating_new_table()
+        {
+            var columns = _subject.GetColumns("QualitySizes");
+            var indexes = _subject.GetIndexes("QualitySizes");
+
+
+            _subject.CreateTable("QualityB", columns.Values, indexes);
+
+
+            var newIndexes = _subject.GetIndexes("QualityB");
+
+            newIndexes.Should().HaveSameCount(indexes);
+            newIndexes.Select(c=>c.Column).Should().BeEquivalentTo(indexes.Select(c=>c.Column));
+        }
+
+
+        [Test]
+        public void should_be_able_to_create_table_with_new_indexes()
+        {
+            var columns = _subject.GetColumns("Series");
+            columns.Remove("Title");
+
+            _subject.CreateTable("Series_New", columns.Values, new List<SQLiteIndex>{new SQLiteIndex{Column = "AirTime", Table = "Series_New", Unique = true}});
+
+            var newColumns = _subject.GetColumns("Series_New");
+            var newIndexes = _subject.GetIndexes("Series_New");
+
+            newColumns.Values.Should().HaveSameCount(columns.Values);
+            newIndexes.Should().Contain(i=>i.Column == "AirTime");
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs
new file mode 100644
index 000000000..d193fb1ec
--- /dev/null
+++ b/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/DuplicateFixture.cs
@@ -0,0 +1,41 @@
+using System.Linq;
+using FizzWare.NBuilder;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Datastore.Migration.Framework;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Tv;
+
+namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests
+{
+    [TestFixture]
+    public class DuplicateFixture : DbTest
+    {
+        private SqLiteMigrationHelper _subject;
+
+        [SetUp]
+        public void SetUp()
+        {
+            _subject = Mocker.Resolve<SqLiteMigrationHelper>();
+        }
+
+
+        [Test]
+        public void get_duplicates()
+        {
+            var series = Builder<Series>.CreateListOfSize(10)
+                                        .Random(3)
+                                        .With(c => c.QualityProfileId = 100)
+                                        .BuildListOfNew();
+
+            Db.InsertMany(series);
+
+            var duplicates = _subject.GetDuplicates<int>("series", "QualityProfileId").ToList();
+
+
+            duplicates.Should().HaveCount(1);
+            duplicates.First().Should().HaveCount(3);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs
index 32528beaf..5f511e3dc 100644
--- a/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs
@@ -26,14 +26,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             parseResultMulti = new RemoteEpisode
                                    {
-                                       Report = new ReportInfo(),
+                                       Release = new ReleaseInfo(),
                                        ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, true) },
                                        Episodes = new List<Episode> { new Episode(), new Episode() }
                                    };
 
             parseResultSingle = new RemoteEpisode
                                     {
-                                        Report = new ReportInfo(),
+                                        Release = new ReleaseInfo(),
                                         ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, true) },
                                         Episodes = new List<Episode> { new Episode() }
 
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_true_single_episode_not_first_or_last_30_minute()
         {
             parseResultSingle.Series = series30minutes;
-            parseResultSingle.Report.Size = 184572800;
+            parseResultSingle.Release.Size = 184572800;
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_true_single_episode_not_first_or_last_60_minute()
         {
             parseResultSingle.Series = series60minutes;
-            parseResultSingle.Report.Size = 368572800;
+            parseResultSingle.Release.Size = 368572800;
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_false_single_episode_not_first_or_last_30_minute()
         {
             parseResultSingle.Series = series30minutes;
-            parseResultSingle.Report.Size = 1.Gigabytes();
+            parseResultSingle.Release.Size = 1.Gigabytes();
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -116,7 +116,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_false_single_episode_not_first_or_last_60_minute()
         {
             parseResultSingle.Series = series60minutes;
-            parseResultSingle.Report.Size = 1.Gigabytes();
+            parseResultSingle.Release.Size = 1.Gigabytes();
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_true_multi_episode_not_first_or_last_30_minute()
         {
             parseResultMulti.Series = series30minutes;
-            parseResultMulti.Report.Size = 184572800;
+            parseResultMulti.Release.Size = 184572800;
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -152,7 +152,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_true_multi_episode_not_first_or_last_60_minute()
         {
             parseResultMulti.Series = series60minutes;
-            parseResultMulti.Report.Size = 368572800;
+            parseResultMulti.Release.Size = 368572800;
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -171,7 +171,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_false_multi_episode_not_first_or_last_30_minute()
         {
             parseResultMulti.Series = series30minutes;
-            parseResultMulti.Report.Size = 1.Gigabytes();
+            parseResultMulti.Release.Size = 1.Gigabytes();
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -190,7 +190,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_false_multi_episode_not_first_or_last_60_minute()
         {
             parseResultMulti.Series = series60minutes;
-            parseResultMulti.Report.Size = 10.Gigabytes();
+            parseResultMulti.Release.Size = 10.Gigabytes();
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -209,7 +209,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_true_single_episode_first_30_minute()
         {
             parseResultSingle.Series = series30minutes;
-            parseResultSingle.Report.Size = 184572800;
+            parseResultSingle.Release.Size = 184572800;
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -228,7 +228,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_true_single_episode_first_60_minute()
         {
             parseResultSingle.Series = series60minutes;
-            parseResultSingle.Report.Size = 368572800;
+            parseResultSingle.Release.Size = 368572800;
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -247,7 +247,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void IsAcceptableSize_false_single_episode_first_30_minute()
         {
             parseResultSingle.Series = series30minutes;
-            parseResultSingle.Report.Size = 1.Gigabytes();
+            parseResultSingle.Release.Size = 1.Gigabytes();
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -268,7 +268,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
 
 
             parseResultSingle.Series = series60minutes;
-            parseResultSingle.Report.Size = 10.Gigabytes();
+            parseResultSingle.Release.Size = 10.Gigabytes();
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
 
@@ -289,7 +289,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
 
 
             parseResultSingle.Series = series30minutes;
-            parseResultSingle.Report.Size = 18457280000;
+            parseResultSingle.Release.Size = 18457280000;
             qualityType.MaxSize = 0;
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
@@ -311,7 +311,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
 
 
             parseResultSingle.Series = series60minutes;
-            parseResultSingle.Report.Size = 36857280000;
+            parseResultSingle.Release.Size = 36857280000;
             qualityType.MaxSize = 0;
 
             Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
@@ -334,7 +334,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series60minutes;
             parseResultSingle.Series.SeriesType = SeriesTypes.Daily;
 
-            parseResultSingle.Report.Size = 300.Megabytes();
+            parseResultSingle.Release.Size = 300.Megabytes();
 
             qualityType.MaxSize = (int)600.Megabytes();
 
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs
new file mode 100644
index 000000000..7ff36a03b
--- /dev/null
+++ b/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs
@@ -0,0 +1,48 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Tv;
+using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.Test.Framework;
+
+namespace NzbDrone.Core.Test.DecisionEngineTests
+{
+    [TestFixture]
+    public class CutoffSpecificationFixture : CoreTest<QualityUpgradableSpecification>
+    {
+        [Test]
+        public void should_return_true_if_current_episode_is_less_than_cutoff()
+        {
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.Bluray1080p },
+                                 new QualityModel(Quality.DVD, true)).Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_false_if_current_episode_is_equal_to_cutoff()
+        {
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p },
+                               new QualityModel(Quality.HDTV720p, true)).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_false_if_current_episode_is_greater_than_cutoff()
+        {
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p },
+                                new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_true_when_new_episode_is_proper_but_existing_is_not()
+        {
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p },
+                                new QualityModel(Quality.HDTV720p, false), new QualityModel(Quality.HDTV720p, true)).Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_false_if_cutoff_is_met_and_quality_is_higher()
+        {
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p },
+                                new QualityModel(Quality.HDTV720p, true), new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse();
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs
index 171387772..d250c4d98 100644
--- a/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs
+++ b/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs
@@ -4,6 +4,7 @@ using FluentAssertions;
 using Moq;
 using NUnit.Framework;
 using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.IndexerSearch.Definitions;
 using NzbDrone.Core.Parser;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Test.Framework;
@@ -15,7 +16,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
     [TestFixture]
     public class DownloadDecisionMakerFixture : CoreTest<DownloadDecisionMaker>
     {
-        private List<ReportInfo> _reports;
+        private List<ReleaseInfo> _reports;
         private RemoteEpisode _remoteEpisode;
 
         private Mock<IDecisionEngineSpecification> _pass1;
@@ -56,10 +57,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(false);
             _fail3.Setup(c => c.RejectionReason).Returns("_fail3");
 
-            _reports = new List<ReportInfo> { new ReportInfo { Title = "The.Office.S03E115.DVDRip.XviD-OSiTV" } };
+            _reports = new List<ReleaseInfo> { new ReleaseInfo { Title = "The.Office.S03E115.DVDRip.XviD-OSiTV" } };
             _remoteEpisode = new RemoteEpisode { Series = new Series() };
 
-            Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>()))
+            Mocker.GetMock<IParsingService>()
+                  .Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()))
                   .Returns(_remoteEpisode);
         }
 
@@ -130,7 +132,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
 
             var results = Subject.GetRssDecision(_reports).ToList();
 
-            Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>()), Times.Never());
+            Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Never());
 
             _pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
             _pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
@@ -146,7 +148,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
 
             var results = Subject.GetRssDecision(_reports).ToList();
 
-            Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>()), Times.Never());
+            Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Never());
 
             _pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
             _pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
@@ -174,19 +176,19 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             GivenSpecifications(_pass1);
 
-            Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>()))
+            Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()))
                      .Throws<TestException>();
 
-            _reports = new List<ReportInfo>
+            _reports = new List<ReleaseInfo>
                 {
-                    new ReportInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"},
-                    new ReportInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"},
-                    new ReportInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"}
+                    new ReleaseInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"},
+                    new ReleaseInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"},
+                    new ReleaseInfo{Title = "The.Office.S03E115.DVDRip.XviD-OSiTV"}
                 };
 
             Subject.GetRssDecision(_reports);
 
-            Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>()), Times.Exactly(_reports.Count));
+            Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Exactly(_reports.Count));
 
             ExceptionVerification.ExpectedErrors(3);
         }
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs
index f9bcf475e..cdbce8077 100644
--- a/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             _parseResult = new RemoteEpisode
                 {
-                    Report = new ReportInfo
+                    Release = new ReleaseInfo
                         {
                             Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR"
                         }
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradableSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradableSpecificationFixture.cs
deleted file mode 100644
index c7eb4bf4b..000000000
--- a/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradableSpecificationFixture.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using FluentAssertions;
-using NUnit.Framework;
-using NzbDrone.Core.Qualities;
-using NzbDrone.Core.Tv;
-using NzbDrone.Core.DecisionEngine;
-using NzbDrone.Core.Test.Framework;
-
-namespace NzbDrone.Core.Test.DecisionEngineTests
-{
-    [TestFixture]
-    public class QualityUpgradableSpecificationFixture : CoreTest<QualityUpgradableSpecification>
-    {
-        [Test]
-        public void IsUpgradePossible_should_return_true_if_current_episode_is_less_than_cutoff()
-        {
-            Subject.IsUpgradable(new QualityProfile { Cutoff = Quality.Bluray1080p },
-                                 new QualityModel(Quality.DVD, true)).Should().BeTrue();
-        }
-
-        [Test]
-        public void IsUpgradePossible_should_return_false_if_current_episode_is_equal_to_cutoff()
-        {
-            Subject.IsUpgradable(new QualityProfile { Cutoff = Quality.HDTV720p },
-                               new QualityModel(Quality.HDTV720p, true)).Should().BeFalse();
-        }
-
-        [Test]
-        public void IsUpgradePossible_should_return_false_if_current_episode_is_greater_than_cutoff()
-        {
-            Subject.IsUpgradable(new QualityProfile { Cutoff = Quality.HDTV720p },
-                                new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse();
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs
index 2a489ee8a..cc631b871 100644
--- a/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs
@@ -17,7 +17,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             new object[] { Quality.SDTV, false, Quality.SDTV, true, Quality.SDTV, true },
             new object[] { Quality.WEBDL720p, false, Quality.WEBDL720p, true, Quality.WEBDL720p, true },
             new object[] { Quality.SDTV, false, Quality.SDTV, false, Quality.SDTV, false },
-            new object[] { Quality.SDTV, false, Quality.DVD, true, Quality.SDTV, false },
             new object[] { Quality.WEBDL720p, false, Quality.HDTV720p, true, Quality.Bluray720p, false },
             new object[] { Quality.WEBDL720p, false, Quality.HDTV720p, true, Quality.WEBDL720p, false },
             new object[] { Quality.WEBDL720p, false, Quality.WEBDL720p, false, Quality.WEBDL720p, false },
@@ -37,7 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             GivenAutoDownloadPropers(true);
 
-            Subject.IsUpgradable(new QualityProfile() { Cutoff = cutoff }, new QualityModel(current, currentProper), new QualityModel(newQuality, newProper))
+            Subject.IsUpgradable(new QualityModel(current, currentProper), new QualityModel(newQuality, newProper))
                     .Should().Be(expected);
         }
 
@@ -46,8 +45,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             GivenAutoDownloadPropers(false);
 
-            Subject.IsUpgradable(new QualityProfile { Cutoff = Quality.Bluray1080p },
-                                 new QualityModel(Quality.DVD, true),
+            Subject.IsUpgradable(new QualityModel(Quality.DVD, true),
                                  new QualityModel(Quality.DVD, false)).Should().BeFalse();
         }
     }
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/RetentionSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/RetentionSpecificationFixture.cs
index 7a61aa04a..45db8a529 100644
--- a/NzbDrone.Core.Test/DecisionEngineTests/RetentionSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/DecisionEngineTests/RetentionSpecificationFixture.cs
@@ -1,4 +1,5 @@
-using FluentAssertions;
+using System;
+using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.DecisionEngine.Specifications;
@@ -19,9 +20,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             parseResult = new RemoteEpisode
             {
-                Report = new ReportInfo
+                Release = new ReleaseInfo
                     {
-                        Age = 100
+                        PublishDate = DateTime.Now.AddDays(-100)
                     }
             };
         }
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs
new file mode 100644
index 000000000..b95925ced
--- /dev/null
+++ b/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using FizzWare.NBuilder;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
+using NzbDrone.Core.IndexerSearch.Definitions;
+using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Tv;
+using NzbDrone.Core.DecisionEngine;
+
+using NzbDrone.Core.Test.Framework;
+
+namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
+{
+    [TestFixture]
+
+    public class ProperSpecificationFixture : CoreTest<ProperSpecification>
+    {
+        private RemoteEpisode _parseResultMulti;
+        private RemoteEpisode _parseResultSingle;
+        private EpisodeFile _firstFile;
+        private EpisodeFile _secondFile;
+
+        [SetUp]
+        public void Setup()
+        {
+            Mocker.Resolve<QualityUpgradableSpecification>();
+
+            _firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, false), DateAdded = DateTime.Now };
+            _secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, false), DateAdded = DateTime.Now };
+
+            var singleEpisodeList = new List<Episode> { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } };
+            var doubleEpisodeList = new List<Episode> { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } };
+
+            var fakeSeries = Builder<Series>.CreateNew()
+                         .With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p })
+                         .Build();
+
+            _parseResultMulti = new RemoteEpisode
+            {
+                Series = fakeSeries,
+                ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) },
+                Episodes = doubleEpisodeList
+            };
+
+            _parseResultSingle = new RemoteEpisode
+            {
+                Series = fakeSeries,
+                ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) },
+                Episodes = singleEpisodeList
+            };
+        }
+
+        private void WithFirstFileUpgradable()
+        {
+            _firstFile.Quality = new QualityModel(Quality.SDTV);
+        }
+
+        private void GivenAutoDownloadPropers()
+        {
+            Mocker.GetMock<IConfigService>()
+                  .Setup(s => s.AutoDownloadPropers)
+                  .Returns(true);
+        }
+
+        [Test]
+        public void should_return_false_when_episodeFile_was_added_more_than_7_days_ago()
+        {
+            _firstFile.Quality.Quality = Quality.DVD;
+
+            _firstFile.DateAdded = DateTime.Today.AddDays(-30);
+            Subject.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_false_when_first_episodeFile_was_added_more_than_7_days_ago()
+        {
+            _firstFile.Quality.Quality = Quality.DVD;
+            _secondFile.Quality.Quality = Quality.DVD;
+
+            _firstFile.DateAdded = DateTime.Today.AddDays(-30);
+            Subject.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_false_when_second_episodeFile_was_added_more_than_7_days_ago()
+        {
+            _firstFile.Quality.Quality = Quality.DVD;
+            _secondFile.Quality.Quality = Quality.DVD;
+
+            _secondFile.DateAdded = DateTime.Today.AddDays(-30);
+            Subject.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_true_when_episodeFile_was_added_more_than_7_days_ago_but_proper_is_for_better_quality()
+        {
+            WithFirstFileUpgradable();
+
+            _firstFile.DateAdded = DateTime.Today.AddDays(-30);
+            Subject.IsSatisfiedBy(_parseResultSingle, null).Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_true_when_episodeFile_was_added_more_than_7_days_ago_but_is_for_search()
+        {
+            WithFirstFileUpgradable();
+
+            _firstFile.DateAdded = DateTime.Today.AddDays(-30);
+            Subject.IsSatisfiedBy(_parseResultSingle, new SingleEpisodeSearchCriteria()).Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_false_when_proper_but_auto_download_propers_is_false()
+        {
+            _firstFile.Quality.Quality = Quality.DVD;
+
+            _firstFile.DateAdded = DateTime.Today;
+            Subject.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_true_when_episodeFile_was_added_today()
+        {
+            GivenAutoDownloadPropers();
+
+            _firstFile.Quality.Quality = Quality.DVD;
+
+            _firstFile.DateAdded = DateTime.Today;
+            Subject.IsSatisfiedBy(_parseResultSingle, null).Should().BeTrue();
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs b/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs
index db083a6b9..eb85c757d 100644
--- a/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs
@@ -4,7 +4,6 @@ using FizzWare.NBuilder;
 using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Core.DecisionEngine.Specifications;
-using NzbDrone.Core.IndexerSearch.Definitions;
 using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Qualities;
@@ -124,52 +123,5 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, false);
             _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse();
         }
-
-        [Test]
-        public void should_return_false_when_episodeFile_was_added_more_than_7_days_ago()
-        {
-            _firstFile.Quality.Quality = Quality.DVD;
-
-            _firstFile.DateAdded = DateTime.Today.AddDays(-30);
-            _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse();
-        }
-
-        [Test]
-        public void should_return_false_when_first_episodeFile_was_added_more_than_7_days_ago()
-        {
-            _firstFile.Quality.Quality = Quality.DVD;
-            _secondFile.Quality.Quality = Quality.DVD;
-
-            _firstFile.DateAdded = DateTime.Today.AddDays(-30);
-            _upgradeDisk.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse();
-        }
-
-        [Test]
-        public void should_return_false_when_second_episodeFile_was_added_more_than_7_days_ago()
-        {
-            _firstFile.Quality.Quality = Quality.DVD;
-            _secondFile.Quality.Quality = Quality.DVD;
-
-            _secondFile.DateAdded = DateTime.Today.AddDays(-30);
-            _upgradeDisk.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse();
-        }
-
-        [Test]
-        public void should_return_true_when_episodeFile_was_added_more_than_7_days_ago_but_proper_is_for_better_quality()
-        {
-            WithFirstFileUpgradable();
-
-            _firstFile.DateAdded = DateTime.Today.AddDays(-30);
-            _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Should().BeTrue();
-        }
-
-        [Test]
-        public void should_return_true_when_episodeFile_was_added_more_than_7_days_ago_but_is_for_search()
-        {
-            WithFirstFileUpgradable();
-
-            _firstFile.DateAdded = DateTime.Today.AddDays(-30);
-            _upgradeDisk.IsSatisfiedBy(_parseResultSingle, new SingleEpisodeSearchCriteria()).Should().BeTrue();
-        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs
index 8c5c5ecb7..f13c81ccb 100644
--- a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs
+++ b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs
@@ -4,7 +4,7 @@ using FizzWare.NBuilder;
 using FluentAssertions;
 using Moq;
 using NUnit.Framework;
-using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.DecisionEngine.Specifications;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Qualities;
@@ -34,8 +34,8 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
             remoteEpisode.Episodes = new List<Episode>();
             remoteEpisode.Episodes.AddRange(episodes);
 
-            remoteEpisode.Report = new ReportInfo();
-            remoteEpisode.Report.Age = 0;
+            remoteEpisode.Release = new ReleaseInfo();
+            remoteEpisode.Release.PublishDate = DateTime.UtcNow;
 
             return remoteEpisode;
         }
diff --git a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs
index d1be02648..370142d75 100644
--- a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs
+++ b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs
@@ -1,9 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Linq;
 using FizzWare.NBuilder;
 using FluentAssertions;
 using NUnit.Framework;
-using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.DecisionEngine.Specifications;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Qualities;
@@ -32,9 +33,9 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
             remoteEpisode.Episodes = new List<Episode>();
             remoteEpisode.Episodes.AddRange(episodes);
 
-            remoteEpisode.Report = new ReportInfo();
-            remoteEpisode.Report.Age = Age;
-            remoteEpisode.Report.Size = size;
+            remoteEpisode.Release = new ReleaseInfo();
+            remoteEpisode.Release.PublishDate = DateTime.Now.AddDays(-Age);
+            remoteEpisode.Release.Size = size;
 
             return remoteEpisode;
         }
@@ -110,9 +111,9 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
         public void should_order_by_smallest_rounded_to_200mb_then_age()
         {
             var remoteEpisodeSd = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.SDTV), size: 100.Megabytes(), Age: 1);
-            var remoteEpisodeHdSmallOld = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size:1200.Megabytes(), Age:1000);
-            var remoteEpisodeHdSmallYounge = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size:1250.Megabytes(), Age:10);
-            var remoteEpisodeHdLargeYounge = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size:3000.Megabytes(), Age:1);
+            var remoteEpisodeHdSmallOld = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 1200.Megabytes(), Age: 1000);
+            var remoteEpisodeHdSmallYounge = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 1250.Megabytes(), Age: 10);
+            var remoteEpisodeHdLargeYounge = GetRemoteEpisode(new List<Episode> { GetEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 3000.Megabytes(), Age: 1);
 
             var decisions = new List<DownloadDecision>();
             decisions.Add(new DownloadDecision(remoteEpisodeSd));
diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/BlackholeProviderFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/BlackholeProviderFixture.cs
index 6229aaf8a..9de3178b9 100644
--- a/NzbDrone.Core.Test/Download/DownloadClientTests/BlackholeProviderFixture.cs
+++ b/NzbDrone.Core.Test/Download/DownloadClientTests/BlackholeProviderFixture.cs
@@ -30,9 +30,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
             Mocker.GetMock<IConfigService>().SetupGet(c => c.BlackholeFolder).Returns(_blackHoleFolder);
 
             _remoteEpisode = new RemoteEpisode();
-            _remoteEpisode.Report = new ReportInfo();
-            _remoteEpisode.Report.Title = _title;
-            _remoteEpisode.Report.NzbUrl = _nzbUrl;
+            _remoteEpisode.Release = new ReleaseInfo();
+            _remoteEpisode.Release.Title = _title;
+            _remoteEpisode.Release.DownloadUrl = _nzbUrl;
         }
 
         private void WithExistingFile()
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
         {
             var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
             var expectedFilename = Path.Combine(_blackHoleFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb");
-            _remoteEpisode.Report.Title = illegalTitle;
+            _remoteEpisode.Release.Title = illegalTitle;
 
             Subject.DownloadNzb(_remoteEpisode);
 
diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs
index 638613108..c897df251 100644
--- a/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs
+++ b/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs
@@ -1,7 +1,6 @@
 using System;
 using System.Linq;
 using FizzWare.NBuilder;
-using FluentAssertions;
 using Moq;
 using NUnit.Framework;
 using NzbDrone.Common;
@@ -31,9 +30,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetProviderTests
             fakeConfig.SetupGet(c => c.NzbgetRecentTvPriority).Returns(PriorityType.High);
 
             _remoteEpisode = new RemoteEpisode();
-            _remoteEpisode.Report = new ReportInfo();
-            _remoteEpisode.Report.Title = _title;
-            _remoteEpisode.Report.NzbUrl = _url;
+            _remoteEpisode.Release = new ReleaseInfo();
+            _remoteEpisode.Release.Title = _title;
+            _remoteEpisode.Release.DownloadUrl = _url;
 
             _remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
                                                       .All()
diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs
index 66b3695cc..cfac3a99a 100644
--- a/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs
+++ b/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs
@@ -34,9 +34,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
             Mocker.GetMock<IConfigService>().SetupGet(c => c.DownloadedEpisodesFolder).Returns(_sabDrop);
 
             _remoteEpisode = new RemoteEpisode();
-            _remoteEpisode.Report = new ReportInfo();
-            _remoteEpisode.Report.Title = _title;
-            _remoteEpisode.Report.NzbUrl = _nzbUrl;
+            _remoteEpisode.Release = new ReleaseInfo();
+            _remoteEpisode.Release.Title = _title;
+            _remoteEpisode.Release.DownloadUrl = _nzbUrl;
 
             _remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo();
             _remoteEpisode.ParsedEpisodeInfo.FullSeason = false;
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
         [Test]
         public void should_throw_if_full_season_download()
         {
-            _remoteEpisode.Report.Title = "30 Rock - Season 1";
+            _remoteEpisode.Release.Title = "30 Rock - Season 1";
             _remoteEpisode.ParsedEpisodeInfo.FullSeason = true;
 
             Assert.Throws<NotImplementedException>(() => Subject.DownloadNzb(_remoteEpisode));
@@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
         {
             var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
             var expectedFilename = Path.Combine(_pneumaticFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb");
-            _remoteEpisode.Report.Title = illegalTitle;
+            _remoteEpisode.Release.Title = illegalTitle;
 
             Subject.DownloadNzb(_remoteEpisode);
 
diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs
index 111347602..f23a2b762 100644
--- a/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs
+++ b/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs
@@ -11,7 +11,6 @@ using NzbDrone.Core.Download.Clients.Sabnzbd;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
-using NzbDrone.Test.Common;
 
 namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
 {
@@ -36,9 +35,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
             fakeConfig.SetupGet(c => c.SabTvCategory).Returns("tv");
 
             _remoteEpisode = new RemoteEpisode();
-            _remoteEpisode.Report = new ReportInfo();
-            _remoteEpisode.Report.Title = TITLE;
-            _remoteEpisode.Report.NzbUrl = URL;
+            _remoteEpisode.Release = new ReleaseInfo();
+            _remoteEpisode.Release.Title = TITLE;
+            _remoteEpisode.Release.DownloadUrl = URL;
 
             _remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
                                                       .All()
diff --git a/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs b/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs
index cc0285b78..86413bcc4 100644
--- a/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs
+++ b/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download
 
             _parseResult = Builder<RemoteEpisode>.CreateNew()
                    .With(c => c.Series = Builder<Series>.CreateNew().Build())
-                   .With(c => c.Report = Builder<ReportInfo>.CreateNew().Build())
+                   .With(c => c.Release = Builder<ReleaseInfo>.CreateNew().Build())
                    .With(c => c.Episodes = episodes)
                    .Build();
 
diff --git a/NzbDrone.Core.Test/Files/Media/H264_sample.mp4 b/NzbDrone.Core.Test/Files/Media/H264_sample.mp4
new file mode 100644
index 000000000..35bc6b353
Binary files /dev/null and b/NzbDrone.Core.Test/Files/Media/H264_sample.mp4 differ
diff --git a/NzbDrone.Core.Test/Files/SceneMappings.json b/NzbDrone.Core.Test/Files/SceneMappings.json
index a914ecdc9..71e1f5937 100644
--- a/NzbDrone.Core.Test/Files/SceneMappings.json
+++ b/NzbDrone.Core.Test/Files/SceneMappings.json
@@ -1,27 +1,32 @@
 [
 	{
-		"CleanTitle": "csinewyork",
-		"Id": "73696",
-		"Title": "CSI"
+		"title": "Adventure Time",
+		"searchTitle": "Adventure Time",
+		"season": -1,
+		"tvdbId": 152831
 	},
 	{
-		"CleanTitle": "csiny",
-		"Id": "73696",
-		"Title": "CSI"
+		"title": "Americas Funniest Home Videos",
+		"searchTitle": "Americas Funniest Home Videos",
+		"season": -1,
+		"tvdbId": 76235
 	},
 	{
-		"CleanTitle": "csi",
-		"Id": "72546",
-		"Title": "CSI"
+		"title": "Antiques Roadshow UK",
+		"searchTitle": "Antiques Roadshow UK",
+		"season": -1,
+		"tvdbId": 83774
 	},
 	{
-		"CleanTitle": "csilasvegas",
-		"Id": "72546",
-		"Title": "CSI"
+		"title": "Aqua Something You Know Whatever",
+		"searchTitle": "Aqua Something You Know Whatever",
+		"season": 9,
+		"tvdbId": 77120
 	},
 	{
-		"CleanTitle": "archer",
-		"Id": "110381",
-		"Title": "Archer"
+		"title": "Aqua Teen Hunger Force",
+		"searchTitle": "Aqua Teen Hunger Force",
+		"season": -1,
+		"tvdbId": 77120
 	}
 ]
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/Framework/DbTest.cs b/NzbDrone.Core.Test/Framework/DbTest.cs
index ec94f7418..f8d3a0979 100644
--- a/NzbDrone.Core.Test/Framework/DbTest.cs
+++ b/NzbDrone.Core.Test/Framework/DbTest.cs
@@ -6,13 +6,15 @@ using FluentMigrator.Runner;
 using Marr.Data;
 using Moq;
 using NUnit.Framework;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Datastore.Migration.Framework;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Test.Framework
 {
-    [Category("DbTest")]
+
     public abstract class DbTest<TSubject, TModel> : DbTest
         where TSubject : class
         where TModel : ModelBase, new()
@@ -61,7 +63,7 @@ namespace NzbDrone.Core.Test.Framework
 
 
 
-
+    [Category("DbTest")]
     public abstract class DbTest : CoreTest
     {
         private ITestDatabase _db;
@@ -93,7 +95,7 @@ namespace NzbDrone.Core.Test.Framework
 
             Mocker.SetConstant<IAnnouncer>(Mocker.Resolve<MigrationLogger>());
             Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
-            Mocker.SetConstant<ISQLiteMigrationHelper>(Mocker.Resolve<SQLiteMigrationHelper>());
+            Mocker.SetConstant<ISqLiteMigrationHelper>(Mocker.Resolve<SqLiteMigrationHelper>());
             Mocker.SetConstant<ISQLiteAlter>(Mocker.Resolve<SQLiteAlter>());
             Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
 
@@ -147,27 +149,27 @@ namespace NzbDrone.Core.Test.Framework
     public class TestDatabase : ITestDatabase
     {
         private readonly IDatabase _dbConnection;
-        private IMessageAggregator _messageAggregator;
+        private IEventAggregator _eventAggregator;
 
         public TestDatabase(IDatabase dbConnection)
         {
-            _messageAggregator = new Mock<IMessageAggregator>().Object;
+            _eventAggregator = new Mock<IEventAggregator>().Object;
             _dbConnection = dbConnection;
         }
 
         public void InsertMany<T>(IEnumerable<T> items) where T : ModelBase, new()
         {
-            new BasicRepository<T>(_dbConnection, _messageAggregator).InsertMany(items.ToList());
+            new BasicRepository<T>(_dbConnection, _eventAggregator).InsertMany(items.ToList());
         }
 
         public T Insert<T>(T item) where T : ModelBase, new()
         {
-            return new BasicRepository<T>(_dbConnection, _messageAggregator).Insert(item);
+            return new BasicRepository<T>(_dbConnection, _eventAggregator).Insert(item);
         }
 
         public List<T> All<T>() where T : ModelBase, new()
         {
-            return new BasicRepository<T>(_dbConnection, _messageAggregator).All().ToList();
+            return new BasicRepository<T>(_dbConnection, _eventAggregator).All().ToList();
         }
 
         public T Single<T>() where T : ModelBase, new()
@@ -177,12 +179,12 @@ namespace NzbDrone.Core.Test.Framework
 
         public void Update<T>(T childModel) where T : ModelBase, new()
         {
-            new BasicRepository<T>(_dbConnection, _messageAggregator).Update(childModel);
+            new BasicRepository<T>(_dbConnection, _eventAggregator).Update(childModel);
         }
 
         public void Delete<T>(T childModel) where T : ModelBase, new()
         {
-            new BasicRepository<T>(_dbConnection, _messageAggregator).Delete(childModel);
+            new BasicRepository<T>(_dbConnection, _eventAggregator).Delete(childModel);
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs b/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs
index 4ac080e2a..2eca3586b 100644
--- a/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs
+++ b/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs
@@ -5,7 +5,7 @@ using NzbDrone.Core.Test.Framework;
 
 namespace NzbDrone.Core.Test.IndexerTests
 {
-    public class BasicRssParserFixture : CoreTest<BasicRssParser>
+    public class BasicRssParserFixture : CoreTest<RssParserBase>
     {
 
         [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", "LOL")]
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Test.IndexerTests
         [TestCase("The.Office.S03E115.DVDRip.XviD-OSiTV", "OSiTV")]
         public void parse_releaseGroup(string title, string expected)
         {
-            BasicRssParser.ParseReleaseGroup(title).Should().Be(expected);
+            RssParserBase.ParseReleaseGroup(title).Should().Be(expected);
         }
 
 
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.IndexerTests
         [TestCase("845 MB", 886046720)]
         public void parse_size(string sizeString, long expectedSize)
         {
-            var result = BasicRssParser.GetReportSize(sizeString);
+            var result = RssParserBase.ParseSize(sizeString);
 
             result.Should().Be(expectedSize);
         }
diff --git a/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs b/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs
index 3e6239823..2de52fb5f 100644
--- a/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs
+++ b/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs
@@ -6,7 +6,6 @@ using Moq;
 using NUnit.Framework;
 using NzbDrone.Core.Indexers;
 using NzbDrone.Core.Indexers.Newznab;
-using NzbDrone.Core.Indexers.NzbClub;
 using NzbDrone.Core.Indexers.Omgwtfnzbs;
 using NzbDrone.Core.Indexers.Wombles;
 using NzbDrone.Core.Lifecycle;
@@ -24,7 +23,6 @@ namespace NzbDrone.Core.Test.IndexerTests
             _indexers = new List<IIndexer>();
 
             _indexers.Add(new Newznab());
-            _indexers.Add(new NzbClub());
             _indexers.Add(new Omgwtfnzbs());
             _indexers.Add(new Wombles());
 
diff --git a/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs b/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs
index 813c1a45f..1be142824 100644
--- a/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs
+++ b/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs
@@ -1,13 +1,14 @@
 using System.Collections.Generic;
 using FluentAssertions;
 using NzbDrone.Core.Indexers;
+using NzbDrone.Core.Indexers.Eztv;
 using NzbDrone.Core.Indexers.Newznab;
-using NzbDrone.Core.Indexers.NzbClub;
 using NzbDrone.Core.Indexers.Wombles;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Test.Framework;
 using NUnit.Framework;
 using NzbDrone.Test.Common.Categories;
+using System.Linq;
 
 namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
 {
@@ -22,24 +23,24 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
         }
 
         [Test]
-        [Explicit]
-        public void nzbclub_rss()
+        public void wombles_rss()
         {
-            var indexer = new NzbClub();
+            var indexer = new Wombles();
 
             var result = Subject.FetchRss(indexer);
 
-            ValidateResult(result);
+            ValidateResult(result, skipSize: true, skipInfo: true);
         }
 
+
         [Test]
-        public void wombles_rss()
+        public void extv_rss()
         {
-            var indexer = new Wombles();
+            var indexer = new Eztv();
 
             var result = Subject.FetchRss(indexer);
 
-            ValidateResult(result, skipSize: true, skipInfo: true);
+            ValidateTorrentResult(result, skipSize: false, skipInfo: true);
         }
 
 
@@ -63,15 +64,17 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
 
 
 
-        private void ValidateResult(IList<ReportInfo> reports, bool skipSize = false, bool skipInfo = false)
+        private void ValidateResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false)
         {
             reports.Should().NotBeEmpty();
-            reports.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.Title));
-            reports.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.NzbUrl));
+            reports.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Title));
+            reports.Should().NotContain(c => string.IsNullOrWhiteSpace(c.DownloadUrl));
+            reports.Should().OnlyContain(c => c.PublishDate.Year > 2000);
+            reports.Should().OnlyContain(c => c.DownloadUrl.StartsWith("http"));
 
             if (!skipInfo)
             {
-                reports.Should().OnlyContain(c => !string.IsNullOrWhiteSpace(c.NzbInfoUrl));
+                reports.Should().NotContain(c => string.IsNullOrWhiteSpace(c.InfoUrl));
             }
 
             if (!skipSize)
@@ -80,5 +83,18 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
             }
         }
 
+        private void ValidateTorrentResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false)
+        {
+
+            reports.Should().OnlyContain(c => c.GetType() == typeof(TorrentInfo));
+
+            ValidateResult(reports, skipSize, skipInfo);
+
+            reports.Should().OnlyContain(c => c.DownloadUrl.EndsWith(".torrent"));
+
+            reports.Cast<TorrentInfo>().Should().OnlyContain(c => c.MagnetUrl.StartsWith("magnet:"));
+            reports.Cast<TorrentInfo>().Should().NotContain(c => string.IsNullOrWhiteSpace(c.InfoHash));
+        }
+
     }
-}
\ No newline at end of file
+}
diff --git a/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs b/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs
index 6bb9644a9..2b88348aa 100644
--- a/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs
+++ b/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs
@@ -3,6 +3,7 @@ using System.Diagnostics;
 using FluentAssertions;
 using NLog;
 using NUnit.Framework;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Datastore;
 using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.Instrumentation;
@@ -14,8 +15,6 @@ namespace NzbDrone.Core.Test.InstrumentationTests
     [TestFixture]
     public class DatabaseTargetFixture : DbTest<DatabaseTarget, Log>
     {
-        string _loggerName;
-
         private static string _uniqueMessage;
         Logger _logger;
 
@@ -36,8 +35,7 @@ namespace NzbDrone.Core.Test.InstrumentationTests
             LogManager.ReconfigExistingLoggers();
 
 
-            _logger = LogManager.GetCurrentClassLogger();
-            _loggerName = _logger.Name.Replace("NzbDrone.","");
+            _logger = NzbDroneLogger.GetLogger();
 
             _uniqueMessage = "Unique message: " + Guid.NewGuid().ToString();
         }
@@ -119,7 +117,7 @@ namespace NzbDrone.Core.Test.InstrumentationTests
         private void VerifyLog(Log logItem, LogLevel level)
         {
             logItem.Time.Should().BeWithin(TimeSpan.FromSeconds(2));
-            logItem.Logger.Should().Be(_loggerName);
+            logItem.Logger.Should().Be(this.GetType().Name);
             logItem.Level.Should().Be(level.Name);
             logItem.Method.Should().Be(new StackTrace().GetFrame(1).GetMethod().Name);
             _logger.Name.Should().EndWith(logItem.Logger);
diff --git a/NzbDrone.Core.Test/MediaFileTests/DropFolderImportServiceFixture.cs b/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
similarity index 97%
rename from NzbDrone.Core.Test/MediaFileTests/DropFolderImportServiceFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
index 870e5025a..09e51ac3a 100644
--- a/NzbDrone.Core.Test/MediaFileTests/DropFolderImportServiceFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
@@ -15,10 +15,10 @@ using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests
+namespace NzbDrone.Core.Test.MediaFiles
 {
     [TestFixture]
-    public class DropFolderImportServiceFixture : CoreTest<DownloadedEpisodesImportService>
+    public class DownloadedEpisodesImportServiceFixture : CoreTest<DownloadedEpisodesImportService>
     {
         private string[] _subFolders = new[] { "c:\\root\\foldername".AsOsAgnostic() };
         private string[] _videoFiles = new[] { "c:\\root\\foldername\\video.ext".AsOsAgnostic() };
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
         [Test]
         public void should_skip_if_file_is_in_use_by_another_process()
         {
-            Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<FileInfo>()))
+            Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<string>()))
                   .Returns(true);
 
             Subject.Execute(new DownloadedEpisodesScanCommand());
diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/ImportDecisionMakerFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
similarity index 97%
rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/ImportDecisionMakerFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
index 386c2a9ff..68dfdc5db 100644
--- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/ImportDecisionMakerFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
@@ -13,12 +13,12 @@ using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
+namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
 {
     [TestFixture]
     public class ImportDecisionMakerFixture : CoreTest<ImportDecisionMaker>
     {
-        private List<String> _videoFiles;
+        private List<string> _videoFiles;
         private LocalEpisode _localEpisode;
         private Series _series;
 
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
             _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(false);
             _fail3.Setup(c => c.RejectionReason).Returns("_fail3");
 
-            _videoFiles = new List<String> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" };
+            _videoFiles = new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" };
             _series = new Series();
             _localEpisode = new LocalEpisode { Series = _series, Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" };
 
diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecificationFixture.cs
similarity index 65%
rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecificationFixture.cs
index 47804bde0..4f1f545a4 100644
--- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/FreeSpaceSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecificationFixture.cs
@@ -8,12 +8,11 @@ using NUnit.Framework;
 using NzbDrone.Common;
 using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
 using NzbDrone.Core.Parser.Model;
-using NzbDrone.Core.Providers;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
+namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
 {
     [TestFixture]
     public class FreeSpaceSpecificationFixture : CoreTest<FreeSpaceSpecification>
@@ -40,7 +39,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
 
             _localEpisode = new LocalEpisode
                                 {
-                                    Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
+                                    Path = @"C:\Test\Unsorted\30 Rock\30.rock.s01e01.avi".AsOsAgnostic(),
                                     Episodes = episodes,
                                     Series = _series
                                 };
@@ -51,7 +50,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
             _localEpisode.Size = size;
         }
 
-        private void GivenFreeSpace(long size)
+        private void GivenFreeSpace(long? size)
         {
             Mocker.GetMock<IDiskProvider>()
                   .Setup(s => s.GetAvailableSpace(It.IsAny<String>()))
@@ -98,5 +97,50 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
             Mocker.GetMock<IDiskProvider>()
                 .Verify(v => v.GetAvailableSpace(_rootFolder), Times.Once());
         }
+
+        [Test]
+        public void should_pass_if_free_space_is_null()
+        {
+            GivenFileSize(100.Megabytes());
+            GivenFreeSpace(null);
+
+            Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
+        }
+
+        [Test]
+        public void should_pass_if_exception_is_thrown()
+        {
+            GivenFileSize(100.Megabytes());
+
+            Mocker.GetMock<IDiskProvider>()
+                  .Setup(s => s.GetAvailableSpace(It.IsAny<String>()))
+                  .Throws(new TestException());
+
+            Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
+            ExceptionVerification.ExpectedErrors(1);
+        }
+
+        [Test]
+        public void should_skip_check_for_files_under_series_folder()
+        {
+            _localEpisode.ExistingFile = true;
+
+            Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
+
+            Mocker.GetMock<IDiskProvider>()
+                  .Verify(s => s.GetAvailableSpace(It.IsAny<String>()), Times.Never());
+        }
+
+        [Test]
+        public void should_return_true_if_free_space_is_null()
+        {
+            long? freeSpace = null;
+
+            Mocker.GetMock<IDiskProvider>()
+                  .Setup(s => s.GetAvailableSpace(It.IsAny<String>()))
+                  .Returns(freeSpace);
+
+            Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
+        }
     }
 }
diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotInUseSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotInUseSpecificationFixture.cs
similarity index 77%
rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotInUseSpecificationFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotInUseSpecificationFixture.cs
index 98080014b..7887e40aa 100644
--- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotInUseSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotInUseSpecificationFixture.cs
@@ -1,20 +1,15 @@
-using System.IO;
-using System.Linq;
-using FizzWare.NBuilder;
+using FizzWare.NBuilder;
 using FluentAssertions;
-using Marr.Data;
 using Moq;
-using Newtonsoft.Json.Serialization;
 using NUnit.Framework;
 using NzbDrone.Common;
-using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
+namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
 {
     [TestFixture]
     public class NotInUseSpecificationFixture : CoreTest<NotInUseSpecification>
@@ -34,9 +29,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
 
         private void GivenChildOfSeries()
         {
-            Mocker.GetMock<IDiskProvider>()
-                .Setup(s => s.IsParent(_localEpisode.Series.Path, _localEpisode.Path))
-                .Returns(true);
+            _localEpisode.ExistingFile = true;
         }
 
         private void GivenNewFile()
@@ -62,7 +55,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
             Subject.IsSatisfiedBy(_localEpisode);
 
             Mocker.GetMock<IDiskProvider>()
-                .Verify(v => v.IsFileLocked(It.IsAny<FileInfo>()), Times.Never());
+                .Verify(v => v.IsFileLocked(It.IsAny<string>()), Times.Never());
         }
 
         [Test]
@@ -71,7 +64,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
             GivenNewFile();
 
             Mocker.GetMock<IDiskProvider>()
-                .Setup(s => s.IsFileLocked(It.IsAny<FileInfo>()))
+                .Setup(s => s.IsFileLocked(It.IsAny<string>()))
                 .Returns(true);
 
             Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
@@ -83,7 +76,7 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
             GivenNewFile();
 
             Mocker.GetMock<IDiskProvider>()
-                .Setup(s => s.IsFileLocked(It.IsAny<FileInfo>()))
+                .Setup(s => s.IsFileLocked(It.IsAny<string>()))
                 .Returns(false);
 
             Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotSampleSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotSampleSpecificationFixture.cs
similarity index 97%
rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotSampleSpecificationFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotSampleSpecificationFixture.cs
index f89cfa48d..c3f5b0429 100644
--- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotSampleSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotSampleSpecificationFixture.cs
@@ -5,13 +5,14 @@ using FluentAssertions;
 using Moq;
 using NUnit.Framework;
 using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
+using NzbDrone.Core.MediaFiles.MediaInfo;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Providers;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
+namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
 {
     [TestFixture]
     public class NotSampleSpecificationFixture : CoreTest<NotSampleSpecification>
diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotUnpackingSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecificationFixture.cs
similarity index 91%
rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotUnpackingSpecificationFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecificationFixture.cs
index 102f7a4f1..05a45a1f6 100644
--- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/NotUnpackingSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecificationFixture.cs
@@ -1,22 +1,17 @@
 using System;
-using System.IO;
-using System.Linq;
 using FizzWare.NBuilder;
 using FluentAssertions;
-using Marr.Data;
 using Moq;
-using Newtonsoft.Json.Serialization;
 using NUnit.Framework;
 using NzbDrone.Common;
 using NzbDrone.Core.Configuration;
-using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
+namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
 {
     [TestFixture]
     public class NotUnpackingSpecificationFixture : CoreTest<NotUnpackingSpecification>
diff --git a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/UpgradeSpecificationFixture.cs b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs
similarity index 99%
rename from NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/UpgradeSpecificationFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs
index 9ac31aa1b..a0d0f7aef 100644
--- a/NzbDrone.Core.Test/MediaFileTests/EpisodeImportTests/UpgradeSpecificationFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs
@@ -10,7 +10,7 @@ using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 
-namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
+namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
 {
     [TestFixture]
     public class UpgradeSpecificationFixture : CoreTest<UpgradeSpecification>
diff --git a/NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs b/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs
similarity index 95%
rename from NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs
index a7d13ff0c..b1cab5f58 100644
--- a/NzbDrone.Core.Test/MediaFileTests/ImportApprovedEpisodesFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs
@@ -4,17 +4,18 @@ using FizzWare.NBuilder;
 using FluentAssertions;
 using Moq;
 using NUnit.Framework;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.MediaFiles.EpisodeImport;
 using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Qualities;
-using NzbDrone.Core.Tv;
 using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests
+namespace NzbDrone.Core.Test.MediaFiles
 {
     [TestFixture]
     public class ImportApprovedEpisodesFixture : CoreTest<ImportApprovedEpisodes>
@@ -103,7 +104,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
         {
             Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
 
-            Mocker.GetMock<IMessageAggregator>()
+            Mocker.GetMock<IEventAggregator>()
                 .Verify(v => v.PublishEvent(It.IsAny<EpisodeImportedEvent>()), Times.Once());
         }
 
@@ -122,7 +123,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
         {
             Subject.Import(new List<ImportDecision> { _approvedDecisions.First() });
 
-            Mocker.GetMock<IMessageAggregator>()
+            Mocker.GetMock<IEventAggregator>()
                 .Verify(v => v.PublishEvent(It.IsAny<EpisodeImportedEvent>()), Times.Never());
         }
     }
diff --git a/NzbDrone.Core.Test/MediaFileTests/MediaFileRepositoryFixture.cs b/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs
similarity index 97%
rename from NzbDrone.Core.Test/MediaFileTests/MediaFileRepositoryFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs
index 567a55949..2b54fff52 100644
--- a/NzbDrone.Core.Test/MediaFileTests/MediaFileRepositoryFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs
@@ -8,7 +8,7 @@ using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests
+namespace NzbDrone.Core.Test.MediaFiles
 {
     [TestFixture]
     public class MediaFileRepositoryFixture : DbTest<MediaFileRepository, EpisodeFile>
diff --git a/NzbDrone.Core.Test/MediaFileTests/MediaFileServiceTest.cs b/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs
similarity index 99%
rename from NzbDrone.Core.Test/MediaFileTests/MediaFileServiceTest.cs
rename to NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs
index 1e1060239..2135e773a 100644
--- a/NzbDrone.Core.Test/MediaFileTests/MediaFileServiceTest.cs
+++ b/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs
@@ -8,7 +8,7 @@ using NzbDrone.Core.Organizer;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Core.Test.MediaFileTests
+namespace NzbDrone.Core.Test.MediaFiles
 {
     [TestFixture]
     public class MediaFileServiceTest : CoreTest<MediaFileService>
diff --git a/NzbDrone.Core.Test/MediaFileTests/MediaFileTableCleanupServiceFixture.cs b/NzbDrone.Core.Test/MediaFiles/MediaFileTableCleanupServiceFixture.cs
similarity index 99%
rename from NzbDrone.Core.Test/MediaFileTests/MediaFileTableCleanupServiceFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/MediaFileTableCleanupServiceFixture.cs
index 0b61e29d0..343a03158 100644
--- a/NzbDrone.Core.Test/MediaFileTests/MediaFileTableCleanupServiceFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/MediaFileTableCleanupServiceFixture.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using FizzWare.NBuilder;
 using Moq;
 using NUnit.Framework;
@@ -8,9 +9,8 @@ using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.MediaFiles.Commands;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
-using System.Linq;
 
-namespace NzbDrone.Core.Test.MediaFileTests
+namespace NzbDrone.Core.Test.MediaFiles
 {
     public class MediaFileTableCleanupServiceFixture : CoreTest<MediaFileTableCleanupService>
     {
diff --git a/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs b/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs
new file mode 100644
index 000000000..c3267089b
--- /dev/null
+++ b/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs
@@ -0,0 +1,22 @@
+using System.IO;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.MediaFiles.MediaInfo;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Test.Common.Categories;
+
+namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
+{
+    [TestFixture]
+    [DiskAccessTest]
+    public class VideoFileInfoReaderFixture : CoreTest<VideoFileInfoReader>
+    {
+        [Test]
+        public void get_runtime()
+        {
+            var path = Path.Combine(Directory.GetCurrentDirectory(), "Files", "Media", "H264_sample.mp4");
+
+            Subject.GetRunTime(path).Seconds.Should().Be(10);
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs b/NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs
similarity index 95%
rename from NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs
index 80b4dc855..070b4eb03 100644
--- a/NzbDrone.Core.Test/MediaFileTests/RenameEpisodeFileServiceFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs
@@ -3,14 +3,15 @@ using System.Linq;
 using FizzWare.NBuilder;
 using Moq;
 using NUnit.Framework;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.MediaFiles.Commands;
 using NzbDrone.Core.MediaFiles.Events;
-using NzbDrone.Core.Tv;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Tv;
 
-namespace NzbDrone.Core.Test.MediaFileTests
+namespace NzbDrone.Core.Test.MediaFiles
 {
     public class RenameEpisodeFileServiceFixture : CoreTest<RenameEpisodeFileService>
     {
@@ -70,7 +71,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
 
             Subject.Execute(new RenameSeriesCommand(_series.Id));
 
-            Mocker.GetMock<IMessageAggregator>()
+            Mocker.GetMock<IEventAggregator>()
                   .Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Never());
         }
 
@@ -85,7 +86,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
 
             Subject.Execute(new RenameSeriesCommand(_series.Id));
 
-            Mocker.GetMock<IMessageAggregator>()
+            Mocker.GetMock<IEventAggregator>()
                   .Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Never());
         }
 
@@ -97,7 +98,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
 
             Subject.Execute(new RenameSeriesCommand(_series.Id));
 
-            Mocker.GetMock<IMessageAggregator>()
+            Mocker.GetMock<IEventAggregator>()
                   .Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Once());
         }
 
diff --git a/NzbDrone.Core.Test/MediaFileTests/UpgradeMediaFileServiceFixture.cs b/NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs
similarity index 98%
rename from NzbDrone.Core.Test/MediaFileTests/UpgradeMediaFileServiceFixture.cs
rename to NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs
index 210eaff99..6594a3c77 100644
--- a/NzbDrone.Core.Test/MediaFileTests/UpgradeMediaFileServiceFixture.cs
+++ b/NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Linq;
+using System.Linq;
 using FizzWare.NBuilder;
 using Marr.Data;
 using Moq;
@@ -10,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 
-namespace NzbDrone.Core.Test.MediaFileTests
+namespace NzbDrone.Core.Test.MediaFiles
 {
     public class UpgradeMediaFileServiceFixture : CoreTest<UpgradeMediaFileService>
     {
diff --git a/NzbDrone.Core.Test/Messaging/Commands/CommandEqualityComparerFixture.cs b/NzbDrone.Core.Test/Messaging/Commands/CommandEqualityComparerFixture.cs
new file mode 100644
index 000000000..34d4b1d30
--- /dev/null
+++ b/NzbDrone.Core.Test/Messaging/Commands/CommandEqualityComparerFixture.cs
@@ -0,0 +1,97 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Indexers;
+using NzbDrone.Core.IndexerSearch;
+using NzbDrone.Core.MediaFiles.Commands;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Update.Commands;
+using NzbDrone.SignalR;
+
+namespace NzbDrone.Core.Test.Messaging.Commands
+{
+    [TestFixture]
+    public class CommandEqualityComparerFixture
+    {
+        [Test]
+        public void should_return_true_when_there_are_no_properties()
+        {
+            var command1 = new DownloadedEpisodesScanCommand();
+            var command2 = new DownloadedEpisodesScanCommand();
+
+            CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_true_when_single_property_matches()
+        {
+            var command1 = new EpisodeSearchCommand { EpisodeId = 1 };
+            var command2 = new EpisodeSearchCommand { EpisodeId = 1 };
+
+            CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_true_when_multiple_properties_match()
+        {
+            var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 };
+            var command2 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 };
+
+            CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_false_when_single_property_doesnt_match()
+        {
+            var command1 = new EpisodeSearchCommand { EpisodeId = 1 };
+            var command2 = new EpisodeSearchCommand { EpisodeId = 2 };
+
+            CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_false_when_only_one_property_matches()
+        {
+            var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 };
+            var command2 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 2 };
+
+            CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_false_when_no_properties_match()
+        {
+            var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 };
+            var command2 = new SeasonSearchCommand { SeriesId = 2, SeasonNumber = 2 };
+
+            CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse();
+        }
+
+        [Test]
+        public void should_return_false_when_only_one_has_properties()
+        {
+            var command1 = new SeasonSearchCommand();
+            var command2 = new SeasonSearchCommand { SeriesId = 2, SeasonNumber = 2 };
+
+            CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse();
+        }
+
+
+        [Test]
+        public void should_return_false_when_only_one_has_null_property()
+        {
+            var command1 = new BroadcastSignalRMessage(null);
+            var command2 = new BroadcastSignalRMessage(new SignalRMessage());
+
+            CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse();
+        }
+
+
+        [Test]
+        public void should_return_false_when_commands_are_diffrent_types()
+        {
+            CommandEqualityComparer.Instance.Equals(new RssSyncCommand(), new ApplicationUpdateCommand()).Should().BeFalse();
+        }
+
+  
+    }
+}
diff --git a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs b/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs
similarity index 63%
rename from NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs
rename to NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs
index 1e450ae21..650f81224 100644
--- a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs
+++ b/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs
@@ -2,13 +2,16 @@
 using System.Collections.Generic;
 using Moq;
 using NUnit.Framework;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Commands.Tracking;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Common.Test.EventingTests
+namespace NzbDrone.Core.Test.Messaging.Commands
 {
     [TestFixture]
-    public class MessageAggregatorCommandTests : TestBase<MessageAggregator>
+    public class CommandExecutorFixture : TestBase<CommandExecutor>
     {
         private Mock<IExecute<CommandA>> _executorA;
         private Mock<IExecute<CommandB>> _executorB;
@@ -27,6 +30,10 @@ namespace NzbDrone.Common.Test.EventingTests
                   .Setup(c => c.Build(typeof(IExecute<CommandB>)))
                   .Returns(_executorB.Object);
 
+
+            Mocker.GetMock<ITrackCommands>()
+                  .Setup(c => c.FindExisting(It.IsAny<Command>()))
+                  .Returns<Command>(null);
         }
 
         [Test]
@@ -42,7 +49,7 @@ namespace NzbDrone.Common.Test.EventingTests
         [Test]
         public void should_publish_command_by_with_optional_arg_using_name()
         {
-            Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(ICommand)))
+            Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(Command)))
                   .Returns(new List<Type> { typeof(CommandA), typeof(CommandB) });
 
             Subject.PublishCommand(typeof(CommandA).FullName);
@@ -55,7 +62,6 @@ namespace NzbDrone.Common.Test.EventingTests
         {
             var commandA = new CommandA();
 
-
             Subject.PublishCommand(commandA);
 
             _executorA.Verify(c => c.Execute(commandA), Times.Once());
@@ -72,21 +78,44 @@ namespace NzbDrone.Common.Test.EventingTests
 
             Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA));
         }
+
+
+        [Test]
+        public void broken_executor_should_publish_executed_event()
+        {
+            var commandA = new CommandA();
+
+            _executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
+                       .Throws(new NotImplementedException());
+
+            Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA));
+
+            VerifyEventPublished<CommandExecutedEvent>();
+        }
+
+        [Test]
+        public void should_publish_executed_event_on_success()
+        {
+            var commandA = new CommandA();
+            Subject.PublishCommand(commandA);
+
+            VerifyEventPublished<CommandExecutedEvent>();
+        }
     }
 
-    public class CommandA : ICommand
+    public class CommandA : Command
     {
-// ReSharper disable UnusedParameter.Local
         public CommandA(int id = 0)
-// ReSharper restore UnusedParameter.Local
         {
-
         }
     }
 
-    public class CommandB : ICommand
+    public class CommandB : Command
     {
 
+        public CommandB()
+        {
+        }
     }
 
 }
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/Messaging/Commands/CommandFixture.cs b/NzbDrone.Core.Test/Messaging/Commands/CommandFixture.cs
new file mode 100644
index 000000000..a75fc2224
--- /dev/null
+++ b/NzbDrone.Core.Test/Messaging/Commands/CommandFixture.cs
@@ -0,0 +1,19 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Update.Commands;
+
+namespace NzbDrone.Core.Test.Messaging.Commands
+{
+    [TestFixture]
+    public class CommandFixture
+    {
+        [Test]
+        public void default_values()
+        {
+            var command = new ApplicationUpdateCommand();
+
+            command.Id.Should().NotBe(0);
+            command.Name.Should().Be("ApplicationUpdate");
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs b/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs
index 4a2883253..412239c0b 100644
--- a/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs
+++ b/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs
@@ -47,11 +47,11 @@ namespace NzbDrone.Core.Test.MetadataSourceTests
             ValidateEpisodes(details.Item2);
         }
 
-
         [Test]
         public void getting_details_of_invalid_series()
         {
             Assert.Throws<RestException>(() => Subject.GetSeriesInfo(Int32.MaxValue));
+
             ExceptionVerification.ExpectedWarns(1);
         }
 
@@ -88,12 +88,16 @@ namespace NzbDrone.Core.Test.MetadataSourceTests
             foreach (var episode in episodes)
             {
                 ValidateEpisode(episode);
+
+                //if atleast one episdoe has title it means parse it working.
+                episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Title));
             }
         }
 
         private void ValidateEpisode(Episode episode)
         {
             episode.Should().NotBeNull();
+            episode.Title.Should().NotBeBlank();
             //episode.Title.Should().NotBeBlank();
             episode.EpisodeNumber.Should().NotBe(0);
             episode.TvDbEpisodeId.Should().BeGreaterThan(0);
diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 524d84b1d..5035ea37e 100644
--- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -108,8 +108,11 @@
     <Compile Include="Datastore\PagingSpecExtenstionsTests\ToSortDirectionFixture.cs" />
     <Compile Include="Datastore\PagingSpecExtenstionsTests\PagingOffsetFixture.cs" />
     <Compile Include="Datastore\ReflectionStrategyFixture\Benchmarks.cs" />
-    <Compile Include="Datastore\SQLiteMigrationHelperFixture.cs" />
+    <Compile Include="Datastore\SQLiteMigrationHelperTests\AlterFixture.cs" />
+    <Compile Include="Datastore\SQLiteMigrationHelperTests\DuplicateFixture.cs" />
+    <Compile Include="DecisionEngineTests\CutoffSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\NotRestrictedReleaseSpecificationFixture.cs" />
+    <Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />
     <Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" />
     <Compile Include="Download\DownloadApprovedReportsTests\GetQualifiedReportsFixture.cs" />
     <Compile Include="Download\DownloadClientTests\BlackholeProviderFixture.cs" />
@@ -132,17 +135,21 @@
     <Compile Include="JobTests\TestJobs.cs" />
     <Compile Include="MediaCoverTests\CoverExistsSpecificationFixture.cs" />
     <Compile Include="MediaCoverTests\MediaCoverServiceFixture.cs" />
-    <Compile Include="MediaFileTests\EpisodeImportTests\NotUnpackingSpecificationFixture.cs" />
-    <Compile Include="MediaFileTests\EpisodeImportTests\NotInUseSpecificationFixture.cs" />
-    <Compile Include="MediaFileTests\EpisodeImportTests\FreeSpaceSpecificationFixture.cs" />
-    <Compile Include="MediaFileTests\RenameEpisodeFileServiceFixture.cs" />
-    <Compile Include="MediaFileTests\UpgradeMediaFileServiceFixture.cs" />
-    <Compile Include="MediaFileTests\ImportApprovedEpisodesFixture.cs" />
-    <Compile Include="MediaFileTests\EpisodeImportTests\UpgradeSpecificationFixture.cs" />
-    <Compile Include="MediaFileTests\EpisodeImportTests\NotSampleSpecificationFixture.cs" />
-    <Compile Include="MediaFileTests\EpisodeImportTests\ImportDecisionMakerFixture.cs" />
-    <Compile Include="MediaFileTests\MediaFileTableCleanupServiceFixture.cs" />
-    <Compile Include="MediaFileTests\MediaFileRepositoryFixture.cs" />
+    <Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" />
+    <Compile Include="MediaFiles\EpisodeImport\ImportDecisionMakerFixture.cs" />
+    <Compile Include="MediaFiles\EpisodeImport\Specifications\NotInUseSpecificationFixture.cs" />
+    <Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecificationFixture.cs" />
+    <Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecificationFixture.cs" />
+    <Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" />
+    <Compile Include="MediaFiles\MediaInfo\VideoFileInfoReaderFixture.cs" />
+    <Compile Include="MediaFiles\RenameEpisodeFileServiceFixture.cs" />
+    <Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" />
+    <Compile Include="MediaFiles\ImportApprovedEpisodesFixture.cs" />
+    <Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" />
+    <Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
+    <Compile Include="Messaging\Commands\CommandEqualityComparerFixture.cs" />
+    <Compile Include="Messaging\Commands\CommandExecutorFixture.cs" />
+    <Compile Include="Messaging\Commands\CommandFixture.cs" />
     <Compile Include="MetadataSourceTests\TraktProxyFixture.cs" />
     <Compile Include="NotificationTests\NotificationServiceFixture.cs" />
     <Compile Include="NotificationTests\Xbmc\GetJsonVersionFixture.cs" />
@@ -155,6 +162,8 @@
     <Compile Include="NotificationTests\Xbmc\Json\GetSeriesPathFixture.cs" />
     <Compile Include="NotificationTests\Xbmc\Json\UpdateFixture.cs" />
     <Compile Include="OrganizerTests\BuildFilePathFixture.cs" />
+    <Compile Include="ParserTests\ParsingServiceTests\GetEpisodesFixture.cs" />
+    <Compile Include="ParserTests\ParsingServiceTests\MapFixture.cs" />
     <Compile Include="Qualities\QualitySizeRepositoryFixture.cs" />
     <Compile Include="Qualities\QualityProfileRepositoryFixture.cs" />
     <Compile Include="RootFolderTests\FreeSpaceOnDrivesFixture.cs" />
@@ -174,22 +183,18 @@
     <Compile Include="TvTests\EpisodeRepositoryTests\EpisodesRepositoryReadFixture.cs" />
     <Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWithoutFilesFixture.cs" />
     <Compile Include="TvTests\EpisodeRepositoryTests\EpisodesBetweenDatesFixture.cs" />
-    <Compile Include="TvTests\SeasonProviderTest.cs" />
     <Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\UpgradeHistorySpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\UpgradeDiskSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\QualityUpgradeSpecificationFixture.cs" />
-    <Compile Include="DecisionEngineTests\QualityUpgradableSpecificationFixture.cs" />
     <Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceFixture.cs" />
     <Compile Include="NotificationTests\ProwlProviderTest.cs" />
     <Compile Include="ProviderTests\DiskProviderTests\ArchiveProviderFixture.cs" />
-    <Compile Include="MediaFileTests\DropFolderImportServiceFixture.cs" />
+    <Compile Include="MediaFiles\DownloadedEpisodesImportServiceFixture.cs" />
     <Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" />
-    <Compile Include="TvTests\SeasonServiceTests\HandleEpisodeInfoDeletedEventFixture.cs" />
-    <Compile Include="TvTests\SeasonServiceTests\SetSeasonPassFixture.cs" />
-    <Compile Include="TvTests\SeasonServiceTests\SetMonitoredFixture.cs" />
     <Compile Include="TvTests\SeriesRepositoryTests\QualityProfileRepositoryFixture.cs" />
+    <Compile Include="TvTests\SeriesServiceTests\UpdateSeriesFixture.cs" />
     <Compile Include="UpdateTests\UpdateServiceFixture.cs" />
     <Compile Include="ProviderTests\XemCommunicationProviderTests\GetSceneTvdbMappingsFixture.cs" />
     <Compile Include="ProviderTests\XemCommunicationProviderTests\GetXemSeriesIdsFixture.cs" />
@@ -205,14 +210,14 @@
     <Compile Include="TvTests\QualityModelFixture.cs" />
     <Compile Include="RootFolderTests\RootFolderServiceFixture.cs" />
     <Compile Include="HistoryTests\HistoryRepositoryFixture.cs" />
-    <Compile Include="MediaFileTests\MediaFileServiceTest.cs" />
+    <Compile Include="MediaFiles\MediaFileServiceTest.cs" />
     <Compile Include="Configuration\ConfigServiceFixture.cs" />
     <Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest.cs" />
     <Compile Include="Framework\TestDbHelper.cs" />
     <Compile Include="ParserTests\ParserFixture.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Qualities\QualityProfileServiceFixture.cs" />
-    <Compile Include="TvTests\SeriesServiceFixture.cs" />
+    <Compile Include="TvTests\SeriesServiceTests\AddSeriesFixture.cs" />
     <Compile Include="UpdateTests\UpdatePackageProviderFixture.cs" />
     <Compile Include="XbmcVersionTests.cs" />
   </ItemGroup>
@@ -229,6 +234,10 @@
       <Project>{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}</Project>
       <Name>NzbDrone.Core</Name>
     </ProjectReference>
+    <ProjectReference Include="..\NzbDrone.SignalR\NzbDrone.SignalR.csproj">
+      <Project>{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}</Project>
+      <Name>NzbDrone.SignalR</Name>
+    </ProjectReference>
     <ProjectReference Include="..\NzbDrone.Test.Common\NzbDrone.Test.Common.csproj">
       <Project>{CADDFCE0-7509-4430-8364-2074E1EEFCA2}</Project>
       <Name>NzbDrone.Test.Common</Name>
@@ -242,6 +251,9 @@
     <Content Include="App_Data\Config.xml">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="Files\Media\H264_sample.mp4">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="Files\Nzbget\JsonError.txt">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
diff --git a/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/NzbDrone.Core.Test/ParserTests/ParserFixture.cs
index 192ff7380..ace78f50e 100644
--- a/NzbDrone.Core.Test/ParserTests/ParserFixture.cs
+++ b/NzbDrone.Core.Test/ParserTests/ParserFixture.cs
@@ -4,11 +4,10 @@ using FluentAssertions;
 using Moq;
 using NUnit.Framework;
 using NzbDrone.Common.Contract;
-using NzbDrone.Core.Indexers;
+using NzbDrone.Common.Expansive;
 using NzbDrone.Core.Parser;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Test.Common;
-using NzbDrone.Common.Expansive;
 
 namespace NzbDrone.Core.Test.ParserTests
 {
@@ -80,6 +79,8 @@ namespace NzbDrone.Core.Test.ParserTests
         [TestCase("Top_Gear.19x06.720p_HDTV_x264-FoV", "Top Gear", 19, 6)]
         [TestCase("Portlandia.S03E10.Alexandra.720p.WEB-DL.AAC2.0.H.264-CROM.mkv", "Portlandia", 3, 10)]
         [TestCase("(Game of Thrones s03 e - \"Game of Thrones Season 3 Episode 10\"", "Game of Thrones", 3, 10)]
+        [TestCase("House.Hunters.International.S05E607.720p.hdtv.x264", "House.Hunters.International", 5, 607)]
+        [TestCase("Adventure.Time.With.Finn.And.Jake.S01E20.720p.BluRay.x264-DEiMOS", "Adventure.Time.With.Finn.And.Jake", 1, 20)]
         public void ParseTitle_single(string postTitle, string title, int seasonNumber, int episodeNumber)
         {
             var result = Parser.Parser.ParseTitle(postTitle);
@@ -103,6 +104,8 @@ namespace NzbDrone.Core.Test.ParserTests
         [TestCase(@"/TV Drop/King of the Hill - 10x12 - 24 Hour Propane People [SDTV]/1012 - 24 Hour Propane People.avi", 10, 12)]
         [TestCase(@"S:\TV Drop\King of the Hill - 10x12 - 24 Hour Propane People [SDTV]\Hour Propane People.avi", 10, 12)]
         [TestCase(@"/TV Drop/King of the Hill - 10x12 - 24 Hour Propane People [SDTV]/Hour Propane People.avi", 10, 12)]
+        [TestCase(@"E:\Downloads\tv\The.Big.Bang.Theory.S01E01.720p.HDTV\ajifajjjeaeaeqwer_eppj.avi", 1, 1)]
+        [TestCase(@"C:\Test\Unsorted\The.Big.Bang.Theory.S01E01.720p.HDTV\tbbt101.avi", 1, 1)]
         public void PathParse_tests(string path, int season, int episode)
         {
             var result = Parser.Parser.ParsePath(path);
@@ -396,14 +399,6 @@ namespace NzbDrone.Core.Test.ParserTests
             result.Should().BeNull();
         }
 
-        [TestCase("[112461]-[FULL]-[#a.b.teevee@EFNet]-[ 666.Park.Avenue.S01E03.720p.HDTV.X264-DIMENSION ]-[02/31] - \"the.devils.address.103.720p-dimension.par2\" yEnc", "666.Park.Avenue.S01E03.720p.HDTV.X264-DIMENSION")]
-        [TestCase("[112438]-[FULL]-[#a.b.teevee@EFNet]-[ Downton_Abbey.3x05.HDTV_x264-FoV ]-[01/26] - \"downton_abbey.3x05.hdtv_x264-fov.nfo\" yEnc", "Downton_Abbey.3x05.HDTV_x264-FoV")]
-        [TestCase("[ 21154 ] - [ TrollHD ] - [ 00/73 ] - \"MythBusters S03E20 Escape Slide Parachute 1080i HDTV-UPSCALE DD5.1 MPEG2-TrollHD.nzb\" yEnc", "MythBusters S03E20 Escape Slide Parachute 1080i HDTV-UPSCALE DD5.1 MPEG2-TrollHD.nzb")]
-        public void parse_header(string title, string expected)
-        {
-            BasicRssParser.ParseHeader(title).Should().Be(expected);
-        }
-
         [TestCase("76El6LcgLzqb426WoVFg1vVVVGx4uCYopQkfjmLe")]
         [TestCase("Vrq6e1Aba3U amCjuEgV5R2QvdsLEGYF3YQAQkw8")]
         [TestCase("TDAsqTea7k4o6iofVx3MQGuDK116FSjPobMuh8oB")]
diff --git a/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs b/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs
new file mode 100644
index 000000000..b0692b6fb
--- /dev/null
+++ b/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using FizzWare.NBuilder;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.DataAugmentation.Scene;
+using NzbDrone.Core.IndexerSearch.Definitions;
+using NzbDrone.Core.Parser;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Tv;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
+{
+    [TestFixture]
+    public class GetEpisodesFixture : TestBase<ParsingService>
+    {
+        private Series _series;
+        private List<Episode> _episodes;
+        private ParsedEpisodeInfo _parsedEpisodeInfo;
+        private SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria;
+
+        [SetUp]
+        public void Setup()
+        {
+            _series = Builder<Series>.CreateNew()
+                .With(s => s.Title = "30 Rock")
+                .With(s => s.CleanTitle = "rock")
+                .Build();
+
+            _episodes = Builder<Episode>.CreateListOfSize(1)
+                                        .All()
+                                        .With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
+                                        .Build()
+                                        .ToList();
+
+            _parsedEpisodeInfo = new ParsedEpisodeInfo
+            {
+                SeriesTitle = _series.Title,
+                SeasonNumber = 1,
+                EpisodeNumbers = new[] { 1 }
+            };
+
+            _singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria
+            {
+                Series = _series,
+                EpisodeNumber = _episodes.First().EpisodeNumber,
+                SeasonNumber = _episodes.First().SeasonNumber,
+                Episodes = _episodes
+            };
+
+            Mocker.GetMock<ISeriesService>()
+                  .Setup(s => s.FindByTitle(It.IsAny<String>()))
+                  .Returns(_series);
+        }
+
+        private void GivenDailySeries()
+        {
+            _series.SeriesType = SeriesTypes.Daily;
+        }
+
+        private void GivenDailyParseResult()
+        {
+            _parsedEpisodeInfo.AirDate = DateTime.Today;
+        }
+
+        private void GivenSceneNumberingSeries()
+        {
+            _series.UseSceneNumbering = true;
+        }
+
+        [Test]
+        public void should_get_daily_episode_episode_when_search_criteria_is_null()
+        {
+            GivenDailySeries();
+            GivenDailyParseResult();
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Once());
+        }
+
+        [Test]
+        public void should_use_search_criteria_episode_when_it_matches_daily()
+        {
+            GivenDailySeries();
+            GivenDailyParseResult();
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Never());
+        }
+
+        [Test]
+        public void should_fallback_to_daily_episode_lookup_when_search_criteria_episode_doesnt_match()
+        {
+            GivenDailySeries();
+            _parsedEpisodeInfo.AirDate = DateTime.Today.AddDays(-5);
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Once());
+        }
+
+        [Test]
+        public void should_use_scene_numbering_when_series_uses_scene_numbering()
+        {
+            GivenSceneNumberingSeries();
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), true), Times.Once());
+        }
+
+        [Test]
+        public void should_match_search_criteria_by_scene_numbering()
+        {
+            GivenSceneNumberingSeries();
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), true), Times.Never());
+        }
+
+        [Test]
+        public void should_fallback_to_findEpisode_when_search_criteria_match_fails_for_scene_numbering()
+        {
+            GivenSceneNumberingSeries();
+            _episodes.First().SceneEpisodeNumber = 10;
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), true), Times.Once());
+        }
+
+        [Test]
+        public void should_find_episode()
+        {
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), false), Times.Once());
+        }
+
+        [Test]
+        public void should_match_episode_with_search_criteria()
+        {
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), false), Times.Never());
+        }
+
+        [Test]
+        public void should_fallback_to_findEpisode_when_search_criteria_match_fails()
+        {
+            _episodes.First().EpisodeNumber = 10;
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<IEpisodeService>()
+                .Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>(), false), Times.Once());
+        }
+    }
+}
diff --git a/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs b/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs
new file mode 100644
index 000000000..efa6c5b3d
--- /dev/null
+++ b/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using FizzWare.NBuilder;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.DataAugmentation.Scene;
+using NzbDrone.Core.IndexerSearch.Definitions;
+using NzbDrone.Core.Parser;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Tv;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
+{
+    [TestFixture]
+    public class MapFixture : TestBase<ParsingService>
+    {
+        private Series _series;
+        private List<Episode> _episodes;
+        private ParsedEpisodeInfo _parsedEpisodeInfo;
+        private SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria;
+
+        [SetUp]
+        public void Setup()
+        {
+            _series = Builder<Series>.CreateNew()
+                .With(s => s.Title = "30 Rock")
+                .With(s => s.CleanTitle = "rock")
+                .Build();
+
+            _episodes = Builder<Episode>.CreateListOfSize(1)
+                                        .All()
+                                        .With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
+                                        .Build()
+                                        .ToList();
+
+            _parsedEpisodeInfo = new ParsedEpisodeInfo
+            {
+                SeriesTitle = _series.Title,
+                SeasonNumber = 1,
+                EpisodeNumbers = new[] { 1 }
+            };
+
+            _singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria
+            {
+                Series = _series,
+                EpisodeNumber = _episodes.First().EpisodeNumber,
+                SeasonNumber = _episodes.First().SeasonNumber,
+                Episodes = _episodes
+            };
+        }
+
+        private void GivenMatchBySeriesTitle()
+        {
+            Mocker.GetMock<ISeriesService>()
+                  .Setup(s => s.FindByTitle(It.IsAny<String>()))
+                  .Returns(_series);
+        }
+
+        private void GivenMatchByTvRageId()
+        {
+            Mocker.GetMock<ISeriesService>()
+                  .Setup(s => s.FindByTvRageId(It.IsAny<Int32>()))
+                  .Returns(_series);
+        }
+
+        private void GivenParseResultSeriesDoesntMatchSearchCriteria()
+        {
+            _parsedEpisodeInfo.SeriesTitle = "Another Name";
+        }
+
+        [Test]
+        public void should_lookup_series_by_name()
+        {
+            GivenMatchBySeriesTitle();
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId);
+
+            Mocker.GetMock<ISeriesService>()
+                  .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Once());
+        }
+
+        [Test]
+        public void should_use_tvrageid_when_series_title_lookup_fails()
+        {
+            GivenMatchByTvRageId();
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId);
+
+            Mocker.GetMock<ISeriesService>()
+                  .Verify(v => v.FindByTvRageId(It.IsAny<Int32>()), Times.Once());
+        }
+
+        [Test]
+        public void should_use_search_criteria_series_title()
+        {
+            GivenMatchBySeriesTitle();
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<ISeriesService>()
+                  .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Never());
+        }
+
+        [Test]
+        public void should_FindByTitle_when_search_criteria_matching_fails()
+        {
+            GivenParseResultSeriesDoesntMatchSearchCriteria();
+
+            Subject.Map(_parsedEpisodeInfo, 10, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<ISeriesService>()
+                  .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Once());
+        }
+
+        [Test]
+        public void should_FindByTvRageId_when_search_criteria_and_FIndByTitle_matching_fails()
+        {
+            GivenParseResultSeriesDoesntMatchSearchCriteria();
+
+            Subject.Map(_parsedEpisodeInfo, 10, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<ISeriesService>()
+                  .Verify(v => v.FindByTvRageId(It.IsAny<Int32>()), Times.Once());
+        }
+
+        [Test]
+        public void should_use_tvdbid_matching_when_alias_is_found()
+        {
+            Mocker.GetMock<ISceneMappingService>()
+                  .Setup(s => s.GetTvDbId(It.IsAny<String>()))
+                  .Returns(_series.TvdbId);
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<ISeriesService>()
+                  .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Never());
+        }
+
+        [Test]
+        public void should_use_tvrageid_match_from_search_criteria_when_title_match_fails()
+        {
+            GivenParseResultSeriesDoesntMatchSearchCriteria();
+
+            Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
+
+            Mocker.GetMock<ISeriesService>()
+                  .Verify(v => v.FindByTitle(It.IsAny<String>()), Times.Never());
+        }
+    }
+}
diff --git a/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
index a1a8c60f0..3eb98a5b9 100644
--- a/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
+++ b/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
@@ -1,6 +1,4 @@
-
-
-using System;
+using System;
 using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Core.Qualities;
@@ -12,120 +10,6 @@ namespace NzbDrone.Core.Test.ParserTests
 
     public class QualityParserFixture : CoreTest
     {
-        public static object[] SdtvCases =
-        {
-            new object[] { "S07E23 .avi ", false },
-            new object[] {"The.Shield.S01E13.x264-CtrlSD", false},
-            new object[] { "Nikita S02E01 HDTV XviD 2HD", false },
-            new object[] { "Gossip Girl S05E11 PROPER HDTV XviD 2HD", true },
-            new object[] { "The Jonathan Ross Show S02E08 HDTV x264 FTP", false },
-            new object[] { "White.Van.Man.2011.S02E01.WS.PDTV.x264-TLA", false },
-            new object[] { "White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA", true },
-            new object[] { "The Real Housewives of Vancouver S01E04 DSR x264 2HD", false },
-            new object[] { "Vanguard S01E04 Mexicos Death Train DSR x264 MiNDTHEGAP", false },
-            new object[] { "Chuck S11E03 has no periods or extension HDTV", false },
-            new object[] { "Chuck.S04E05.HDTV.XviD-LOL", false },
-            new object[] { "Sonny.With.a.Chance.S02E15.avi", false },
-            new object[] { "Sonny.With.a.Chance.S02E15.xvid", false },
-            new object[] { "Sonny.With.a.Chance.S02E15.divx", false },
-            new object[] { "The.Girls.Next.Door.S03E06.HDTV-WiDE", false },
-            new object[] { "Degrassi.S10E27.WS.DSR.XviD-2HD", false },
-        };
-
-        public static object[] DvdCases =
-        {
-            new object[] { "WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3-REPACK.-HELLYWOOD.avi", true },
-            new object[] { "The.Shield.S01E13.NTSC.x264-CtrlSD", false },
-            new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", false },
-            new object[] { "WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", false },
-            new object[] { "WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", false },
-            new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false },
-            new object[] { "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false },
-            new object[] { "WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3.-HELLYWOOD.avi", false },
-            new object[] { "The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", false },
-            new object[] { "The.Girls.Next.Door.S03E06.DVD.Rip.XviD-WiDE", false },
-            new object[] { "the.shield.1x13.circles.ws.xvidvd-tns", false}
-        };
-
-        public static object[] Webdl480pCases =
-        {
-            new object[] { "Elementary.S01E10.The.Leviathan.480p.WEB-DL.x264-mSD", false },
-            new object[] { "Glee.S04E10.Glee.Actually.480p.WEB-DL.x264-mSD", false },
-            new object[] { "The.Big.Bang.Theory.S06E11.The.Santa.Simulation.480p.WEB-DL.x264-mSD", false },            
-        };
-
-        public static object[] Hdtv720pCases =
-        {
-            new object[] { "Dexter - S01E01 - Title [HDTV]", false },
-            new object[] { "Dexter - S01E01 - Title [HDTV-720p]", false },
-            new object[] { "Pawn Stars S04E87 REPACK 720p HDTV x264 aAF", true },
-            new object[] { "Sonny.With.a.Chance.S02E15.720p", false },
-            new object[] { "S07E23 - [HDTV-720p].mkv ", false },
-            new object[] { "Chuck - S22E03 - MoneyBART - HD TV.mkv", false },            
-            new object[] { "S07E23.mkv ", false },
-            new object[] { "Two.and.a.Half.Men.S08E05.720p.HDTV.X264-DIMENSION", false },
-            new object[] { "Sonny.With.a.Chance.S02E15.mkv", false },        
-        };
-
-        public static object[] Hdtv1080pCases =
-        {
-            new object[] { "Under the Dome S01E10 Let the Games Begin 1080p", false },
-            new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.X264-QCF", false },
-            new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.x264-QCF", false },
-            new object[] { "DEXTER.S07E01.ARE.YOU.1080P.HDTV.proper.X264-QCF", true },
-            new object[] { "Dexter - S01E01 - Title [HDTV-1080p]", false },
-        };
-
-        public static object[] Webdl720pCases =
-        {
-            new object[] { "Arrested.Development.S04E01.720p.WEBRip.AAC2.0.x264-NFRiP", false },
-            new object[] { "Vanguard S01E04 Mexicos Death Train 720p WEB DL", false },
-            new object[] { "Hawaii Five 0 S02E21 720p WEB DL DD5 1 H 264", false },
-            new object[] { "Castle S04E22 720p WEB DL DD5 1 H 264 NFHD", false },
-            new object[] { "Chuck - S11E06 - D-Yikes! - 720p WEB-DL.mkv", false },
-            new object[] { "Sonny.With.a.Chance.S02E15.720p.WEB-DL.DD5.1.H.264-SURFER", false },
-            new object[] { "S07E23 - [WEBDL].mkv ", false },
-            new object[] { "Fringe S04E22 720p WEB-DL DD5.1 H264-EbP.mkv", false },
-        };
-
-        public static object[] Webdl1080pCases =
-        {
-            new object[] { "Arrested.Development.S04E01.iNTERNAL.1080p.WEBRip.x264-QRUS", false },
-            new object[] { "CSI NY S09E03 1080p WEB DL DD5 1 H264 NFHD", false },
-            new object[] { "Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 NFHD", false },
-            new object[] { "Criminal.Minds.S08E01.1080p.WEB-DL.DD5.1.H264-NFHD", false },
-            new object[] { "Its.Always.Sunny.in.Philadelphia.S08E01.1080p.WEB-DL.proper.AAC2.0.H.264", true },
-            new object[] { "Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 REPACK NFHD", true },
-            new object[] { "Glee.S04E09.Swan.Song.1080p.WEB-DL.DD5.1.H.264-ECI", false },
-            new object[] { "The.Big.Bang.Theory.S06E11.The.Santa.Simulation.1080p.WEB-DL.DD5.1.H.264", false },        
-        };
-
-        public static object[] Bluray720pCases =
-        {
-            new object[] { "WEEDS.S03E01-06.DUAL.Bluray.AC3.-HELLYWOOD.avi", false },
-            new object[] { "Chuck - S01E03 - Come Fly With Me - 720p BluRay.mkv",  false },
-        };
-
-        public static object[] Bluray1080pCases =
-        {
-            new object[] { "Chuck - S01E03 - Come Fly With Me - 1080p BluRay.mkv", false },
-            new object[] { "Sons.Of.Anarchy.S02E13.1080p.BluRay.x264-AVCDVD", false },
-        };
-
-        public static object[] RawCases =
-        {
-            new object[] { "POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false },
-            new object[] { "How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false },
-            new object[] { "The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false },
-        };
-
-        public static object[] UnknownCases =
-        {
-            new object[] { "Sonny.With.a.Chance.S02E15", false },
-            new object[] { "Law & Order: Special Victims Unit - 11x11 - Quickie", false },
-
-        };
-
         public static object[] SelfQualityParserCases =
         {
             new object[] { Quality.SDTV },
@@ -138,92 +22,130 @@ namespace NzbDrone.Core.Test.ParserTests
             new object[] { Quality.Bluray1080p }
         };
 
-        [Test, TestCaseSource("SdtvCases")]
-        public void should_parse_sdtv_quality(string postTitle, bool proper)
-        {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.SDTV);
-            result.Proper.Should().Be(proper);
+        [TestCase("S07E23 .avi ", false)]
+        [TestCase("The.Shield.S01E13.x264-CtrlSD", false)]
+        [TestCase("Nikita S02E01 HDTV XviD 2HD", false)]
+        [TestCase("Gossip Girl S05E11 PROPER HDTV XviD 2HD", true)]
+        [TestCase("The Jonathan Ross Show S02E08 HDTV x264 FTP", false)]
+        [TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-TLA", false)]
+        [TestCase("White.Van.Man.2011.S02E01.WS.PDTV.x264-REPACK-TLA", true)]
+        [TestCase("The Real Housewives of Vancouver S01E04 DSR x264 2HD", false)]
+        [TestCase("Vanguard S01E04 Mexicos Death Train DSR x264 MiNDTHEGAP", false)]
+        [TestCase("Chuck S11E03 has no periods or extension HDTV", false)]
+        [TestCase("Chuck.S04E05.HDTV.XviD-LOL", false)]
+        [TestCase("Sonny.With.a.Chance.S02E15.avi", false)]
+        [TestCase("Sonny.With.a.Chance.S02E15.xvid", false)]
+        [TestCase("Sonny.With.a.Chance.S02E15.divx", false)]
+        [TestCase("The.Girls.Next.Door.S03E06.HDTV-WiDE", false)]
+        [TestCase("Degrassi.S10E27.WS.DSR.XviD-2HD", false)]
+        public void should_parse_sdtv_quality(string title, bool proper)
+        {
+            ParseAndVerifyQuality(title, Quality.SDTV, proper);
         }
 
-        [Test, TestCaseSource("DvdCases")]
-        public void should_parse_dvd_quality(string postTitle, bool proper)
-        {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.DVD);
-            result.Proper.Should().Be(proper);
+        [TestCase("WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3-REPACK.-HELLYWOOD.avi", true)]
+        [TestCase("The.Shield.S01E13.NTSC.x264-CtrlSD", false)]
+        [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", false)]
+        [TestCase("WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", false)]
+        [TestCase("WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", false)]
+        [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false)]
+        [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false)]
+        [TestCase("WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3.-HELLYWOOD.avi", false)]
+        [TestCase("The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", false)]
+        [TestCase("The.Girls.Next.Door.S03E06.DVD.Rip.XviD-WiDE", false)]
+        [TestCase("the.shield.1x13.circles.ws.xvidvd-tns", false)]
+        public void should_parse_dvd_quality(string title, bool proper)
+        {
+            ParseAndVerifyQuality(title, Quality.DVD, proper);
         }
 
-        [Test, TestCaseSource("Webdl480pCases")]
-        public void should_parse_webdl480p_quality(string postTitle, bool proper)
+        [TestCase("Elementary.S01E10.The.Leviathan.480p.WEB-DL.x264-mSD", false)]
+        [TestCase("Glee.S04E10.Glee.Actually.480p.WEB-DL.x264-mSD", false)]
+        [TestCase("The.Big.Bang.Theory.S06E11.The.Santa.Simulation.480p.WEB-DL.x264-mSD", false)]  
+        public void should_parse_webdl480p_quality(string title, bool proper)
         {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.WEBDL480p);
-            result.Proper.Should().Be(proper);
+            ParseAndVerifyQuality(title, Quality.WEBDL480p, proper);
         }
 
-        [Test, TestCaseSource("Hdtv720pCases")]
-        public void should_parse_hdtv720p_quality(string postTitle, bool proper)
-        {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.HDTV720p);
-            result.Proper.Should().Be(proper);
+        [TestCase("Dexter - S01E01 - Title [HDTV]", false)]
+        [TestCase("Dexter - S01E01 - Title [HDTV-720p]", false)]
+        [TestCase("Pawn Stars S04E87 REPACK 720p HDTV x264 aAF", true)]
+        [TestCase("Sonny.With.a.Chance.S02E15.720p", false)]
+        [TestCase("S07E23 - [HDTV-720p].mkv ", false)]
+        [TestCase("Chuck - S22E03 - MoneyBART - HD TV.mkv", false)]
+        [TestCase("S07E23.mkv ", false)]
+        [TestCase("Two.and.a.Half.Men.S08E05.720p.HDTV.X264-DIMENSION", false)]
+        [TestCase("Sonny.With.a.Chance.S02E15.mkv", false)]
+        [TestCase(@"E:\Downloads\tv\The.Big.Bang.Theory.S01E01.720p.HDTV\ajifajjjeaeaeqwer_eppj.avi", false)]
+        public void should_parse_hdtv720p_quality(string title, bool proper)
+        {
+            ParseAndVerifyQuality(title, Quality.HDTV720p, proper);
         }
 
-        [Test, TestCaseSource("Hdtv1080pCases")]
-        public void should_parse_hdtv1080p_quality(string postTitle, bool proper)
+        [TestCase("Under the Dome S01E10 Let the Games Begin 1080p", false)]
+        [TestCase("DEXTER.S07E01.ARE.YOU.1080P.HDTV.X264-QCF", false)]
+        [TestCase("DEXTER.S07E01.ARE.YOU.1080P.HDTV.x264-QCF", false)]
+        [TestCase("DEXTER.S07E01.ARE.YOU.1080P.HDTV.proper.X264-QCF", true)]
+        [TestCase("Dexter - S01E01 - Title [HDTV-1080p]", false)]
+        public void should_parse_hdtv1080p_quality(string title, bool proper)
         {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.HDTV1080p);
-            result.Proper.Should().Be(proper);
+            ParseAndVerifyQuality(title, Quality.HDTV1080p, proper);
         }
 
-        [Test, TestCaseSource("Webdl720pCases")]
-        public void should_parse_webdl720p_quality(string postTitle, bool proper)
-        {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.WEBDL720p);
-            result.Proper.Should().Be(proper);
+        [TestCase("Arrested.Development.S04E01.720p.WEBRip.AAC2.0.x264-NFRiP", false)]
+        [TestCase("Vanguard S01E04 Mexicos Death Train 720p WEB DL", false)]
+        [TestCase("Hawaii Five 0 S02E21 720p WEB DL DD5 1 H 264", false)]
+        [TestCase("Castle S04E22 720p WEB DL DD5 1 H 264 NFHD", false)]
+        [TestCase("Chuck - S11E06 - D-Yikes! - 720p WEB-DL.mkv", false)]
+        [TestCase("Sonny.With.a.Chance.S02E15.720p.WEB-DL.DD5.1.H.264-SURFER", false)]
+        [TestCase("S07E23 - [WEBDL].mkv ", false)]
+        [TestCase("Fringe S04E22 720p WEB-DL DD5.1 H264-EbP.mkv", false)]
+        public void should_parse_webdl720p_quality(string title, bool proper)
+        {
+            ParseAndVerifyQuality(title, Quality.WEBDL720p, proper);
         }
 
-        [Test, TestCaseSource("Webdl1080pCases")]
-        public void should_parse_webdl1080p_quality(string postTitle, bool proper)
-        {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.WEBDL1080p);
-            result.Proper.Should().Be(proper);
+        [TestCase("Arrested.Development.S04E01.iNTERNAL.1080p.WEBRip.x264-QRUS", false)]
+        [TestCase("CSI NY S09E03 1080p WEB DL DD5 1 H264 NFHD", false)]
+        [TestCase("Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 NFHD", false)]
+        [TestCase("Criminal.Minds.S08E01.1080p.WEB-DL.DD5.1.H264-NFHD", false)]
+        [TestCase("Its.Always.Sunny.in.Philadelphia.S08E01.1080p.WEB-DL.proper.AAC2.0.H.264", true)]
+        [TestCase("Two and a Half Men S10E03 1080p WEB DL DD5 1 H 264 REPACK NFHD", true)]
+        [TestCase("Glee.S04E09.Swan.Song.1080p.WEB-DL.DD5.1.H.264-ECI", false)]
+        [TestCase("The.Big.Bang.Theory.S06E11.The.Santa.Simulation.1080p.WEB-DL.DD5.1.H.264", false)]
+        public void should_parse_webdl1080p_quality(string title, bool proper)
+        {
+            ParseAndVerifyQuality(title, Quality.WEBDL1080p, proper);
         }
 
-        [Test, TestCaseSource("Bluray720pCases")]
-        public void should_parse_bluray720p_quality(string postTitle, bool proper)
+        [TestCase("WEEDS.S03E01-06.DUAL.Bluray.AC3.-HELLYWOOD.avi", false)]
+        [TestCase("Chuck - S01E03 - Come Fly With Me - 720p BluRay.mkv", false)]
+        [TestCase("The Big Bang Theory.S03E01.The Electric Can Opener Fluctuation.m2ts", false)]
+        public void should_parse_bluray720p_quality(string title, bool proper)
         {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.Bluray720p);
-            result.Proper.Should().Be(proper);
+            ParseAndVerifyQuality(title, Quality.Bluray720p, proper);
         }
 
-        [Test, TestCaseSource("Bluray1080pCases")]
-        public void should_parse_bluray1080p_quality(string postTitle, bool proper)
+        [TestCase("Chuck - S01E03 - Come Fly With Me - 1080p BluRay.mkv", false)]
+        [TestCase("Sons.Of.Anarchy.S02E13.1080p.BluRay.x264-AVCDVD", false)]
+        public void should_parse_bluray1080p_quality(string title, bool proper)
         {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.Bluray1080p);
-            result.Proper.Should().Be(proper);
+            ParseAndVerifyQuality(title, Quality.Bluray1080p, proper);
         }
 
-        [Test, TestCaseSource("RawCases")]
-        public void should_parse_raw_quality(string postTitle, bool proper)
+        [TestCase("POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
+        [TestCase("How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false)]
+        [TestCase("The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
+        public void should_parse_raw_quality(string title, bool proper)
         {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.RAWHD);
-            result.Proper.Should().Be(proper);
+            ParseAndVerifyQuality(title, Quality.RAWHD, proper);
         }
 
-        [Test, TestCaseSource("UnknownCases")]
-        public void quality_parse(string postTitle, bool proper)
+        [TestCase("Sonny.With.a.Chance.S02E15", false)]
+        [TestCase("Law & Order: Special Victims Unit - 11x11 - Quickie", false)]
+        public void quality_parse(string title, bool proper)
         {
-            var result = Parser.QualityParser.ParseQuality(postTitle);
-            result.Quality.Should().Be(Quality.Unknown);
-            result.Proper.Should().Be(proper);
+            ParseAndVerifyQuality(title, Quality.Unknown, proper);
         }
 
         [Test, TestCaseSource("SelfQualityParserCases")]
@@ -233,5 +155,12 @@ namespace NzbDrone.Core.Test.ParserTests
             var result = Parser.QualityParser.ParseQuality(fileName);
             result.Quality.Should().Be(quality);
         }
+
+        private void ParseAndVerifyQuality(string title, Quality quality, bool proper)
+        {
+            var result = Parser.QualityParser.ParseQuality(title);
+            result.Quality.Should().Be(quality);
+            result.Proper.Should().Be(proper);
+        }
     }
 }
diff --git a/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetVideoFilesFixture.cs b/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetVideoFilesFixture.cs
index b73e09984..bb2bb4f93 100644
--- a/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetVideoFilesFixture.cs
+++ b/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetVideoFilesFixture.cs
@@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
         public void should_return_video_files_only()
         {
             var path = @"C:\Test\";
-
+            var test = Subject.GetVideoFiles(path);
             Subject.GetVideoFiles(path).Should().HaveCount(4);
         }
     }
diff --git a/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs b/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs
index 2589fe5df..1b689c53c 100644
--- a/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs
+++ b/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs
@@ -12,34 +12,88 @@ namespace NzbDrone.Core.Test.SeriesStatsTests
     [TestFixture]
     public class SeriesStatisticsFixture : DbTest<SeriesStatisticsRepository, Series>
     {
+        private Series _series;
         private Episode _episode;
 
         [SetUp]
         public void Setup()
         {
-            var series = Builder<Series>.CreateNew()
+            _series = Builder<Series>.CreateNew()
                                         .With(s => s.Id = 0)
                                         .With(s => s.Runtime = 30)
                                         .Build();
 
-            series.Id = Db.Insert(series).Id;
+            _series.Id = Db.Insert(_series).Id;
 
             _episode = Builder<Episode>.CreateNew()
                                           .With(e => e.Id = 0)
-                                          .With(e => e.SeriesId = series.Id)
+                                          .With(e => e.EpisodeFileId = 0)
+                                          .With(e => e.Monitored = false)
+                                          .With(e => e.SeriesId = _series.Id)
                                           .With(e => e.AirDateUtc = DateTime.Today.AddDays(5))
                                           .Build();
+        }
 
+        private void GivenEpisodeWithFile()
+        {
+            _episode.EpisodeFileId = 1;
+        }
+
+        private void GivenMonitoredEpisode()
+        {
+            _episode.Monitored = true;
+        }
+
+        private void GivenFile()
+        {
             Db.Insert(_episode);
         }
 
         [Test]
         public void should_get_stats_for_series()
         {
+            GivenMonitoredEpisode();
+            GivenFile();
+
             var stats = Subject.SeriesStatistics();
 
             stats.Should().HaveCount(1);
             stats.First().NextAiring.Should().Be(_episode.AirDateUtc);
         }
+
+        [Test]
+        public void should_not_have_next_airing_for_episode_with_file()
+        {
+            GivenEpisodeWithFile();
+            GivenFile();
+
+            var stats = Subject.SeriesStatistics();
+
+            stats.Should().HaveCount(1);
+            stats.First().NextAiring.Should().NotHaveValue();
+        }
+
+        [Test]
+        public void should_not_include_unmonitored_episode_in_episode_count()
+        {
+            GivenFile();
+
+            var stats = Subject.SeriesStatistics();
+
+            stats.Should().HaveCount(1);
+            stats.First().EpisodeCount.Should().Be(0);
+        }
+
+        [Test]
+        public void should_include_unmonitored_episode_with_file_in_episode_count()
+        {
+            GivenEpisodeWithFile();
+            GivenFile();
+
+            var stats = Subject.SeriesStatistics();
+
+            stats.Should().HaveCount(1);
+            stats.First().EpisodeCount.Should().Be(1);
+        }
     }
 }
diff --git a/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs b/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs
index 496bcf77e..e08d77b23 100644
--- a/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs
+++ b/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs
@@ -14,20 +14,24 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
         private Series _monitoredSeries;
         private Series _unmonitoredSeries;
         private PagingSpec<Episode> _pagingSpec;
-            
+
         [SetUp]
         public void Setup()
         {
             _monitoredSeries = Builder<Series>.CreateNew()
                                         .With(s => s.Id = 0)
+                                        .With(s => s.TvRageId = RandomNumber)
                                         .With(s => s.Runtime = 30)
                                         .With(s => s.Monitored = true)
+                                        .With(s => s.TitleSlug = "Title3")
                                         .Build();
 
             _unmonitoredSeries = Builder<Series>.CreateNew()
                                         .With(s => s.Id = 0)
+                                        .With(s => s.TvdbId = RandomNumber)
                                         .With(s => s.Runtime = 30)
                                         .With(s => s.Monitored = false)
+                                        .With(s => s.TitleSlug = "Title2")
                                         .Build();
 
             _monitoredSeries.Id = Db.Insert(_monitoredSeries).Id;
@@ -44,6 +48,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
             var monitoredSeriesEpisodes = Builder<Episode>.CreateListOfSize(3)
                                            .All()
                                            .With(e => e.Id = 0)
+                                           .With(e => e.TvDbEpisodeId = RandomNumber)
                                            .With(e => e.SeriesId = _monitoredSeries.Id)
                                            .With(e => e.EpisodeFileId = 0)
                                            .With(e => e.AirDateUtc = DateTime.Now.AddDays(-5))
@@ -57,6 +62,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
             var unmonitoredSeriesEpisodes = Builder<Episode>.CreateListOfSize(3)
                                            .All()
                                            .With(e => e.Id = 0)
+                                            .With(e => e.TvDbEpisodeId = RandomNumber)
                                            .With(e => e.SeriesId = _unmonitoredSeries.Id)
                                            .With(e => e.EpisodeFileId = 0)
                                            .With(e => e.AirDateUtc = DateTime.Now.AddDays(-5))
@@ -67,6 +73,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
                                            .With(e => e.SeasonNumber = 0)
                                            .Build();
 
+
             Db.InsertMany(monitoredSeriesEpisodes);
             Db.InsertMany(unmonitoredSeriesEpisodes);
         }
diff --git a/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs b/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs
index 428568e8d..a3cd0b10a 100644
Binary files a/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs and b/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs differ
diff --git a/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs b/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs
deleted file mode 100644
index efb11c7bb..000000000
--- a/NzbDrone.Core.Test/TvTests/SeasonProviderTest.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System.Linq;
-using FizzWare.NBuilder;
-using FluentAssertions;
-using NUnit.Framework;
-using NzbDrone.Core.Tv;
-using NzbDrone.Core.Test.Framework;
-
-namespace NzbDrone.Core.Test.TvTests
-{
-    public class SeasonProviderTest : DbTest<SeasonRepository, Season>
-    {
-        [TestCase(true)]
-        [TestCase(false)]
-        public void Ismonitored_should_return_monitored_status_of_season(bool monitored)
-        {
-            var fakeSeason = Builder<Season>.CreateNew()
-                                            .With(s => s.Monitored = monitored)
-                                            .BuildNew<Season>();
-
-            Db.Insert(fakeSeason);
-
-            var result = Subject.IsMonitored(fakeSeason.SeriesId, fakeSeason.SeasonNumber);
-
-            result.Should().Be(monitored);
-        }
-
-        [Test]
-        public void Monitored_should_return_true_if_not_in_db()
-        {
-            Subject.IsMonitored(10, 0).Should().BeTrue();
-        }
-
-        [Test]
-        public void GetSeason_should_return_seasons_for_specified_series_only()
-        {
-            var seriesA = new[] { 1, 2, 3 };
-            var seriesB = new[] { 4, 5, 6 };
-
-            var seasonsA = seriesA.Select(c => new Season {SeasonNumber = c, SeriesId = 1}).ToList();
-            var seasonsB = seriesB.Select(c => new Season {SeasonNumber = c, SeriesId = 2}).ToList();
-
-            Subject.InsertMany(seasonsA);
-            Subject.InsertMany(seasonsB);
-
-            Subject.GetSeasonNumbers(1).Should().Equal(seriesA);
-            Subject.GetSeasonNumbers(2).Should().Equal(seriesB);
-        }
-
-
-        [Test]
-        public void GetSeason_should_return_emptylist_if_series_doesnt_exist()
-        {
-            Subject.GetSeasonNumbers(1).Should().BeEmpty();
-        }
-
-    }
-}
diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs
deleted file mode 100644
index d31c2b679..000000000
--- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/HandleEpisodeInfoDeletedEventFixture.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using FizzWare.NBuilder;
-using Moq;
-using NUnit.Framework;
-using NzbDrone.Core.Test.Framework;
-using NzbDrone.Core.Tv;
-using NzbDrone.Core.Tv.Events;
-
-namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests
-{
-    [TestFixture]
-    public class HandleEpisodeInfoDeletedEventFixture : CoreTest<SeasonService>
-    {
-        private List<Season> _seasons;
-        private List<Episode> _episodes;
-
-        [SetUp]
-        public void Setup()
-        {
-            _seasons = Builder<Season>
-                .CreateListOfSize(1)
-                .All()
-                .With(s => s.SeriesId = 1)
-                .Build()
-                .ToList();
-
-            _episodes = Builder<Episode>
-                .CreateListOfSize(1)
-                .All()
-                .With(e => e.SeasonNumber = _seasons.First().SeasonNumber)
-                .With(s => s.SeriesId = _seasons.First().SeasonNumber)
-                .Build()
-                .ToList();
-
-            Mocker.GetMock<ISeasonRepository>()
-                  .Setup(s => s.GetSeasonBySeries(It.IsAny<int>()))
-                  .Returns(_seasons);
-
-            Mocker.GetMock<IEpisodeService>()
-                .Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), _seasons.First().SeasonNumber))
-                .Returns(_episodes);
-        }
-
-        private void GivenAbandonedSeason()
-        {
-            Mocker.GetMock<IEpisodeService>()
-                .Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), _seasons.First().SeasonNumber))
-                .Returns(new List<Episode>());
-        }
-
-        [Test]
-        public void should_not_delete_when_season_is_still_valid()
-        {           
-            Subject.Handle(new EpisodeInfoDeletedEvent(_episodes));
-
-            Mocker.GetMock<ISeasonRepository>()
-                .Verify(v => v.Delete(It.IsAny<Season>()), Times.Never());
-        }
-
-        [Test]
-        public void should_delete_season_if_no_episodes_exist_in_that_season()
-        {
-            GivenAbandonedSeason();
-
-            Subject.Handle(new EpisodeInfoDeletedEvent(_episodes));
-
-            Mocker.GetMock<ISeasonRepository>()
-                .Verify(v => v.Delete(It.IsAny<Season>()), Times.Once());
-        }
-
-        [Test]
-        public void should_only_delete_a_season_once()
-        {
-            _episodes = Builder<Episode>
-                .CreateListOfSize(5)
-                .All()
-                .With(e => e.SeasonNumber = _seasons.First().SeasonNumber)
-                .With(s => s.SeriesId = _seasons.First().SeasonNumber)
-                .Build()
-                .ToList();
-
-            GivenAbandonedSeason();
-
-            Subject.Handle(new EpisodeInfoDeletedEvent(_episodes));
-
-            Mocker.GetMock<ISeasonRepository>()
-                .Verify(v => v.Delete(It.IsAny<Season>()), Times.Once());
-        }
-    }
-}
diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs
deleted file mode 100644
index 775d7d41c..000000000
--- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetMonitoredFixture.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Moq;
-using NUnit.Framework;
-using NzbDrone.Core.Test.Framework;
-using NzbDrone.Core.Tv;
-
-namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests
-{
-    [TestFixture]
-    public class SetMonitoredFixture : CoreTest<SeasonService>
-    {
-        private Season _season;
-        
-        [SetUp]
-        public void Setup()
-        {
-            _season = new Season
-            {
-                Id = 1,
-                SeasonNumber = 1,
-                SeriesId = 1,
-                Monitored = false
-            };
-
-            Mocker.GetMock<ISeasonRepository>()
-                  .Setup(s => s.Get(It.IsAny<Int32>(), It.IsAny<Int32>()))
-                  .Returns(_season);
-        }
-
-        [TestCase(true)]
-        [TestCase(false)]
-        public void should_update_season(bool monitored)
-        {
-            Subject.SetMonitored(_season.SeriesId, _season.SeasonNumber, monitored);
-
-            Mocker.GetMock<ISeasonRepository>()
-                .Verify(v => v.Update(_season), Times.Once());
-        }
-
-        [TestCase(true)]
-        [TestCase(false)]
-        public void should_update_episodes_in_season(bool monitored)
-        {
-            Subject.SetMonitored(_season.SeriesId, _season.SeasonNumber, monitored);
-
-            Mocker.GetMock<IEpisodeService>()
-                .Verify(v => v.SetEpisodeMonitoredBySeason(_season.SeriesId, _season.SeasonNumber, monitored), Times.Once());
-        }
-    }
-}
diff --git a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs b/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs
deleted file mode 100644
index b8ac4a9ea..000000000
--- a/NzbDrone.Core.Test/TvTests/SeasonServiceTests/SetSeasonPassFixture.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using FizzWare.NBuilder;
-using FluentAssertions;
-using Moq;
-using NUnit.Framework;
-using NzbDrone.Core.Test.Framework;
-using NzbDrone.Core.Tv;
-
-namespace NzbDrone.Core.Test.TvTests.SeasonServiceTests
-{
-    [TestFixture]
-    public class SetSeasonPassFixture : CoreTest<SeasonService>
-    {
-        private const Int32 SERIES_ID = 1;
-        private List<Season> _seasons;
-        
-        [SetUp]
-        public void Setup()
-        {
-            _seasons = Builder<Season>.CreateListOfSize(5)
-                .All()
-                .With(s => s.SeriesId = SERIES_ID)
-                .Build()
-                .ToList();
-
-            Mocker.GetMock<ISeasonRepository>()
-                  .Setup(s => s.GetSeasonBySeries(It.IsAny<Int32>()))
-                  .Returns(_seasons);
-        }
-
-        [Test]
-        public void should_updateMany()
-        {
-            Subject.SetSeasonPass(SERIES_ID, 1);
-
-            Mocker.GetMock<ISeasonRepository>()
-                .Verify(v => v.UpdateMany(It.IsAny<List<Season>>()), Times.Once());
-        }
-
-        [Test]
-        public void should_set_lower_seasons_to_false()
-        {
-            const int seasonNumber = 3;
-
-            var result = Subject.SetSeasonPass(SERIES_ID, seasonNumber);
-
-            result.Where(s => s.SeasonNumber < seasonNumber).Should().OnlyContain(s => s.Monitored == false);
-        }
-
-        [Test]
-        public void should_set_equal_or_higher_seasons_to_false()
-        {
-            const int seasonNumber = 3;
-
-            var result = Subject.SetSeasonPass(SERIES_ID, seasonNumber);
-
-            result.Where(s => s.SeasonNumber >= seasonNumber).Should().OnlyContain(s => s.Monitored == true);
-        }
-
-        [Test]
-        public void should_set_episodes_in_lower_seasons_to_false()
-        {
-            const int seasonNumber = 3;
-
-            Subject.SetSeasonPass(SERIES_ID, seasonNumber);
-
-            Mocker.GetMock<IEpisodeService>()
-                .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i < seasonNumber), false), Times.AtLeastOnce());
-
-            Mocker.GetMock<IEpisodeService>()
-                .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i < seasonNumber), true), Times.Never());
-        }
-
-        [Test]
-        public void should_set_episodes_in_equal_or_higher_seasons_to_false()
-        {
-            const int seasonNumber = 3;
-
-            Subject.SetSeasonPass(SERIES_ID, seasonNumber);
-
-            Mocker.GetMock<IEpisodeService>()
-                .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i >= seasonNumber), true), Times.AtLeastOnce());
-
-            Mocker.GetMock<IEpisodeService>()
-                .Verify(v => v.SetEpisodeMonitoredBySeason(SERIES_ID, It.Is<Int32>(i => i >= seasonNumber), false), Times.Never());
-        }
-    }
-}
diff --git a/NzbDrone.Core.Test/TvTests/SeriesServiceFixture.cs b/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs
similarity index 75%
rename from NzbDrone.Core.Test/TvTests/SeriesServiceFixture.cs
rename to NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs
index 99ead2647..ab33ff072 100644
Binary files a/NzbDrone.Core.Test/TvTests/SeriesServiceFixture.cs and b/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs differ
diff --git a/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs b/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs
new file mode 100644
index 000000000..903e98102
Binary files /dev/null and b/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs differ
diff --git a/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs b/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs
index 138e3b404..aa58b0bbd 100644
--- a/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs
+++ b/NzbDrone.Core.Test/UpdateTests/UpdatePackageProviderFixture.cs
@@ -1,22 +1,25 @@
+using System;
 using FluentAssertions;
 using NUnit.Framework;
-using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Update;
-using System.Linq;
 
 namespace NzbDrone.Core.Test.UpdateTests
 {
     public class UpdatePackageProviderFixture : CoreTest<UpdatePackageProvider>
     {
         [Test]
-        public void should_get_list_of_available_updates()
+        public void no_update_when_version_higher()
         {
             UseRealHttp();
+            Subject.GetLatestUpdate("master", new Version(10,0)).Should().BeNull();
+        }
 
-            Mocker.GetMock<IConfigFileProvider>().SetupGet(c => c.Branch).Returns("master");
-
-            Subject.GetLatestUpdate().Should().BeNull();
+        [Test]
+        public void finds_update_when_version_lower()
+        {
+            UseRealHttp();
+            Subject.GetLatestUpdate("master", new Version(1, 0)).Should().NotBeNull();
         }
     }
 }
diff --git a/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs
index 2a485146c..31ebee3e5 100644
--- a/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs
+++ b/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs
@@ -1,5 +1,4 @@
 using System;
-using System.Diagnostics;
 using System.IO;
 using FluentAssertions;
 using Moq;
diff --git a/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/NzbDrone.Core/Configuration/ConfigFileProvider.cs
index fa694700d..8389ec658 100644
--- a/NzbDrone.Core/Configuration/ConfigFileProvider.cs
+++ b/NzbDrone.Core/Configuration/ConfigFileProvider.cs
@@ -2,13 +2,16 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Xml;
 using System.Xml.Linq;
 using NzbDrone.Common;
 using NzbDrone.Common.Cache;
 using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Configuration.Events;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Configuration
 {
@@ -24,24 +27,23 @@ namespace NzbDrone.Core.Configuration
         string Password { get; }
         string LogLevel { get; }
         string Branch { get; }
+        bool Torrent { get; }
     }
 
     public class ConfigFileProvider : IConfigFileProvider
     {
         private const string CONFIG_ELEMENT_NAME = "Config";
 
-        private readonly IAppFolderInfo _appFolderInfo;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly ICached<string> _cache;
 
         private readonly string _configFile;
 
-        public ConfigFileProvider(IAppFolderInfo appFolderInfo, ICacheManger cacheManger, IMessageAggregator messageAggregator)
+        public ConfigFileProvider(IAppFolderInfo appFolderInfo, ICacheManger cacheManger, IEventAggregator eventAggregator)
         {
-            _appFolderInfo = appFolderInfo;
             _cache = cacheManger.GetCache<string>(GetType());
-            _messageAggregator = messageAggregator;
-            _configFile = _appFolderInfo.GetConfigPath();
+            _eventAggregator = eventAggregator;
+            _configFile = appFolderInfo.GetConfigPath();
         }
 
         public Dictionary<string, object> GetConfigDictionary()
@@ -81,7 +83,7 @@ namespace NzbDrone.Core.Configuration
                 }
             }
 
-            _messageAggregator.PublishEvent(new ConfigFileSavedEvent());
+            _eventAggregator.PublishEvent(new ConfigFileSavedEvent());
         }
 
         public int Port
@@ -94,6 +96,11 @@ namespace NzbDrone.Core.Configuration
             get { return GetValueBoolean("LaunchBrowser", true); }
         }
 
+        public bool Torrent
+        {
+            get { return GetValueBoolean("Torrent", false, persist: false); }
+        }
+
         public bool AuthenticationEnabled
         {
             get { return GetValueBoolean("AuthenticationEnabled", false); }
@@ -124,9 +131,9 @@ namespace NzbDrone.Core.Configuration
             return Convert.ToInt32(GetValue(key, defaultValue));
         }
 
-        public bool GetValueBoolean(string key, bool defaultValue)
+        public bool GetValueBoolean(string key, bool defaultValue, bool persist = true)
         {
-            return Convert.ToBoolean(GetValue(key, defaultValue));
+            return Convert.ToBoolean(GetValue(key, defaultValue, persist));
         }
 
         public T GetValueEnum<T>(string key, T defaultValue)
@@ -134,13 +141,13 @@ namespace NzbDrone.Core.Configuration
             return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), true);
         }
 
-        public string GetValue(string key, object defaultValue)
+        public string GetValue(string key, object defaultValue, bool persist = true)
         {
             return _cache.Get(key, () =>
                 {
                     EnsureDefaultConfigFile();
 
-                    var xDoc = XDocument.Load(_configFile);
+                    var xDoc = LoadConfigFile();
                     var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
 
                     var parentContainer = config;
@@ -151,7 +158,10 @@ namespace NzbDrone.Core.Configuration
                         return valueHolder.First().Value;
 
                     //Save the value
-                    SetValue(key, defaultValue);
+                    if (persist)
+                    {
+                        SetValue(key, defaultValue);
+                    }
 
                     //return the default value
                     return defaultValue.ToString();
@@ -162,7 +172,7 @@ namespace NzbDrone.Core.Configuration
         {
             EnsureDefaultConfigFile();
 
-            var xDoc = XDocument.Load(_configFile);
+            var xDoc = LoadConfigFile();
             var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
 
             var parentContainer = config;
@@ -203,7 +213,7 @@ namespace NzbDrone.Core.Configuration
         {
             EnsureDefaultConfigFile();
 
-            var xDoc = XDocument.Load(_configFile);
+            var xDoc = LoadConfigFile();
             var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
 
             var type = GetType();
@@ -222,6 +232,19 @@ namespace NzbDrone.Core.Configuration
             xDoc.Save(_configFile);
         }
 
+        private XDocument LoadConfigFile()
+        {
+            try
+            {
+                return XDocument.Load(_configFile);
+            }
+
+            catch (XmlException ex)
+            {
+                throw new InvalidConfigFileException(_configFile + " is invalid, please see the http://wiki.nzbdrone.com for steps to resolve this issue.", ex);
+            }
+        }
+
         public void HandleAsync(ApplicationStartedEvent message)
         {
             DeleteOldValues();
diff --git a/NzbDrone.Core/Configuration/ConfigRepository.cs b/NzbDrone.Core/Configuration/ConfigRepository.cs
index b0665445e..0b8c3fffb 100644
--- a/NzbDrone.Core/Configuration/ConfigRepository.cs
+++ b/NzbDrone.Core/Configuration/ConfigRepository.cs
@@ -1,6 +1,8 @@
 using System.Linq;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Configuration
 {
@@ -12,8 +14,8 @@ namespace NzbDrone.Core.Configuration
 
     public class ConfigRepository : BasicRepository<Config>, IConfigRepository
     {
-        public ConfigRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public ConfigRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/Configuration/ConfigService.cs b/NzbDrone.Core/Configuration/ConfigService.cs
index e3ca0317b..1e1af6182 100644
--- a/NzbDrone.Core/Configuration/ConfigService.cs
+++ b/NzbDrone.Core/Configuration/ConfigService.cs
@@ -2,11 +2,13 @@
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Configuration.Events;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.Download.Clients.Nzbget;
 using NzbDrone.Core.Download.Clients.Sabnzbd;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Configuration
 {
@@ -18,14 +20,14 @@ namespace NzbDrone.Core.Configuration
     public class ConfigService : IConfigService
     {
         private readonly IConfigRepository _repository;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly Logger _logger;
         private static Dictionary<string, string> _cache;
 
-        public ConfigService(IConfigRepository repository, IMessageAggregator messageAggregator, Logger logger)
+        public ConfigService(IConfigRepository repository, IEventAggregator eventAggregator, Logger logger)
         {
             _repository = repository;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _logger = logger;
             _cache = new Dictionary<string, string>();
         }
@@ -68,7 +70,7 @@ namespace NzbDrone.Core.Configuration
                     SetValue(configValue.Key, configValue.Value.ToString());
             }
 
-            _messageAggregator.PublishEvent(new ConfigSavedEvent());
+            _eventAggregator.PublishEvent(new ConfigSavedEvent());
         }
 
         public String SabHost
diff --git a/NzbDrone.Core/Configuration/InvalidConfigFileException.cs b/NzbDrone.Core/Configuration/InvalidConfigFileException.cs
new file mode 100644
index 000000000..7069749ae
--- /dev/null
+++ b/NzbDrone.Core/Configuration/InvalidConfigFileException.cs
@@ -0,0 +1,12 @@
+using System;
+using NzbDrone.Common.Exceptions;
+
+namespace NzbDrone.Core.Configuration
+{
+    public class InvalidConfigFileException : NzbDroneException
+    {
+        public InvalidConfigFileException(string message, Exception innerException) : base(message, innerException)
+        {
+        }
+    }
+}
diff --git a/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs b/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs
index f59ff9ee9..b5977e053 100644
--- a/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs
+++ b/NzbDrone.Core/DataAugmentation/DailySeries/DailySeriesDataProxy.cs
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries
         {
             try
             {
-                var dailySeriesIds = _httpProvider.DownloadString(Services.RootUrl + "/DailySeries/AllIds");
+                var dailySeriesIds = _httpProvider.DownloadString(Services.RootUrl + "/v1/DailySeries");
 
                 var seriesIds = Json.Deserialize<List<int>>(dailySeriesIds);
 
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries
         {
             try
             {
-                var result = _httpProvider.DownloadString(Services.RootUrl + "/DailySeries/Check?seriesId=" + tvdbid);
+                var result = _httpProvider.DownloadString(Services.RootUrl + "/v1/DailySeries?seriesId=" + tvdbid);
                 return Convert.ToBoolean(result);
             }
             catch (Exception ex)
diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs
index 011096f15..07bdfc065 100644
--- a/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs
+++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs
@@ -5,15 +5,15 @@ namespace NzbDrone.Core.DataAugmentation.Scene
 {
     public class SceneMapping : ModelBase
     {
-        [JsonProperty("CleanTitle")]
+        [JsonProperty("title")]
         public string ParseTerm { get; set; }
 
-        [JsonProperty("Title")]
+        [JsonProperty("searchTitle")]
         public string SearchTerm { get; set; }
 
-        [JsonProperty("Id")]
         public int TvdbId { get; set; }
 
+        [JsonProperty("season")]
         public int SeasonNumber { get; set; }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs
index 2447a96ec..9bda36efc 100644
--- a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs
+++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingProxy.cs
@@ -1,7 +1,6 @@
 using System.Collections.Generic;
 using NzbDrone.Common;
 using NzbDrone.Common.Serializer;
-using NzbDrone.Core.Configuration;
 
 namespace NzbDrone.Core.DataAugmentation.Scene
 {
@@ -21,7 +20,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene
 
         public List<SceneMapping> Fetch()
         {
-            var mappingsJson = _httpProvider.DownloadString(Services.RootUrl + "/SceneMapping/Active");
+            var mappingsJson = _httpProvider.DownloadString(Services.RootUrl + "/v1/SceneMapping");
             return Json.Deserialize<List<SceneMapping>>(mappingsJson);
         }
     }
diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingRepository.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingRepository.cs
index 417b6cf11..5f1ade5c2 100644
--- a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingRepository.cs
+++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingRepository.cs
@@ -1,5 +1,7 @@
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.DataAugmentation.Scene
 {
@@ -10,8 +12,8 @@ namespace NzbDrone.Core.DataAugmentation.Scene
 
     public class SceneMappingRepository : BasicRepository<SceneMapping>, ISceneMappingRepository
     {
-        public SceneMappingRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public SceneMappingRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs
index 3c84d378c..d05dd69fd 100644
--- a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs
+++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs
@@ -2,8 +2,10 @@ using System;
 using System.Linq;
 using NLog;
 using NzbDrone.Common.Cache;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Parser;
 
 namespace NzbDrone.Core.DataAugmentation.Scene
@@ -18,7 +20,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene
         IHandleAsync<ApplicationStartedEvent>,
         IExecute<UpdateSceneMappingCommand>
     {
-
         private readonly ISceneMappingRepository _repository;
         private readonly ISceneMappingProxy _sceneMappingProxy;
         private readonly Logger _logger;
@@ -44,7 +45,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene
             return mapping.SearchTerm;
         }
 
-
         public Nullable<Int32> GetTvDbId(string cleanName)
         {
             var mapping = _gettvdbIdCache.Find(cleanName.CleanSeriesTitle());
@@ -55,7 +55,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene
             return mapping.TvdbId;
         }
 
-
         private void UpdateMappings()
         {
             _logger.Info("Updating Scene mapping");
@@ -74,7 +73,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene
                     }
 
                     _repository.InsertMany(mappings);
-
                 }
                 else
                 {
diff --git a/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs b/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs
index 68356ab6e..215f8e033 100644
--- a/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs
+++ b/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs
@@ -1,8 +1,9 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.DataAugmentation.Scene
 {
-    public class UpdateSceneMappingCommand : ICommand
+    public class UpdateSceneMappingCommand : Command
     {
+
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/BasicRepository.cs b/NzbDrone.Core/Datastore/BasicRepository.cs
index daefd7eac..8552930fd 100644
--- a/NzbDrone.Core/Datastore/BasicRepository.cs
+++ b/NzbDrone.Core/Datastore/BasicRepository.cs
@@ -4,9 +4,10 @@ using System.Linq;
 using System.Linq.Expressions;
 using Marr.Data;
 using Marr.Data.QGen;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore.Events;
 using NzbDrone.Common;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 
 
 namespace NzbDrone.Core.Datastore
@@ -38,17 +39,17 @@ namespace NzbDrone.Core.Datastore
     public class BasicRepository<TModel> : IBasicRepository<TModel> where TModel : ModelBase, new()
     {
         private readonly IDatabase _database;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
 
         private IDataMapper DataMapper
         {
             get { return _database.GetDataMapper(); }
         }
 
-        public BasicRepository(IDatabase database, IMessageAggregator messageAggregator)
+        public BasicRepository(IDatabase database, IEventAggregator eventAggregator)
         {
             _database = database;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
         }
 
         protected QueryBuilder<TModel> Query
@@ -115,7 +116,6 @@ namespace NzbDrone.Core.Datastore
             }
 
             DataMapper.Insert(model);
-            PublishModelEvent(model, RepositoryAction.Created);
 
             return model;
         }
@@ -222,22 +222,27 @@ namespace NzbDrone.Core.Datastore
             DataMapper.Delete<TModel>(c => c.Id > 0);
         }
 
-        private void PublishModelEvent(TModel model, RepositoryAction action)
+        protected void ModelCreated(TModel model)
         {
-            if (PublishModelEvents)
-            {
-                _messageAggregator.PublishEvent(new ModelEvent<TModel>(model, action));
-            }
+            PublishModelEvent(model, ModelAction.Created);
         }
 
-        protected virtual void OnModelChanged(IEnumerable<TModel> models)
+        protected void ModelUpdated(TModel model)
         {
-
+            PublishModelEvent(model, ModelAction.Updated);
         }
 
-        protected virtual void OnModelDeleted(IEnumerable<TModel> models)
+        protected void ModelDeleted(TModel model)
         {
+            PublishModelEvent(model, ModelAction.Deleted);
+        }
 
+        private void PublishModelEvent(TModel model, ModelAction action)
+        {
+            if (PublishModelEvents)
+            {
+                _eventAggregator.PublishEvent(new ModelEvent<TModel>(model, action));
+            }
         }
 
         protected virtual bool PublishModelEvents
diff --git a/NzbDrone.Core/Datastore/CachedBasicRepository.cs b/NzbDrone.Core/Datastore/CachedBasicRepository.cs
deleted file mode 100644
index 1938af390..000000000
--- a/NzbDrone.Core/Datastore/CachedBasicRepository.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Collections.Generic;
-using NzbDrone.Common.Cache;
-using NzbDrone.Common.Messaging;
-
-namespace NzbDrone.Core.Datastore
-{
-    public abstract class CachedBasicRepository<TModel> : BasicRepository<TModel> where TModel : ModelBase, new()
-    {
-        private readonly ICacheManger _cacheManger;
-
-        protected CachedBasicRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
-        {
-            _cacheManger = new CacheManger();
-        }
-
-        protected ICached<T> GetCache<T>(string name)
-        {
-            return _cacheManger.GetCache<T>(GetType(), name);
-        }
-
-        protected override void OnModelChanged(IEnumerable<TModel> models)
-        {
-            PurgeCache();
-        }
-
-        protected override void OnModelDeleted(IEnumerable<TModel> models)
-        {
-            PurgeCache();
-        }
-
-        private void PurgeCache()
-        {
-            foreach (var model in _cacheManger.Caches)
-            {
-                model.Clear();
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/DbFactory.cs b/NzbDrone.Core/Datastore/DbFactory.cs
index fb8d43a37..537fec3b0 100644
--- a/NzbDrone.Core/Datastore/DbFactory.cs
+++ b/NzbDrone.Core/Datastore/DbFactory.cs
@@ -3,9 +3,10 @@ using System.Data.SQLite;
 using Marr.Data;
 using Marr.Data.Reflection;
 using NzbDrone.Common.Composition;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore.Migration.Framework;
 using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 
 
 namespace NzbDrone.Core.Datastore
@@ -29,12 +30,16 @@ namespace NzbDrone.Core.Datastore
 
         public static void RegisterDatabase(IContainer container)
         {
+            container.Resolve<IDbFactory>().Create();
+
             container.Register(c => c.Resolve<IDbFactory>().Create());
 
+            container.Resolve<IDbFactory>().Create(MigrationType.Log);
+
             container.Register<ILogRepository>(c =>
             {
                 var db = c.Resolve<IDbFactory>().Create(MigrationType.Log);
-                return new LogRepository(db, c.Resolve<IMessageAggregator>());
+                return new LogRepository(db, c.Resolve<IEventAggregator>());
             });
         }
 
diff --git a/NzbDrone.Core/Datastore/Events/ModelEvent.cs b/NzbDrone.Core/Datastore/Events/ModelEvent.cs
index 30b9af580..3cabd3a69 100644
--- a/NzbDrone.Core/Datastore/Events/ModelEvent.cs
+++ b/NzbDrone.Core/Datastore/Events/ModelEvent.cs
@@ -2,23 +2,25 @@
 
 namespace NzbDrone.Core.Datastore.Events
 {
-    public class ModelEvent<T> : IEvent where T : ModelBase
+    public class ModelEvent <TModel> : IEvent
     {
-        public T Model { get; set; }
-        public RepositoryAction Action { get; set; }
+        public TModel Model { get; set; }
+        public ModelAction Action { get; set; }
 
-        public ModelEvent(T model, RepositoryAction action)
+        public ModelEvent(TModel model, ModelAction action)
         {
             Model = model;
             Action = action;
         }
     }
 
-    public enum RepositoryAction
+    public enum ModelAction
     {
+        Unknow = 0,
         Created = 1,
         Updated = 2,
-        Deleted = 3
+        Deleted = 3,
+        Sync = 4
     }
 
 
diff --git a/NzbDrone.Core/Datastore/Migration/008_remove_backlog.cs b/NzbDrone.Core/Datastore/Migration/008_remove_backlog.cs
index 6614e2c14..83fd62478 100644
--- a/NzbDrone.Core/Datastore/Migration/008_remove_backlog.cs
+++ b/NzbDrone.Core/Datastore/Migration/008_remove_backlog.cs
@@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration
     {
         protected override void MainDbUpgrade()
         {
-            SQLiteAlter.DropColumns("Series", new[] { "BacklogSetting" });
-            SQLiteAlter.DropColumns("NamingConfig", new[] { "UseSceneName" });
+            SqLiteAlter.DropColumns("Series", new[] { "BacklogSetting" });
+            SqLiteAlter.DropColumns("NamingConfig", new[] { "UseSceneName" });
         }
     }
 }
diff --git a/NzbDrone.Core/Datastore/Migration/009_fix_renameEpisodes.cs b/NzbDrone.Core/Datastore/Migration/009_fix_renameEpisodes.cs
index d52799a03..f5ef5b2d1 100644
--- a/NzbDrone.Core/Datastore/Migration/009_fix_renameEpisodes.cs
+++ b/NzbDrone.Core/Datastore/Migration/009_fix_renameEpisodes.cs
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
     {
         protected override void MainDbUpgrade()
         {
-            SQLiteAlter.DropColumns("NamingConfig", new[] { "SeasonFolderFormat" });
+            SqLiteAlter.DropColumns("NamingConfig", new[] { "SeasonFolderFormat" });
 
             Execute.Sql("UPDATE NamingConfig SET RenameEpisodes = 1 WHERE RenameEpisodes = -1");
             Execute.Sql("UPDATE NamingConfig SET RenameEpisodes = 0 WHERE RenameEpisodes = -2");
diff --git a/NzbDrone.Core/Datastore/Migration/011_remove_ignored.cs b/NzbDrone.Core/Datastore/Migration/011_remove_ignored.cs
index 103f0648a..8f62c3b46 100644
--- a/NzbDrone.Core/Datastore/Migration/011_remove_ignored.cs
+++ b/NzbDrone.Core/Datastore/Migration/011_remove_ignored.cs
@@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration
     {
         protected override void MainDbUpgrade()
         {
-            SQLiteAlter.DropColumns("Episodes", new[] { "Ignored" });
-            SQLiteAlter.DropColumns("Seasons", new[] { "Ignored" });
+            SqLiteAlter.DropColumns("Episodes", new[] { "Ignored" });
+            SqLiteAlter.DropColumns("Seasons", new[] { "Ignored" });
         }
     }
 }
diff --git a/NzbDrone.Core/Datastore/Migration/012_remove_custom_start_date.cs b/NzbDrone.Core/Datastore/Migration/012_remove_custom_start_date.cs
index 417e73013..a7f156c87 100644
--- a/NzbDrone.Core/Datastore/Migration/012_remove_custom_start_date.cs
+++ b/NzbDrone.Core/Datastore/Migration/012_remove_custom_start_date.cs
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
     {
         protected override void MainDbUpgrade()
         {
-            SQLiteAlter.DropColumns("Series", new[] { "CustomStartDate" });
+            SqLiteAlter.DropColumns("Series", new[] { "CustomStartDate" });
         }
     }
 }
diff --git a/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs b/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs
index 434ccc8a2..b88282202 100644
--- a/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs
+++ b/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
     {
         protected override void MainDbUpgrade()
         {
-            SQLiteAlter.DropColumns("Episodes", new []{ "AirDate" });
+            SqLiteAlter.DropColumns("Episodes", new []{ "AirDate" });
         }
     }
 }
diff --git a/NzbDrone.Core/Datastore/Migration/018_remove_duplicates.cs b/NzbDrone.Core/Datastore/Migration/018_remove_duplicates.cs
new file mode 100644
index 000000000..7fb11e5e7
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Migration/018_remove_duplicates.cs
@@ -0,0 +1,60 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+using System.Linq;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+    [Migration(18)]
+    public class remove_duplicates : NzbDroneMigrationBase
+    {
+        protected override void MainDbUpgrade()
+        {
+            using (var transaction = MigrationHelper.BeginTransaction())
+            {
+                RemoveDuplicateSeries<int>("TvdbId");
+                RemoveDuplicateSeries<string>("TitleSlug");
+
+                var duplicatedEpisodes = MigrationHelper.GetDuplicates<int>("Episodes", "TvDbEpisodeId");
+
+                foreach (var duplicate in duplicatedEpisodes)
+                {
+                    foreach (var episodeId in duplicate.OrderBy(c => c.Key).Skip(1).Select(c => c.Key))
+                    {
+                        RemoveEpisodeRows(episodeId);
+                    }
+                }
+
+                transaction.Commit();
+            }
+        }
+
+        private void RemoveDuplicateSeries<T>(string field)
+        {
+            var duplicatedSeries = MigrationHelper.GetDuplicates<T>("Series", field);
+
+            foreach (var duplicate in duplicatedSeries)
+            {
+                foreach (var seriesId in duplicate.OrderBy(c => c.Key).Skip(1).Select(c => c.Key))
+                {
+                    RemoveSeriesRows(seriesId);
+                }
+            }
+        }
+
+        private void RemoveSeriesRows(int seriesId)
+        {
+            MigrationHelper.ExecuteNonQuery("DELETE FROM Series WHERE Id = {0}", seriesId.ToString());
+            MigrationHelper.ExecuteNonQuery("DELETE FROM Episodes WHERE SeriesId = {0}", seriesId.ToString());
+            MigrationHelper.ExecuteNonQuery("DELETE FROM Seasons WHERE SeriesId = {0}", seriesId.ToString());
+            MigrationHelper.ExecuteNonQuery("DELETE FROM History WHERE SeriesId = {0}", seriesId.ToString());
+            MigrationHelper.ExecuteNonQuery("DELETE FROM EpisodeFiles WHERE SeriesId = {0}", seriesId.ToString());
+        }
+
+        private void RemoveEpisodeRows(int episodeId)
+        {
+            MigrationHelper.ExecuteNonQuery("DELETE FROM Episodes WHERE Id = {0}", episodeId.ToString());
+            MigrationHelper.ExecuteNonQuery("DELETE FROM History WHERE EpisodeId = {0}", episodeId.ToString());
+        }
+
+    }
+}
diff --git a/NzbDrone.Core/Datastore/Migration/019_restore_unique_constraints.cs b/NzbDrone.Core/Datastore/Migration/019_restore_unique_constraints.cs
new file mode 100644
index 000000000..c4e304df7
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Migration/019_restore_unique_constraints.cs
@@ -0,0 +1,20 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+    [Migration(19)]
+    public class restore_unique_constraints : NzbDroneMigrationBase
+    {
+        protected override void MainDbUpgrade()
+        {
+            SqLiteAlter.AddIndexes("Series", 
+                new SQLiteIndex { Column = "TvdbId", Table = "Series", Unique = true },
+                new SQLiteIndex { Column = "TitleSlug", Table = "Series", Unique = true });
+
+            SqLiteAlter.AddIndexes("Episodes", 
+                new SQLiteIndex { Column = "TvDbEpisodeId", Table = "Episodes", Unique = true });
+        }
+
+    }
+}
diff --git a/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs b/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs
new file mode 100644
index 000000000..0521d3bdb
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Migration/020_add_year_and_seasons_to_series.cs
@@ -0,0 +1,69 @@
+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(20)]
+    public class add_year_and_seasons_to_series : NzbDroneMigrationBase
+    {
+        protected override void MainDbUpgrade()
+        {
+            Alter.Table("Series").AddColumn("Year").AsInt32().Nullable();
+            Alter.Table("Series").AddColumn("Seasons").AsString().Nullable();
+
+            Execute.WithConnection(ConvertSeasons);
+        }
+
+        private void ConvertSeasons(IDbConnection conn, IDbTransaction tran)
+        {
+            using (IDbCommand allSeriesCmd = conn.CreateCommand())
+            {
+                allSeriesCmd.Transaction = tran;
+                allSeriesCmd.CommandText = @"SELECT Id FROM Series";
+                using (IDataReader allSeriesReader = allSeriesCmd.ExecuteReader())
+                {
+                    while (allSeriesReader.Read())
+                    {
+                        int seriesId = allSeriesReader.GetInt32(0);
+                        var seasons = new List<dynamic>();
+
+                        using (IDbCommand seasonsCmd = conn.CreateCommand())
+                        {
+                            seasonsCmd.Transaction = tran;
+                            seasonsCmd.CommandText = String.Format(@"SELECT SeasonNumber, Monitored FROM Seasons WHERE SeriesId = {0}", seriesId);
+
+                            using (IDataReader seasonReader = seasonsCmd.ExecuteReader())
+                            {
+                                while (seasonReader.Read())
+                                {
+                                    int seasonNumber = seasonReader.GetInt32(0);
+                                    bool monitored = seasonReader.GetBoolean(1);
+
+                                    if (seasonNumber == 0)
+                                    {
+                                        monitored = false;
+                                    }
+
+                                    seasons.Add(new { seasonNumber, monitored });
+                                }
+                            }
+                        }
+
+                        using (IDbCommand updateCmd = conn.CreateCommand())
+                        {
+                            var text = String.Format("UPDATE Series SET Seasons = '{0}' WHERE Id = {1}", seasons.ToJson() , seriesId);
+
+                            updateCmd.Transaction = tran;
+                            updateCmd.CommandText = text;
+                            updateCmd.ExecuteNonQuery();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs b/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs
new file mode 100644
index 000000000..d2527c755
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Migration/021_drop_seasons_table.cs
@@ -0,0 +1,14 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+    [Migration(21)]
+    public class drop_seasons_table : NzbDroneMigrationBase
+    {
+        protected override void MainDbUpgrade()
+        {
+            Delete.Table("Seasons");
+        }
+    }
+}
diff --git a/NzbDrone.Core/Datastore/Migration/Framework/MigrationContext.cs b/NzbDrone.Core/Datastore/Migration/Framework/MigrationContext.cs
index ebf634c15..90ac77a1e 100644
--- a/NzbDrone.Core/Datastore/Migration/Framework/MigrationContext.cs
+++ b/NzbDrone.Core/Datastore/Migration/Framework/MigrationContext.cs
@@ -4,5 +4,6 @@
     {
         public MigrationType MigrationType { get; set; }
         public ISQLiteAlter SQLiteAlter { get; set; }
+        public ISqLiteMigrationHelper MigrationHelper { get; set; }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs b/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs
index e2a0480f2..a930511c3 100644
--- a/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs
+++ b/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs
@@ -15,13 +15,15 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
     {
         private readonly IAnnouncer _announcer;
         private readonly ISQLiteAlter _sqLiteAlter;
+        private readonly ISqLiteMigrationHelper _migrationHelper;
 
         private static readonly HashSet<string> MigrationCache = new HashSet<string>();
 
-        public MigrationController(IAnnouncer announcer, ISQLiteAlter sqLiteAlter)
+        public MigrationController(IAnnouncer announcer, ISQLiteAlter sqLiteAlter, ISqLiteMigrationHelper migrationHelper)
         {
             _announcer = announcer;
             _sqLiteAlter = sqLiteAlter;
+            _migrationHelper = migrationHelper;
         }
 
         public void MigrateToLatest(string connectionString, MigrationType migrationType)
@@ -40,7 +42,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
                         ApplicationContext = new MigrationContext
                             {
                                 MigrationType = migrationType,
-                                SQLiteAlter = _sqLiteAlter
+                                SQLiteAlter = _sqLiteAlter,
+                                MigrationHelper = _migrationHelper,
                             }
                     };
 
diff --git a/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneMigrationBase.cs b/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneMigrationBase.cs
index c641d5f55..da9dde57a 100644
--- a/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneMigrationBase.cs
+++ b/NzbDrone.Core/Datastore/Migration/Framework/NzbDroneMigrationBase.cs
@@ -1,9 +1,18 @@
 using System;
+using NLog;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Core.Datastore.Migration.Framework
 {
     public abstract class NzbDroneMigrationBase : FluentMigrator.Migration
     {
+        private Logger _logger;
+
+        protected NzbDroneMigrationBase()
+        {
+            _logger = NzbDroneLogger.GetLogger();
+        }
+
         protected virtual void MainDbUpgrade()
         {
         }
@@ -16,7 +25,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
         {
             var context = (MigrationContext)ApplicationContext;
 
-            SQLiteAlter = context.SQLiteAlter;
+            SqLiteAlter = context.SQLiteAlter;
+            MigrationHelper = context.MigrationHelper;
 
             switch (context.MigrationType)
             {
@@ -33,7 +43,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
             }
         }
 
-        public ISQLiteAlter SQLiteAlter { get; private set; }
+        protected ISQLiteAlter SqLiteAlter { get; private set; }
+        protected ISqLiteMigrationHelper MigrationHelper { get; private set; }
 
         public override void Down()
         {
diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteColumn.cs b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteColumn.cs
new file mode 100644
index 000000000..976916885
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteColumn.cs
@@ -0,0 +1,13 @@
+namespace NzbDrone.Core.Datastore.Migration.Framework
+{
+    public class SQLiteColumn
+    {
+        public string Name { get; set; }
+        public string Schema { get; set; }
+
+        public override string ToString()
+        {
+            return string.Format("[{0}] {1}", Name, Schema);
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs
new file mode 100644
index 000000000..cbf18dbd9
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteIndex.cs
@@ -0,0 +1,39 @@
+using System;
+
+namespace NzbDrone.Core.Datastore.Migration.Framework
+{
+    public class SQLiteIndex : IEquatable<SQLiteIndex>
+    {
+        public string Column { get; set; }
+        public string Table { get; set; }
+        public bool Unique { get; set; }
+
+        public bool Equals(SQLiteIndex other)
+        {
+            return IndexName == other.IndexName;
+        }
+
+        public override int GetHashCode()
+        {
+            return IndexName.GetHashCode();
+        }
+
+        public override string ToString()
+        {
+            return string.Format("[{0}] Unique: {1}", Column, Unique);
+        }
+
+        public string IndexName
+        {
+            get
+            {
+                return string.Format("IX_{0}_{1}", Table, Column);
+            }
+        }
+
+        public string CreateSql(string tableName)
+        {
+            return string.Format(@"CREATE UNIQUE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName);
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper (vaio's conflicted copy 2013-09-04).cs b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper (vaio's conflicted copy 2013-09-04).cs
new file mode 100644
index 000000000..0cbf55e4c
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper (vaio's conflicted copy 2013-09-04).cs	
@@ -0,0 +1,241 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SQLite;
+using System.Linq;
+using System.Text.RegularExpressions;
+using NLog;
+
+namespace NzbDrone.Core.Datastore.Migration.Framework
+{
+    public interface ISQLiteMigrationHelper
+    {
+        Dictionary<String, SQLiteMigrationHelper.SQLiteColumn> GetColumns(string tableName);
+        void CreateTable(string tableName, IEnumerable<SQLiteMigrationHelper.SQLiteColumn> values, IEnumerable<SQLiteMigrationHelper.SQLiteIndex> indexes);
+        void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteMigrationHelper.SQLiteColumn> columns);
+        void DropTable(string tableName);
+        void RenameTable(string tableName, string newName);
+        List<T> GetDuplicates<T>(string tableName, string columnName);
+        SQLiteTransaction BeginTransaction();
+        List<SQLiteMigrationHelper.SQLiteIndex> GetIndexes(string tableName);
+    }
+
+    public class SQLiteMigrationHelper : ISQLiteMigrationHelper
+    {
+        private readonly SQLiteConnection _connection;
+
+        private static readonly Regex SchemaRegex = new Regex(@"['\""\[](?<name>\w+)['\""\]]\s(?<schema>[\w-\s]+)",
+            RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
+
+        private static readonly Regex IndexRegex = new Regex(@"\(""(?<col>.*)""\s(?<direction>ASC|DESC)\)$",
+             RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
+
+        public SQLiteMigrationHelper(IConnectionStringFactory connectionStringFactory, Logger logger)
+        {
+            try
+            {
+                _connection = new SQLiteConnection(connectionStringFactory.MainDbConnectionString);
+                _connection.Open();
+            }
+            catch (Exception e)
+            {
+                logger.ErrorException("Couldn't open database " + connectionStringFactory.MainDbConnectionString, e);
+                throw;
+            }
+
+        }
+
+        private string GetOriginalSql(string tableName)
+        {
+            var command =
+                new SQLiteCommand(string.Format("SELECT sql FROM sqlite_master WHERE type='table' AND name ='{0}'",
+                                                tableName));
+
+            command.Connection = _connection;
+
+            return (string)command.ExecuteScalar();
+        }
+
+        public Dictionary<String, SQLiteColumn> GetColumns(string tableName)
+        {
+            var originalSql = GetOriginalSql(tableName);
+
+            var matches = SchemaRegex.Matches(originalSql);
+
+            return matches.Cast<Match>().ToDictionary(
+                               match => match.Groups["name"].Value.Trim(),
+                               match => new SQLiteColumn
+                                   {
+                                       Name = match.Groups["name"].Value.Trim(),
+                                       Schema = match.Groups["schema"].Value.Trim()
+                                   });
+        }
+
+
+        private static IEnumerable<T> ReadArray<T>(SQLiteDataReader reader)
+        {
+            while (reader.Read())
+            {
+                yield return (T)Convert.ChangeType(reader[0], typeof(T));
+            }
+        }
+
+        public List<SQLiteIndex> GetIndexes(string tableName)
+        {
+            var command = new SQLiteCommand(string.Format("SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name ='{0}'", tableName));
+            command.Connection = _connection;
+
+            var reader = command.ExecuteReader();
+            var sqls = ReadArray<string>(reader).ToList();
+
+
+            var indexes = new List<SQLiteIndex>();
+
+            foreach (var indexSql in sqls)
+            {
+                var newIndex = new SQLiteIndex();
+                var matches = IndexRegex.Match(indexSql);
+
+                newIndex.Column = matches.Groups["col"].Value;
+                newIndex.Unique = indexSql.Contains("UNIQUE");
+                newIndex.Table = tableName;
+
+                indexes.Add(newIndex);
+            }
+
+            return indexes;
+        }
+
+        public void CreateTable(string tableName, IEnumerable<SQLiteColumn> values, IEnumerable<SQLiteIndex> indexes)
+        {
+            var columns = String.Join(",", values.Select(c => c.ToString()));
+
+            ExecuteNonQuery("CREATE TABLE [{0}] ({1})", tableName, columns);
+
+            foreach (var index in indexes)
+            {
+                ExecuteNonQuery("DROP INDEX {0}", index.IndexName);
+                ExecuteNonQuery(index.CreateSql(tableName));
+            }
+        }
+
+        public void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteColumn> columns)
+        {
+            var originalCount = GetRowCount(sourceTable);
+
+            var columnsToTransfer = String.Join(",", columns.Select(c => c.Name));
+
+            var transferCommand = BuildCommand("INSERT INTO {0} SELECT {1} FROM {2};", destinationTable, columnsToTransfer, sourceTable);
+
+            transferCommand.ExecuteNonQuery();
+
+            var transferredRows = GetRowCount(destinationTable);
+
+
+            if (transferredRows != originalCount)
+            {
+                throw new ApplicationException(string.Format("Expected {0} rows to be copied from [{1}] to [{2}]. But only copied {3}", originalCount, sourceTable, destinationTable, transferredRows));
+            }
+        }
+
+
+        public void DropTable(string tableName)
+        {
+            var dropCommand = BuildCommand("DROP TABLE {0};", tableName);
+            dropCommand.ExecuteNonQuery();
+        }
+
+
+        public void RenameTable(string tableName, string newName)
+        {
+            var renameCommand = BuildCommand("ALTER TABLE {0} RENAME TO {1};", tableName, newName);
+            renameCommand.ExecuteNonQuery();
+        }
+
+        public Dictionary<int,T> GetDuplicates<T>(string tableName, string columnName)
+        {
+            var dupCommand = BuildCommand("select id, {0} from {1}", columnName, tableName);
+
+            var result = new Dictionary<int, T>();
+            using (var reader = dupCommand.ExecuteReader())
+            {
+                while (reader.Read())
+                {
+                    
+                }
+            }
+
+            
+            return ReadArray<T>().ToList();
+        }
+
+        public int GetRowCount(string tableName)
+        {
+            var countCommand = BuildCommand("SELECT COUNT(*) FROM {0};", tableName);
+            return Convert.ToInt32(countCommand.ExecuteScalar());
+        }
+
+
+        public SQLiteTransaction BeginTransaction()
+        {
+            return _connection.BeginTransaction();
+        }
+
+        private SQLiteCommand BuildCommand(string format, params string[] args)
+        {
+            var command = new SQLiteCommand(string.Format(format, args));
+            command.Connection = _connection;
+            return command;
+        }
+
+
+
+        private void ExecuteNonQuery(string command, params string[] args)
+        {
+            var sqLiteCommand = new SQLiteCommand(string.Format(command, args))
+            {
+                Connection = _connection
+            };
+
+            sqLiteCommand.ExecuteNonQuery();
+        }
+
+
+        public class SQLiteColumn
+        {
+            public string Name { get; set; }
+            public string Schema { get; set; }
+
+            public override string ToString()
+            {
+                return string.Format("[{0}] {1}", Name, Schema);
+            }
+        }
+
+        public class SQLiteIndex
+        {
+            public string Column { get; set; }
+            public string Table { get; set; }
+            public bool Unique { get; set; }
+
+            public override string ToString()
+            {
+                return string.Format("[{0}] Unique: {1}", Column, Unique);
+            }
+
+            public string IndexName
+            {
+                get
+                {
+                    return string.Format("IX_{0}_{1}", Table, Column);
+                }
+            }
+
+            public string CreateSql(string tableName)
+            {
+                return string.Format(@"CREATE UNIQUE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName);
+            }
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs
index 80b4e25e8..42f8f8e7b 100644
--- a/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs
+++ b/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs
@@ -4,28 +4,35 @@ using System.Data.SQLite;
 using System.Linq;
 using System.Text.RegularExpressions;
 using NLog;
+using NzbDrone.Common.Exceptions;
 
 namespace NzbDrone.Core.Datastore.Migration.Framework
 {
-    public interface ISQLiteMigrationHelper
+    public interface ISqLiteMigrationHelper
     {
-        Dictionary<String, SQLiteMigrationHelper.SQLiteColumn> GetColumns(string tableName);
-        void CreateTable(string tableName, IEnumerable<SQLiteMigrationHelper.SQLiteColumn> values);
-        void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteMigrationHelper.SQLiteColumn> columns);
-        int GetRowCount(string tableName);
+        Dictionary<String, SQLiteColumn> GetColumns(string tableName);
+        void CreateTable(string tableName, IEnumerable<SQLiteColumn> values, IEnumerable<SQLiteIndex> indexes);
+        void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteColumn> columns);
         void DropTable(string tableName);
         void RenameTable(string tableName, string newName);
+        IEnumerable<IGrouping<T, KeyValuePair<int, T>>> GetDuplicates<T>(string tableName, string columnName);
         SQLiteTransaction BeginTransaction();
+        List<SQLiteIndex> GetIndexes(string tableName);
+        int ExecuteScalar(string command, params string[] args);
+        void ExecuteNonQuery(string command, params string[] args);
     }
 
-    public class SQLiteMigrationHelper : ISQLiteMigrationHelper
+    public class SqLiteMigrationHelper : ISqLiteMigrationHelper
     {
         private readonly SQLiteConnection _connection;
 
         private static readonly Regex SchemaRegex = new Regex(@"['\""\[](?<name>\w+)['\""\]]\s(?<schema>[\w-\s]+)",
             RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
 
-        public SQLiteMigrationHelper(IConnectionStringFactory connectionStringFactory,Logger logger)
+        private static readonly Regex IndexRegex = new Regex(@"\(""(?<col>.*)""\s(?<direction>ASC|DESC)\)$",
+             RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
+
+        public SqLiteMigrationHelper(IConnectionStringFactory connectionStringFactory, Logger logger)
         {
             try
             {
@@ -34,7 +41,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
             }
             catch (Exception e)
             {
-                logger.ErrorException("Couldn't open databse " + connectionStringFactory.MainDbConnectionString, e);
+                logger.ErrorException("Couldn't open database " + connectionStringFactory.MainDbConnectionString, e);
                 throw;
             }
 
@@ -47,7 +54,15 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
                                                 tableName));
 
             command.Connection = _connection;
-            return (string)command.ExecuteScalar();
+
+            var sql = (string)command.ExecuteScalar();
+
+            if (string.IsNullOrWhiteSpace(sql))
+            {
+                throw new TableNotFoundException(tableName);
+            }
+
+            return sql;
         }
 
         public Dictionary<String, SQLiteColumn> GetColumns(string tableName)
@@ -65,14 +80,52 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
                                    });
         }
 
-        public void CreateTable(string tableName, IEnumerable<SQLiteColumn> values)
+
+        private static IEnumerable<T> ReadArray<T>(SQLiteDataReader reader)
         {
-            var columns = String.Join(",", values.Select(c => c.ToString()));
+            while (reader.Read())
+            {
+                yield return (T)Convert.ChangeType(reader[0], typeof(T));
+            }
+        }
 
-            var command = new SQLiteCommand(string.Format("CREATE TABLE [{0}] ({1})", tableName, columns));
+        public List<SQLiteIndex> GetIndexes(string tableName)
+        {
+            var command = new SQLiteCommand(string.Format("SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name ='{0}'", tableName));
             command.Connection = _connection;
 
-            command.ExecuteNonQuery();
+            var reader = command.ExecuteReader();
+            var sqls = ReadArray<string>(reader).ToList();
+
+
+            var indexes = new List<SQLiteIndex>();
+
+            foreach (var indexSql in sqls)
+            {
+                var newIndex = new SQLiteIndex();
+                var matches = IndexRegex.Match(indexSql);
+
+                newIndex.Column = matches.Groups["col"].Value;
+                newIndex.Unique = indexSql.Contains("UNIQUE");
+                newIndex.Table = tableName;
+
+                indexes.Add(newIndex);
+            }
+
+            return indexes;
+        }
+
+        public void CreateTable(string tableName, IEnumerable<SQLiteColumn> values, IEnumerable<SQLiteIndex> indexes)
+        {
+            var columns = String.Join(",", values.Select(c => c.ToString()));
+
+            ExecuteNonQuery("CREATE TABLE [{0}] ({1})", tableName, columns);
+
+            foreach (var index in indexes)
+            {
+                ExecuteNonQuery("DROP INDEX IF EXISTS {0}", index.IndexName);
+                ExecuteNonQuery(index.CreateSql(tableName));
+            }
         }
 
         public void CopyData(string sourceTable, string destinationTable, IEnumerable<SQLiteColumn> columns)
@@ -108,6 +161,23 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
             renameCommand.ExecuteNonQuery();
         }
 
+        public IEnumerable<IGrouping<T, KeyValuePair<int, T>>> GetDuplicates<T>(string tableName, string columnName)
+        {
+            var getDuplicates = BuildCommand("select id, {0} from {1}", columnName, tableName);
+
+            var result = new List<KeyValuePair<int, T>>();
+
+            using (var reader = getDuplicates.ExecuteReader())
+            {
+                while (reader.Read())
+                {
+                    result.Add(new KeyValuePair<int, T>(reader.GetInt32(0), (T)Convert.ChangeType(reader[1], typeof(T))));
+                }
+            }
+
+            return result.GroupBy(c => c.Value).Where(g => g.Count() > 1);
+        }
+
         public int GetRowCount(string tableName)
         {
             var countCommand = BuildCommand("SELECT COUNT(*) FROM {0};", tableName);
@@ -128,17 +198,35 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
         }
 
 
-        public class SQLiteColumn
+        public void ExecuteNonQuery(string command, params string[] args)
         {
-            public string Name { get; set; }
-            public string Schema { get; set; }
+            var sqLiteCommand = new SQLiteCommand(string.Format(command, args))
+            {
+                Connection = _connection
+            };
+
+            sqLiteCommand.ExecuteNonQuery();
+        }
+
+        public int ExecuteScalar(string command, params string[] args)
+        {
+            var sqLiteCommand = new SQLiteCommand(string.Format(command, args))
+            {
+                Connection = _connection
+            };
 
-            public override string ToString()
+            return (int)sqLiteCommand.ExecuteScalar();
+        }
+
+        private class TableNotFoundException : NzbDroneException
+        {
+            public TableNotFoundException(string tableName)
+                : base("Table [{0}] not found", tableName)
             {
-                return string.Format("[{0}] {1}", Name, Schema);
+
             }
         }
-    }
 
 
+    }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs b/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs
index ce2372d69..c22670588 100644
--- a/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs
+++ b/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs
@@ -6,13 +6,14 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
     public interface ISQLiteAlter
     {
         void DropColumns(string tableName, IEnumerable<string> columns);
+        void AddIndexes(string tableName, params SQLiteIndex[] indexes);
     }
 
     public class SQLiteAlter : ISQLiteAlter
     {
-        private readonly ISQLiteMigrationHelper _sqLiteMigrationHelper;
+        private readonly ISqLiteMigrationHelper _sqLiteMigrationHelper;
 
-        public SQLiteAlter(ISQLiteMigrationHelper sqLiteMigrationHelper)
+        public SQLiteAlter(ISqLiteMigrationHelper sqLiteMigrationHelper)
         {
             _sqLiteMigrationHelper = sqLiteMigrationHelper;
         }
@@ -22,21 +23,44 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
             using (var transaction = _sqLiteMigrationHelper.BeginTransaction())
             {
                 var originalColumns = _sqLiteMigrationHelper.GetColumns(tableName);
+                var originalIndexes = _sqLiteMigrationHelper.GetIndexes(tableName);
 
                 var newColumns = originalColumns.Where(c => !columns.Contains(c.Key)).Select(c => c.Value).ToList();
+                var newIndexes = originalIndexes.Where(c => !columns.Contains(c.Column));
 
-                var tempTableName = tableName + "_temp";
+                CreateTable(tableName, newColumns, newIndexes);
 
-                _sqLiteMigrationHelper.CreateTable(tempTableName, newColumns);
+                transaction.Commit();
+            }
+        }
+
+        public void AddIndexes(string tableName, params SQLiteIndex[] indexes)
+        {
+            using (var transaction = _sqLiteMigrationHelper.BeginTransaction())
+            {
+                var columns = _sqLiteMigrationHelper.GetColumns(tableName).Select(c => c.Value).ToList();
+                var originalIndexes = _sqLiteMigrationHelper.GetIndexes(tableName);
 
-                _sqLiteMigrationHelper.CopyData(tableName, tempTableName, newColumns);
+                var newIndexes = originalIndexes.Union(indexes);
 
-                _sqLiteMigrationHelper.DropTable(tableName);
 
-                _sqLiteMigrationHelper.RenameTable(tempTableName, tableName);
+                CreateTable(tableName, columns, newIndexes);
 
                 transaction.Commit();
             }
         }
+
+        private void CreateTable(string tableName, List<SQLiteColumn> newColumns, IEnumerable<SQLiteIndex> newIndexes)
+        {
+            var tempTableName = tableName + "_temp";
+
+            _sqLiteMigrationHelper.CreateTable(tempTableName, newColumns, newIndexes);
+
+            _sqLiteMigrationHelper.CopyData(tableName, tempTableName, newColumns);
+
+            _sqLiteMigrationHelper.DropTable(tableName);
+
+            _sqLiteMigrationHelper.RenameTable(tempTableName, tableName);
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/TableMapping.cs b/NzbDrone.Core/Datastore/TableMapping.cs
index 80242f783..533b3ab70 100644
--- a/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/NzbDrone.Core/Datastore/TableMapping.cs
@@ -45,8 +45,6 @@ namespace NzbDrone.Core.Datastore
                   .Relationship()
                   .HasOne(s => s.QualityProfile, s => s.QualityProfileId);
 
-            Mapper.Entity<Season>().RegisterModel("Seasons");
-
             Mapper.Entity<Episode>().RegisterModel("Episodes")
                   .Ignore(e => e.SeriesTitle)
                   .Ignore(e => e.Series)
diff --git a/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs
index d94036901..f2b1404cf 100644
--- a/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs
+++ b/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs
@@ -2,8 +2,9 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Core.DecisionEngine.Specifications.Search;
+using NzbDrone.Core.DecisionEngine.Specifications;
 using NzbDrone.Core.IndexerSearch.Definitions;
+using NzbDrone.Core.Instrumentation;
 using NzbDrone.Core.Parser;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Common.Serializer;
@@ -12,8 +13,8 @@ namespace NzbDrone.Core.DecisionEngine
 {
     public interface IMakeDownloadDecision
     {
-        List<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports);
-        List<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchCriteriaBase searchCriteriaBase);
+        List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports);
+        List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase);
     }
 
     public class DownloadDecisionMaker : IMakeDownloadDecision
@@ -29,21 +30,34 @@ namespace NzbDrone.Core.DecisionEngine
             _logger = logger;
         }
 
-        public List<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports)
+        public List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports)
         {
             return GetDecisions(reports).ToList();
         }
 
-        public List<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchCriteriaBase searchCriteriaBase)
+        public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase)
         {
             return GetDecisions(reports, searchCriteriaBase).ToList();
         }
 
-        private IEnumerable<DownloadDecision> GetDecisions(IEnumerable<ReportInfo> reports, SearchCriteriaBase searchCriteria = null)
+        private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null)
         {
+            if (reports.Any())
+            {
+                _logger.ProgressInfo("Processing {0} reports", reports.Count);
+            }
+
+            else
+            {
+                _logger.ProgressInfo("No reports found");
+            }
+
+            var reportNumber = 1;
+
             foreach (var report in reports)
             {
                 DownloadDecision decision = null;
+                _logger.ProgressTrace("Processing report {0}/{1}", reportNumber, reports.Count);
 
                 try
                 {
@@ -51,8 +65,8 @@ namespace NzbDrone.Core.DecisionEngine
 
                     if (parsedEpisodeInfo != null && !string.IsNullOrWhiteSpace(parsedEpisodeInfo.SeriesTitle))
                     {
-                        var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvRageId);
-                        remoteEpisode.Report = report;
+                        var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvRageId, searchCriteria);
+                        remoteEpisode.Release = report;
 
                         if (remoteEpisode.Series != null)
                         {
@@ -69,6 +83,8 @@ namespace NzbDrone.Core.DecisionEngine
                     _logger.ErrorException("Couldn't process report.", e);
                 }
 
+                reportNumber++;
+
                 if (decision != null)
                 {
                     yield return decision;
@@ -102,13 +118,13 @@ namespace NzbDrone.Core.DecisionEngine
             }
             catch (Exception e)
             {
-                e.Data.Add("report", remoteEpisode.Report.ToJson());
+                e.Data.Add("report", remoteEpisode.Release.ToJson());
                 e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
-                _logger.ErrorException("Couldn't evaluate decision on " + remoteEpisode.Report.Title, e);
+                _logger.ErrorException("Couldn't evaluate decision on " + remoteEpisode.Release.Title, e);
                 return string.Format("{0}: {1}", spec.GetType().Name, e.Message);
             }
 
             return null;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs b/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs
index 8fbd65d1a..8bec31bc1 100644
--- a/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs
+++ b/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs
@@ -1,5 +1,4 @@
 using NLog;
-using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
@@ -7,21 +6,21 @@ namespace NzbDrone.Core.DecisionEngine
 {
     public interface IQualityUpgradableSpecification
     {
-        bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
+        bool IsUpgradable(QualityModel currentQuality, QualityModel newQuality = null);
+        bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
+        bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality);
     }
 
     public class QualityUpgradableSpecification : IQualityUpgradableSpecification
     {
-        private readonly IConfigService _configService;
         private readonly Logger _logger;
 
-        public QualityUpgradableSpecification(IConfigService configService, Logger logger)
+        public QualityUpgradableSpecification(Logger logger)
         {
-            _configService = configService;
             _logger = logger;
         }
 
-        public bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
+        public bool IsUpgradable(QualityModel currentQuality, QualityModel newQuality = null)
         {
             if (newQuality != null)
             {
@@ -31,20 +30,40 @@ namespace NzbDrone.Core.DecisionEngine
                     return false;
                 }
 
-                if (currentQuality.Quality == newQuality.Quality && newQuality.Proper && _configService.AutoDownloadPropers)
+                if (IsProperUpgrade(currentQuality, newQuality))
                 {
-                    _logger.Trace("Upgrading existing item to proper.");
                     return true;
                 }
             }
 
+            return true;
+        }
+
+        public bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
+        {
             if (currentQuality.Quality >= profile.Cutoff)
             {
+                if (newQuality != null && IsProperUpgrade(currentQuality, newQuality))
+                {
+                    return true;
+                }
+
                 _logger.Trace("Existing item meets cut-off. skipping.");
                 return false;
             }
 
             return true;
         }
+
+        public bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality)
+        {
+            if (currentQuality.Quality == newQuality.Quality && newQuality > currentQuality)
+            {
+                _logger.Trace("New quality is a proper for existing quality");
+                return true;
+            }
+
+            return false;
+        }
     }
 }
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs
index ada1817e5..1d6f5af7f 100644
--- a/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs
+++ b/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs
@@ -57,17 +57,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
             //Multiply maxSize by Series.Runtime
             maxSize = maxSize * subject.Series.Runtime * subject.Episodes.Count;
 
-            //Check if there was only one episode parsed
-            //and it is the first or last episode of the season
-            if (subject.Episodes.Count == 1 && _episodeService.IsFirstOrLastEpisodeOfSeason(subject.Episodes.Single().Id))
+            //Check if there was only one episode parsed and it is the first
+            if (subject.Episodes.Count == 1 && subject.Episodes.First().EpisodeNumber == 1)
             {
                 maxSize = maxSize * 2;
             }
 
             //If the parsed size is greater than maxSize we don't want it
-            if (subject.Report.Size > maxSize)
+            if (subject.Release.Size > maxSize)
             {
-                _logger.Trace("Item: {0}, Size: {1} is greater than maximum allowed size ({2}), rejecting.", subject, subject.Report.Size, maxSize);
+                _logger.Trace("Item: {0}, Size: {1} is greater than maximum allowed size ({2}), rejecting.", subject, subject.Release.Size, maxSize);
                 return false;
             }
 
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs
new file mode 100644
index 000000000..f43df2836
--- /dev/null
+++ b/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs
@@ -0,0 +1,43 @@
+using System.Linq;
+using NLog;
+using NzbDrone.Core.IndexerSearch.Definitions;
+using NzbDrone.Core.Parser.Model;
+
+namespace NzbDrone.Core.DecisionEngine.Specifications
+{
+    public class CutoffSpecification : IDecisionEngineSpecification
+    {
+        private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
+        private readonly Logger _logger;
+
+        public CutoffSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger)
+        {
+            _qualityUpgradableSpecification = qualityUpgradableSpecification;
+            _logger = logger;
+        }
+
+        public string RejectionReason
+        {
+            get
+            {
+                return "Cutoff has already been met";
+            }
+        }
+
+        public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
+        {
+            foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
+            {
+                _logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality);
+
+                
+                if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/NzbDrone.Core/DecisionEngine/DownloadDecision.cs b/NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs
similarity index 93%
rename from NzbDrone.Core/DecisionEngine/DownloadDecision.cs
rename to NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs
index b3037395c..22616a735 100644
--- a/NzbDrone.Core/DecisionEngine/DownloadDecision.cs
+++ b/NzbDrone.Core/DecisionEngine/Specifications/DownloadDecision.cs
@@ -2,7 +2,7 @@ using System.Collections.Generic;
 using System.Linq;
 using NzbDrone.Core.Parser.Model;
 
-namespace NzbDrone.Core.DecisionEngine
+namespace NzbDrone.Core.DecisionEngine.Specifications
 {
     public class DownloadDecision
     {
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs
index 698c1b32b..628b8183e 100644
--- a/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs
+++ b/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
         {
             get
             {
-                return "Contrains restricted term.";
+                return "Contains restricted term.";
             }
         }
 
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
 
             foreach (var restriction in restrictions)
             {
-                if (subject.Report.Title.ToLowerInvariant().Contains(restriction.ToLowerInvariant()))
+                if (subject.Release.Title.ToLowerInvariant().Contains(restriction.ToLowerInvariant()))
                 {
                     _logger.Trace("{0} is restricted: {1}", subject, restriction);
                     return false;
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs
new file mode 100644
index 000000000..afcb7149a
--- /dev/null
+++ b/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs
@@ -0,0 +1,20 @@
+using NzbDrone.Core.IndexerSearch.Definitions;
+using NzbDrone.Core.Parser.Model;
+
+namespace NzbDrone.Core.DecisionEngine.Specifications
+{
+    public class NotSampleSpecification : IDecisionEngineSpecification
+    {
+        public string RejectionReason { get { return "Sample"; } }
+
+        public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
+        {
+            if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes())
+            {
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs
index 01b692adf..a2f4dee15 100644
--- a/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs
+++ b/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
 
         public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
         {
-            var age = subject.Report.Age;
+            var age = subject.Release.Age;
 
             _logger.Trace("Checking if report meets retention requirements. {0}", age);
             if (_configService.Retention > 0 && age > _configService.Retention)
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs
new file mode 100644
index 000000000..f0a133409
--- /dev/null
+++ b/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Linq;
+using NLog;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.IndexerSearch.Definitions;
+using NzbDrone.Core.Parser.Model;
+
+namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
+{
+    public class ProperSpecification : IDecisionEngineSpecification
+    {
+        private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
+        private readonly IConfigService _configService;
+        private readonly Logger _logger;
+
+        public ProperSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, IConfigService configService, Logger logger)
+        {
+            _qualityUpgradableSpecification = qualityUpgradableSpecification;
+            _configService = configService;
+            _logger = logger;
+        }
+
+        public string RejectionReason
+        {
+            get
+            {
+                return "Proper for old episode";
+            }
+        }
+
+        public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
+        {
+            if (searchCriteria != null)
+            {
+                return true;
+            }
+
+            foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
+            {
+                if (_qualityUpgradableSpecification.IsProperUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality))
+                {
+                    if (file.DateAdded < DateTime.Today.AddDays(-7))
+                    {
+                        _logger.Trace("Proper for old file, skipping: {0}", subject);
+                        return false;
+                    }
+
+                    if (!_configService.AutoDownloadPropers)
+                    {
+                        _logger.Trace("Auto downloading of propers is disabled");
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/RssSync/UpgradeHistorySpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/RssSync/UpgradeHistorySpecification.cs
index 21611c161..e844cb91d 100644
--- a/NzbDrone.Core/DecisionEngine/Specifications/RssSync/UpgradeHistorySpecification.cs
+++ b/NzbDrone.Core/DecisionEngine/Specifications/RssSync/UpgradeHistorySpecification.cs
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
         {
             get
             {
-                return "Higher quality report exists in history";
+                return "Existing file in history is of equal or higher quality";
             }
         }
 
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
                 if (bestQualityInHistory != null)
                 {
                     _logger.Trace("Comparing history quality with report. History is {0}", bestQualityInHistory);
-                    if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
+                    if (!_qualityUpgradableSpecification.IsUpgradable(bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
                         return false;
                 }
             }
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs
index df4bb37b0..daaf3146c 100644
--- a/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs
+++ b/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
 
             if (dailySearchSpec == null) return true;
 
-            var episode = _episodeService.GetEpisode(dailySearchSpec.SeriesId, dailySearchSpec.Airtime);
+            var episode = _episodeService.GetEpisode(dailySearchSpec.Series.Id, dailySearchSpec.Airtime);
 
             if (!remoteEpisode.ParsedEpisodeInfo.AirDate.HasValue || remoteEpisode.ParsedEpisodeInfo.AirDate.Value.ToString(Episode.AIR_DATE_FORMAT) != episode.AirDate)
             {
diff --git a/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs
index 8c0f07450..004f20134 100644
--- a/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs
+++ b/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs
@@ -1,4 +1,3 @@
-using System;
 using System.Linq;
 using NLog;
 using NzbDrone.Core.IndexerSearch.Definitions;
@@ -21,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
         {
             get
             {
-                return "Higher quality exists on disk";
+                return "Existing file on disk is of equal or higher quality";
             }
         }
 
@@ -31,19 +30,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
             {
                 _logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality);
 
-                if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality))
+                if (!_qualityUpgradableSpecification.IsUpgradable(file.Quality, subject.ParsedEpisodeInfo.Quality))
                 {
                     return false;
                 }
-
-                if (searchCriteria == null &&
-                    subject.ParsedEpisodeInfo.Quality.Quality == file.Quality.Quality &&
-                    subject.ParsedEpisodeInfo.Quality.Proper &&
-                    file.DateAdded < DateTime.Today.AddDays(-7))
-                {
-                    _logger.Trace("Proper for old file, skipping: {0}", subject);
-                    return false;
-                }
             }
 
             return true;
diff --git a/NzbDrone.Core/Download/Clients/BlackholeProvider.cs b/NzbDrone.Core/Download/Clients/BlackholeProvider.cs
index 520e06943..1598ee647 100644
--- a/NzbDrone.Core/Download/Clients/BlackholeProvider.cs
+++ b/NzbDrone.Core/Download/Clients/BlackholeProvider.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.IO;
 using NLog;
 using NzbDrone.Common;
@@ -13,29 +12,20 @@ namespace NzbDrone.Core.Download.Clients
     {
         private readonly IConfigService _configService;
         private readonly IHttpProvider _httpProvider;
-        private readonly IDiskProvider _diskProvider;
         private readonly Logger _logger;
 
 
-        public BlackholeProvider(IConfigService configService, IHttpProvider httpProvider,
-                                    IDiskProvider diskProvider, Logger logger)
+        public BlackholeProvider(IConfigService configService, IHttpProvider httpProvider, Logger logger)
         {
             _configService = configService;
             _httpProvider = httpProvider;
-            _diskProvider = diskProvider;
             _logger = logger;
         }
 
-
-        public bool IsInQueue(RemoteEpisode newEpisode)
-        {
-            throw new NotImplementedException();
-        }
-
         public void DownloadNzb(RemoteEpisode remoteEpisode)
         {
-            var url = remoteEpisode.Report.NzbUrl;
-            var title = remoteEpisode.Report.Title;
+            var url = remoteEpisode.Release.DownloadUrl;
+            var title = remoteEpisode.Release.Title;
 
             title = FileNameBuilder.CleanFilename(title);
 
diff --git a/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs b/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs
index 6fd7a9061..144805113 100644
--- a/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs
+++ b/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Net;
 using Newtonsoft.Json;
 using NLog;
 using NzbDrone.Common;
@@ -24,8 +23,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
 
         public void DownloadNzb(RemoteEpisode remoteEpisode)
         {
-            var url = remoteEpisode.Report.NzbUrl;
-            var title = remoteEpisode.Report.Title + ".nzb";
+            var url = remoteEpisode.Release.DownloadUrl;
+            var title = remoteEpisode.Release.Title + ".nzb";
 
             string cat = _configService.NzbgetTvCategory;
             int priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.NzbgetRecentTvPriority : (int)_configService.NzbgetOlderTvPriority;
diff --git a/NzbDrone.Core/Download/Clients/PneumaticClient.cs b/NzbDrone.Core/Download/Clients/PneumaticClient.cs
index a7e59b6a4..537683243 100644
--- a/NzbDrone.Core/Download/Clients/PneumaticClient.cs
+++ b/NzbDrone.Core/Download/Clients/PneumaticClient.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using NLog;
 using NzbDrone.Common;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Organizer;
 using NzbDrone.Core.Parser.Model;
@@ -15,7 +16,7 @@ namespace NzbDrone.Core.Download.Clients
         private readonly IHttpProvider _httpProvider;
         private readonly IDiskProvider _diskProvider;
 
-        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger logger =  NzbDroneLogger.GetLogger();
 
         public PneumaticClient(IConfigService configService, IHttpProvider httpProvider,
                                     IDiskProvider diskProvider)
@@ -27,8 +28,8 @@ namespace NzbDrone.Core.Download.Clients
 
         public void DownloadNzb(RemoteEpisode remoteEpisode)
         {
-            var url = remoteEpisode.Report.NzbUrl;
-            var title = remoteEpisode.Report.Title;
+            var url = remoteEpisode.Release.DownloadUrl;
+            var title = remoteEpisode.Release.Title;
 
             if (remoteEpisode.ParsedEpisodeInfo.FullSeason)
             {
diff --git a/NzbDrone.Core/Download/Clients/Sabnzbd/SabAutoConfigureService.cs b/NzbDrone.Core/Download/Clients/Sabnzbd/SabAutoConfigureService.cs
index 6ff5a666a..224cae5c5 100644
--- a/NzbDrone.Core/Download/Clients/Sabnzbd/SabAutoConfigureService.cs
+++ b/NzbDrone.Core/Download/Clients/Sabnzbd/SabAutoConfigureService.cs
@@ -6,12 +6,13 @@ using System.Net;
 using System.Net.NetworkInformation;
 using System.Text.RegularExpressions;
 using NLog;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Core.Download.Clients.Sabnzbd
 {
     public class SabAutoConfigureService
     {
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
 
         public SabModel AutoConfigureSab()
         {
diff --git a/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs b/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs
index 281096945..40a241a1c 100644
--- a/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs
+++ b/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs
@@ -1,11 +1,11 @@
 using System;
 using System.Collections.Generic;
-using System.Net;
 using System.Web;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 using NLog;
 using NzbDrone.Common;
+using NzbDrone.Common.Cache;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Parser.Model;
 using RestSharp;
@@ -26,8 +26,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
             string cat = _configService.SabTvCategory;
             int priority = (int)_configService.SabRecentTvPriority;
 
-            string name = remoteEpisode.Report.NzbUrl.Replace("&", "%26");
-            string nzbName = HttpUtility.UrlEncode(remoteEpisode.Report.Title);
+            string name = remoteEpisode.Release.DownloadUrl.Replace("&", "%26");
+            string nzbName = HttpUtility.UrlEncode(remoteEpisode.Release.Title);
 
             string action = string.Format("mode=addurl&name={0}&priority={1}&pp=3&cat={2}&nzbname={3}&output=json",
                 name, priority, cat, nzbName);
@@ -53,19 +53,21 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
     {
         private readonly IConfigService _configService;
         private readonly IHttpProvider _httpProvider;
+        private readonly ICached<IEnumerable<QueueItem>> _queueCache;
         private readonly Logger _logger;
 
-        public SabnzbdClient(IConfigService configService, IHttpProvider httpProvider, Logger logger)
+        public SabnzbdClient(IConfigService configService, IHttpProvider httpProvider, ICacheManger cacheManger, Logger logger)
         {
             _configService = configService;
             _httpProvider = httpProvider;
+            _queueCache = cacheManger.GetCache<IEnumerable<QueueItem>>(GetType(), "queue");
             _logger = logger;
         }
 
         public void DownloadNzb(RemoteEpisode remoteEpisode)
         {
-            var url = remoteEpisode.Report.NzbUrl;
-            var title = remoteEpisode.Report.Title;
+            var url = remoteEpisode.Release.DownloadUrl;
+            var title = remoteEpisode.Release.Title;
 
             string cat = _configService.SabTvCategory;
             int priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.SabRecentTvPriority : (int)_configService.SabOlderTvPriority;
@@ -97,24 +99,31 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
 
         public IEnumerable<QueueItem> GetQueue()
         {
-            string action = String.Format("mode=queue&output=json&start={0}&limit={1}", 0, 0);
-            string request = GetSabRequest(action);
-            string response = _httpProvider.DownloadString(request);
+            return _queueCache.Get("queue", () =>
+            {
+                string action = String.Format("mode=queue&output=json&start={0}&limit={1}", 0, 0);
+                string request = GetSabRequest(action);
+                string response = _httpProvider.DownloadString(request);
 
-            CheckForError(response);
+                CheckForError(response);
 
-            var sabQueue = JsonConvert.DeserializeObject<SabQueue>(JObject.Parse(response).SelectToken("queue").ToString()).Items;
+                var sabQueue = JsonConvert.DeserializeObject<SabQueue>(JObject.Parse(response).SelectToken("queue").ToString()).Items;
 
-            foreach (var sabQueueItem in sabQueue)
-            {
-                var queueItem = new QueueItem();
-                queueItem.Id = sabQueueItem.Id;
-                queueItem.Title = sabQueueItem.Title;
-                queueItem.Size = sabQueueItem.Size;
-                queueItem.SizeLeft = sabQueueItem.Size;
+                var queueItems = new List<QueueItem>();
 
-                yield return queueItem;
-            }
+                foreach (var sabQueueItem in sabQueue)
+                {
+                    var queueItem = new QueueItem();
+                    queueItem.Id = sabQueueItem.Id;
+                    queueItem.Title = sabQueueItem.Title;
+                    queueItem.Size = sabQueueItem.Size;
+                    queueItem.SizeLeft = sabQueueItem.Size;
+
+                    queueItems.Add( queueItem);
+                }
+
+                return queueItems;
+            }, TimeSpan.FromSeconds(10));
         }
 
         public virtual List<SabHistoryItem> GetHistory(int start = 0, int limit = 0)
diff --git a/NzbDrone.Core/Download/DownloadApprovedReports.cs b/NzbDrone.Core/Download/DownloadApprovedReports.cs
index 0da8ff3d9..46f54d1aa 100644
--- a/NzbDrone.Core/Download/DownloadApprovedReports.cs
+++ b/NzbDrone.Core/Download/DownloadApprovedReports.cs
@@ -2,8 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Core.DecisionEngine;
-using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.DecisionEngine.Specifications;
 
 namespace NzbDrone.Core.Download
 {
@@ -60,8 +59,8 @@ namespace NzbDrone.Core.Download
             return decisions.Where(c => c.Approved && c.RemoteEpisode.Episodes.Any())
                             .OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality)
                             .ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault())
-                            .ThenBy(c => c.RemoteEpisode.Report.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count)
-                            .ThenBy(c => c.RemoteEpisode.Report.Age)
+                            .ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count)
+                            .ThenBy(c => c.RemoteEpisode.Release.Age)
                             .ToList();
         }
     }
diff --git a/NzbDrone.Core/Download/DownloadService.cs b/NzbDrone.Core/Download/DownloadService.cs
index 583fdfa1f..a1c4f3bf1 100644
--- a/NzbDrone.Core/Download/DownloadService.cs
+++ b/NzbDrone.Core/Download/DownloadService.cs
@@ -1,5 +1,7 @@
 using NLog;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Parser.Model;
 
 namespace NzbDrone.Core.Download
@@ -12,21 +14,21 @@ namespace NzbDrone.Core.Download
     public class DownloadService : IDownloadService
     {
         private readonly IProvideDownloadClient _downloadClientProvider;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly Logger _logger;
 
 
         public DownloadService(IProvideDownloadClient downloadClientProvider,
-            IMessageAggregator messageAggregator, Logger logger)
+            IEventAggregator eventAggregator, Logger logger)
         {
             _downloadClientProvider = downloadClientProvider;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _logger = logger;
         }
 
         public void DownloadReport(RemoteEpisode remoteEpisode)
         {
-            var downloadTitle = remoteEpisode.Report.Title;
+            var downloadTitle = remoteEpisode.Release.Title;
             var downloadClient = _downloadClientProvider.GetDownloadClient();
 
             if (!downloadClient.IsConfigured)
@@ -37,8 +39,8 @@ namespace NzbDrone.Core.Download
 
             downloadClient.DownloadNzb(remoteEpisode);
 
-            _logger.Info("Report sent to download client. {0}", downloadTitle);
-            _messageAggregator.PublishEvent(new EpisodeGrabbedEvent(remoteEpisode));
+            _logger.ProgressInfo("Report sent to download client. {0}", downloadTitle);
+            _eventAggregator.PublishEvent(new EpisodeGrabbedEvent(remoteEpisode));
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/History/HistoryRepository.cs b/NzbDrone.Core/History/HistoryRepository.cs
index ca436d153..096397248 100644
--- a/NzbDrone.Core/History/HistoryRepository.cs
+++ b/NzbDrone.Core/History/HistoryRepository.cs
@@ -2,8 +2,9 @@
 using System.Collections.Generic;
 using System.Linq;
 using Marr.Data.QGen;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.History
@@ -16,8 +17,8 @@ namespace NzbDrone.Core.History
 
     public class HistoryRepository : BasicRepository<History>, IHistoryRepository
     {
-        public HistoryRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public HistoryRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/History/HistoryService.cs b/NzbDrone.Core/History/HistoryService.cs
index c0752eb2a..6053541d0 100644
--- a/NzbDrone.Core/History/HistoryService.cs
+++ b/NzbDrone.Core/History/HistoryService.cs
@@ -1,12 +1,12 @@
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.History
@@ -65,15 +65,15 @@ namespace NzbDrone.Core.History
                     EventType = HistoryEventType.Grabbed,
                     Date = DateTime.UtcNow,
                     Quality = message.Episode.ParsedEpisodeInfo.Quality,
-                    SourceTitle = message.Episode.Report.Title,
+                    SourceTitle = message.Episode.Release.Title,
                     SeriesId = episode.SeriesId,
                     EpisodeId = episode.Id,
                 };
 
-                history.Data.Add("Indexer", message.Episode.Report.Indexer);
-                history.Data.Add("NzbInfoUrl", message.Episode.Report.NzbInfoUrl);
-                history.Data.Add("ReleaseGroup", message.Episode.Report.ReleaseGroup);
-                history.Data.Add("Age", message.Episode.Report.Age.ToString());
+                history.Data.Add("Indexer", message.Episode.Release.Indexer);
+                history.Data.Add("NzbInfoUrl", message.Episode.Release.InfoUrl);
+                history.Data.Add("ReleaseGroup", message.Episode.Release.ReleaseGroup);
+                history.Data.Add("Age", message.Episode.Release.Age.ToString());
 
                 _historyRepository.Insert(history);
             }
diff --git a/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs
index 11d3d4d79..20a6e8a39 100644
--- a/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs
+++ b/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs
@@ -1,17 +1,19 @@
 using System;
+using System.Collections.Generic;
 using System.Text.RegularExpressions;
 using NzbDrone.Common.EnsureThat;
+using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.IndexerSearch.Definitions
 {
     public abstract class SearchCriteriaBase
     {
-        private static readonly Regex NoneWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
+        private static readonly Regex NonWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
         private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
 
-        public int SeriesId { get; set; }
-        public int SeriesTvRageId { get; set; }
+        public Series Series { get; set; }
         public string SceneTitle { get; set; }
+        public List<Episode> Episodes { get; set; }
 
         public string QueryTitle
         {
@@ -32,7 +34,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
                 .Replace("`", "")
                 .Replace("'", "");
 
-            cleanTitle = NoneWord.Replace(cleanTitle, "+");
+            cleanTitle = NonWord.Replace(cleanTitle, "+");
 
             //remove any repeating +s
             cleanTitle = Regex.Replace(cleanTitle, @"\+{2,}", "+");
diff --git a/NzbDrone.Core/IndexerSearch/Definitions/SingleEpisodeSearchCriteria.cs b/NzbDrone.Core/IndexerSearch/Definitions/SingleEpisodeSearchCriteria.cs
index 371f66bc6..56d110079 100644
--- a/NzbDrone.Core/IndexerSearch/Definitions/SingleEpisodeSearchCriteria.cs
+++ b/NzbDrone.Core/IndexerSearch/Definitions/SingleEpisodeSearchCriteria.cs
@@ -7,7 +7,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
 
         public override string ToString()
         {
-            return string.Format("[{0} : S{1:00}E{2:00} ]", SceneTitle, SeasonNumber, EpisodeNumber);
+            return string.Format("[{0} : S{1:00}E{2:00}]", SceneTitle, SeasonNumber, EpisodeNumber);
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs b/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs
index b0dce2fd1..b69659b77 100644
--- a/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs
+++ b/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs
@@ -1,9 +1,17 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.IndexerSearch
 {
-    public class EpisodeSearchCommand : ICommand
+    public class EpisodeSearchCommand : Command
     {
         public int EpisodeId { get; set; }
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs b/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs
index e62a99836..4f2860eb4 100644
--- a/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs
+++ b/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs
@@ -1,5 +1,8 @@
-using NzbDrone.Common.Messaging;
+using NLog;
 using NzbDrone.Core.Download;
+using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.IndexerSearch
 {
@@ -7,17 +10,23 @@ namespace NzbDrone.Core.IndexerSearch
     {
         private readonly ISearchForNzb _nzbSearchService;
         private readonly IDownloadApprovedReports _downloadApprovedReports;
+        private readonly Logger _logger;
 
-        public EpisodeSearchService(ISearchForNzb nzbSearchService, IDownloadApprovedReports downloadApprovedReports)
+        public EpisodeSearchService(ISearchForNzb nzbSearchService,
+                                    IDownloadApprovedReports downloadApprovedReports,
+                                    Logger logger)
         {
             _nzbSearchService = nzbSearchService;
             _downloadApprovedReports = downloadApprovedReports;
+            _logger = logger;
         }
 
         public void Execute(EpisodeSearchCommand message)
         {
             var decisions = _nzbSearchService.EpisodeSearch(message.EpisodeId);
-            _downloadApprovedReports.DownloadApproved(decisions);
+            var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
+
+            _logger.ProgressInfo("Episode search completed. {0} reports downloaded.", downloaded.Count);
         }
     }
 }
diff --git a/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/NzbDrone.Core/IndexerSearch/NzbSearchService.cs
index 7386341d2..5770687b8 100644
--- a/NzbDrone.Core/IndexerSearch/NzbSearchService.cs
+++ b/NzbDrone.Core/IndexerSearch/NzbSearchService.cs
@@ -5,8 +5,10 @@ using System.Threading.Tasks;
 using NLog;
 using NzbDrone.Core.DataAugmentation.Scene;
 using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.DecisionEngine.Specifications;
 using NzbDrone.Core.IndexerSearch.Definitions;
 using NzbDrone.Core.Indexers;
+using NzbDrone.Core.Instrumentation;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Tv;
 using System.Linq;
@@ -59,7 +61,7 @@ namespace NzbDrone.Core.IndexerSearch
                     throw new InvalidOperationException("Daily episode is missing AirDate. Try to refresh series info.");
                 }
 
-                return SearchDaily(series, DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture));
+                return SearchDaily(series, episode);
             }
 
             return SearchSingle(series, episode);
@@ -67,7 +69,7 @@ namespace NzbDrone.Core.IndexerSearch
 
         private List<DownloadDecision> SearchSingle(Series series, Episode episode)
         {
-            var searchSpec = Get<SingleEpisodeSearchCriteria>(series, episode.SeasonNumber);
+            var searchSpec = Get<SingleEpisodeSearchCriteria>(series, new List<Episode>{episode});
 
             if (series.UseSceneNumbering)
             {
@@ -92,9 +94,10 @@ namespace NzbDrone.Core.IndexerSearch
             return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
         }
 
-        private List<DownloadDecision> SearchDaily(Series series, DateTime airDate)
+        private List<DownloadDecision> SearchDaily(Series series, Episode episode)
         {
-            var searchSpec = Get<DailyEpisodeSearchCriteria>(series);
+            var airDate = DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture);
+            var searchSpec = Get<DailyEpisodeSearchCriteria>(series, new List<Episode>{ episode });
             searchSpec.Airtime = airDate;
 
             return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
@@ -103,20 +106,21 @@ namespace NzbDrone.Core.IndexerSearch
         public List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber)
         {
             var series = _seriesService.GetSeries(seriesId);
+            var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
 
-            var searchSpec = Get<SeasonSearchCriteria>(series, seasonNumber);
+            var searchSpec = Get<SeasonSearchCriteria>(series, episodes);
             searchSpec.SeasonNumber = seasonNumber;
 
             return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
         }
 
-        private TSpec Get<TSpec>(Series series, int seasonNumber = -1) where TSpec : SearchCriteriaBase, new()
+        private TSpec Get<TSpec>(Series series, List<Episode> episodes) where TSpec : SearchCriteriaBase, new()
         {
             var spec = new TSpec();
 
-            spec.SeriesId = series.Id;
-            spec.SeriesTvRageId = series.TvRageId;
+            spec.Series = series;
             spec.SceneTitle = _sceneMapping.GetSceneName(series.TvdbId);
+            spec.Episodes = episodes;
 
             if (string.IsNullOrWhiteSpace(spec.SceneTitle))
             {
@@ -126,11 +130,12 @@ namespace NzbDrone.Core.IndexerSearch
             return spec;
         }
 
-        private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReportInfo>> searchAction, SearchCriteriaBase criteriaBase)
+        private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase)
         {
             var indexers = _indexerService.GetAvailableIndexers().ToList();
-            var reports = new List<ReportInfo>();
+            var reports = new List<ReleaseInfo>();
 
+            _logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase);
 
             var taskList = new List<Task>();
             var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
@@ -159,9 +164,9 @@ namespace NzbDrone.Core.IndexerSearch
 
             Task.WaitAll(taskList.ToArray());
 
-            _logger.Debug("Total of {0} reports were found for {1} in {2} indexers", reports.Count, criteriaBase, indexers.Count);
+            _logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
 
             return _makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs b/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs
index cf9a8ba3e..56be0e0d9 100644
--- a/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs
+++ b/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs
@@ -1,10 +1,18 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.IndexerSearch
 {
-    public class SeasonSearchCommand : ICommand
+    public class SeasonSearchCommand : Command
     {
         public int SeriesId { get; set; }
         public int SeasonNumber { get; set; }
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs b/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs
index 7572b5b6d..0e4b67eab 100644
--- a/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs
+++ b/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs
@@ -1,5 +1,8 @@
-using NzbDrone.Common.Messaging;
+using NLog;
 using NzbDrone.Core.Download;
+using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.IndexerSearch
 {
@@ -7,17 +10,23 @@ namespace NzbDrone.Core.IndexerSearch
     {
         private readonly ISearchForNzb _nzbSearchService;
         private readonly IDownloadApprovedReports _downloadApprovedReports;
+        private readonly Logger _logger;
 
-        public SeasonSearchService(ISearchForNzb nzbSearchService, IDownloadApprovedReports downloadApprovedReports)
+        public SeasonSearchService(ISearchForNzb nzbSearchService,
+                                   IDownloadApprovedReports downloadApprovedReports,
+                                   Logger logger)
         {
             _nzbSearchService = nzbSearchService;
             _downloadApprovedReports = downloadApprovedReports;
+            _logger = logger;
         }
 
         public void Execute(SeasonSearchCommand message)
         {
             var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber);
-            _downloadApprovedReports.DownloadApproved(decisions);
+            var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
+
+            _logger.ProgressInfo("Season search completed. {0} reports downloaded.", downloaded.Count);
         }
     }
 }
diff --git a/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs b/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs
index 4e309fde8..36b75f316 100644
--- a/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs
+++ b/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs
@@ -1,9 +1,17 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.IndexerSearch
 {
-    public class SeriesSearchCommand : ICommand
+    public class SeriesSearchCommand : Command
     {
         public int SeriesId { get; set; }
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs b/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs
index 89dd7c6c2..c87275648 100644
--- a/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs
+++ b/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs
@@ -1,37 +1,44 @@
 using System.Linq;
-using NzbDrone.Common.Messaging;
+using NLog;
 using NzbDrone.Core.Download;
+using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.IndexerSearch
 {
     public class SeriesSearchService : IExecute<SeriesSearchCommand>
     {
-        private readonly ISeasonService _seasonService;
+        private readonly ISeriesService _seriesService;
         private readonly ISearchForNzb _nzbSearchService;
         private readonly IDownloadApprovedReports _downloadApprovedReports;
+        private readonly Logger _logger;
 
-        public SeriesSearchService(ISeasonService seasonService,
+        public SeriesSearchService(ISeriesService seriesService,
                                    ISearchForNzb nzbSearchService,
-                                   IDownloadApprovedReports downloadApprovedReports)
+                                   IDownloadApprovedReports downloadApprovedReports,
+                                   Logger logger)
         {
-            _seasonService = seasonService;
+            _seriesService = seriesService;
             _nzbSearchService = nzbSearchService;
             _downloadApprovedReports = downloadApprovedReports;
+            _logger = logger;
         }
 
         public void Execute(SeriesSearchCommand message)
         {
-            var seasons = _seasonService.GetSeasonsBySeries(message.SeriesId)
-                                        .Where(s => s.SeasonNumber > 0)
-                                        .OrderBy(s => s.SeasonNumber)
-                                        .ToList();
+            var series = _seriesService.GetSeries(message.SeriesId);
 
-            foreach (var season in seasons)
+            var downloadedCount = 0;
+
+            foreach (var season in series.Seasons)
             {
                 var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, season.SeasonNumber);
-                _downloadApprovedReports.DownloadApproved(decisions);
+                downloadedCount += _downloadApprovedReports.DownloadApproved(decisions).Count;
             }
+
+            _logger.ProgressInfo("Series search completed. {0} reports downloaded.", downloadedCount);
         }
     }
 }
diff --git a/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs b/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs
new file mode 100644
index 000000000..d3c8ab15a
--- /dev/null
+++ b/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Xml.Linq;
+using NLog;
+using NzbDrone.Core.Parser.Model;
+
+namespace NzbDrone.Core.Indexers
+{
+    public class BasicTorrentRssParser : RssParserBase
+    {
+        protected override ReleaseInfo CreateNewReleaseInfo()
+        {
+            return new TorrentInfo();
+        }
+
+        protected override ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult)
+        {
+            var torrentInfo = (TorrentInfo)currentResult;
+
+            torrentInfo.MagnetUrl = MagnetUrl(item);
+            torrentInfo.InfoHash = InfoHash(item);
+
+            return torrentInfo;
+        }
+
+        protected override long GetSize(XElement item)
+        {
+            var elementLength =  GetTorrentElement(item).Element("contentLength");
+            return Convert.ToInt64(elementLength.Value);
+        }
+
+        protected virtual string MagnetUrl(XElement item)
+        {
+            var elementLength = GetTorrentElement(item).Element("magnetURI");
+            return elementLength.Value;
+        }
+
+        protected virtual string InfoHash(XElement item)
+        {
+            var elementLength = GetTorrentElement(item).Element("infoHash");
+            return elementLength.Value;
+        }
+
+        private static XElement GetTorrentElement(XElement item)
+        {
+            return item.Element("torrent");
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/DownloadProtocols.cs b/NzbDrone.Core/Indexers/DownloadProtocols.cs
new file mode 100644
index 000000000..4fff5e07d
--- /dev/null
+++ b/NzbDrone.Core/Indexers/DownloadProtocols.cs
@@ -0,0 +1,8 @@
+namespace NzbDrone.Core.Indexers
+{
+    public enum DownloadProtocols
+    {
+        Nzb = 0,
+        Torrent =1
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/Eztv/Eztv.cs b/NzbDrone.Core/Indexers/Eztv/Eztv.cs
new file mode 100644
index 000000000..541f3a078
--- /dev/null
+++ b/NzbDrone.Core/Indexers/Eztv/Eztv.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+
+namespace NzbDrone.Core.Indexers.Eztv
+{
+    public class Eztv : IndexerBase
+    {
+        public override string Name
+        {
+            get { return "Eztv"; }
+        }
+
+        public override IndexerKind Kind
+        {
+            get
+            {
+                return IndexerKind.Torrent;
+            }
+        }
+
+        public override bool EnableByDefault
+        {
+            get { return false; }
+        }
+
+        public override IParseFeed Parser
+        {
+            get
+            {
+                return new BasicTorrentRssParser();
+            }
+        }
+
+        public override IEnumerable<string> RecentFeed
+        {
+            get
+            {
+                return new[]
+                           {
+                              "http://www.ezrss.it/feed/"
+                           };
+            }
+        }
+
+        public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber)
+        {
+            yield return string.Format("http://www.ezrss.it/search/index.php?show_name={0}&season={1}&episode={2}&mode=rss", seriesTitle, seasonNumber, episodeNumber);
+        }
+
+        public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset)
+        {
+            yield return string.Format("http://www.ezrss.it/search/index.php?show_name={0}&season={1}&mode=rss", seriesTitle, seasonNumber);
+
+        }
+
+        public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date)
+        {
+            //EZTV doesn't support searching based on actual epidose airdate. they only support release date.
+            return new string[0];
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/FetchAndParseRssService.cs b/NzbDrone.Core/Indexers/FetchAndParseRssService.cs
index 9ee704910..c9633c8d0 100644
--- a/NzbDrone.Core/Indexers/FetchAndParseRssService.cs
+++ b/NzbDrone.Core/Indexers/FetchAndParseRssService.cs
@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Indexers
 {
     public interface IFetchAndParseRss
     {
-        List<ReportInfo> Fetch();
+        List<ReleaseInfo> Fetch();
     }
 
     public class FetchAndParseRssService : IFetchAndParseRss
@@ -25,9 +25,9 @@ namespace NzbDrone.Core.Indexers
             _logger = logger;
         }
 
-        public List<ReportInfo> Fetch()
+        public List<ReleaseInfo> Fetch()
         {
-            var result = new List<ReportInfo>();
+            var result = new List<ReleaseInfo>();
 
             var indexers = _indexerService.GetAvailableIndexers().ToList();
 
diff --git a/NzbDrone.Core/Indexers/IIndexer.cs b/NzbDrone.Core/Indexers/IIndexer.cs
index c8e8e950f..a681ae3df 100644
--- a/NzbDrone.Core/Indexers/IIndexer.cs
+++ b/NzbDrone.Core/Indexers/IIndexer.cs
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.Indexers
     public interface IIndexer
     {
         string Name { get; }
-
+       
         bool EnableByDefault { get; }
 
         IEnumerable<IndexerDefinition> DefaultDefinitions { get; }
@@ -16,6 +16,7 @@ namespace NzbDrone.Core.Indexers
         IEnumerable<string> RecentFeed { get; }
 
         IParseFeed Parser { get; }
+        IndexerKind Kind { get; }
 
         IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
         IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date);
diff --git a/NzbDrone.Core/Indexers/IParseFeed.cs b/NzbDrone.Core/Indexers/IParseFeed.cs
new file mode 100644
index 000000000..a101f0a07
--- /dev/null
+++ b/NzbDrone.Core/Indexers/IParseFeed.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using System.IO;
+using NzbDrone.Core.Parser.Model;
+
+namespace NzbDrone.Core.Indexers
+{
+    public interface IParseFeed
+    {
+        IEnumerable<ReleaseInfo> Process(string source, string url);
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/IndexerBase.cs b/NzbDrone.Core/Indexers/IndexerBase.cs
index 4b2cd9f99..6f544a2be 100644
--- a/NzbDrone.Core/Indexers/IndexerBase.cs
+++ b/NzbDrone.Core/Indexers/IndexerBase.cs
@@ -7,6 +7,8 @@ namespace NzbDrone.Core.Indexers
     {
         public abstract string Name { get; }
 
+        public abstract IndexerKind Kind { get; }
+
         public virtual bool EnableByDefault { get { return true; } }
 
         public IndexerDefinition InstanceDefinition { get; set; }
@@ -25,17 +27,17 @@ namespace NzbDrone.Core.Indexers
             }
         }
 
-        public virtual IParseFeed Parser
-        {
-            get
-            {
-                return new BasicRssParser();
-            }
-        }
+        public virtual IParseFeed Parser { get; private set; }
 
         public abstract IEnumerable<string> RecentFeed { get; }
         public abstract IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
         public abstract IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date);
         public abstract IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset);
     }
+
+    public enum IndexerKind
+    {
+        Usenet,
+        Torrent
+    }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/IndexerFetchService.cs b/NzbDrone.Core/Indexers/IndexerFetchService.cs
index 2e585f862..9814adc0d 100644
--- a/NzbDrone.Core/Indexers/IndexerFetchService.cs
+++ b/NzbDrone.Core/Indexers/IndexerFetchService.cs
@@ -11,11 +11,11 @@ namespace NzbDrone.Core.Indexers
 {
     public interface IFetchFeedFromIndexers
     {
-        IList<ReportInfo> FetchRss(IIndexer indexer);
+        IList<ReleaseInfo> FetchRss(IIndexer indexer);
 
-        IList<ReportInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria);
-        IList<ReportInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria);
-        IList<ReportInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria);
+        IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria);
+        IList<ReleaseInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria);
+        IList<ReleaseInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria);
     }
 
     public class FetchFeedService : IFetchFeedFromIndexers
@@ -31,7 +31,7 @@ namespace NzbDrone.Core.Indexers
         }
 
 
-        public virtual IList<ReportInfo> FetchRss(IIndexer indexer)
+        public virtual IList<ReleaseInfo> FetchRss(IIndexer indexer)
         {
             _logger.Debug("Fetching feeds from " + indexer.Name);
 
@@ -42,23 +42,23 @@ namespace NzbDrone.Core.Indexers
             return result;
         }
 
-        public IList<ReportInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria)
+        public IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria)
         {
             _logger.Debug("Searching for {0}", searchCriteria);
 
-            var result = Fetch(indexer, searchCriteria, 0).DistinctBy(c => c.NzbUrl).ToList();
+            var result = Fetch(indexer, searchCriteria, 0).DistinctBy(c => c.DownloadUrl).ToList();
 
-            _logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
+            _logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
 
             return result;
         }
 
 
-        private IList<ReportInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria, int offset)
+        private IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria, int offset)
         {
             _logger.Debug("Searching for {0} offset: {1}", searchCriteria, offset);
 
-            var searchUrls = indexer.GetSeasonSearchUrls(searchCriteria.QueryTitle, searchCriteria.SeriesTvRageId, searchCriteria.SeasonNumber, offset);
+            var searchUrls = indexer.GetSeasonSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, offset);
             var result = Fetch(indexer, searchUrls);
 
 
@@ -72,34 +72,32 @@ namespace NzbDrone.Core.Indexers
             return result;
         }
 
-        public IList<ReportInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria)
+        public IList<ReleaseInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria)
         {
             _logger.Debug("Searching for {0}", searchCriteria);
 
-            var searchUrls = indexer.GetEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.SeriesTvRageId, searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber);
+            var searchUrls = indexer.GetEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber);
             var result = Fetch(indexer, searchUrls);
 
 
-            _logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
+            _logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
             return result;
-
         }
 
-
-        public IList<ReportInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria)
+        public IList<ReleaseInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria)
         {
             _logger.Debug("Searching for {0}", searchCriteria);
 
-            var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.SeriesTvRageId, searchCriteria.Airtime);
+            var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.Airtime);
             var result = Fetch(indexer, searchUrls);
 
-            _logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
+            _logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
             return result;
         }
 
-        private List<ReportInfo> Fetch(IIndexer indexer, IEnumerable<string> urls)
+        private List<ReleaseInfo> Fetch(IIndexer indexer, IEnumerable<string> urls)
         {
-            var result = new List<ReportInfo>();
+            var result = new List<ReleaseInfo>();
 
             foreach (var url in urls)
             {
@@ -140,4 +138,4 @@ namespace NzbDrone.Core.Indexers
             return result;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/NzbDrone.Core/Indexers/IndexerRepository.cs b/NzbDrone.Core/Indexers/IndexerRepository.cs
index 678c26fd6..fd66a3910 100644
--- a/NzbDrone.Core/Indexers/IndexerRepository.cs
+++ b/NzbDrone.Core/Indexers/IndexerRepository.cs
@@ -1,7 +1,9 @@
 using System;
 using System.Linq;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Indexers
 {
@@ -13,8 +15,8 @@ namespace NzbDrone.Core.Indexers
 
     public class IndexerRepository : BasicRepository<IndexerDefinition>, IIndexerRepository
     {
-        public IndexerRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public IndexerRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/Indexers/IndexerService.cs b/NzbDrone.Core/Indexers/IndexerService.cs
index 168835ffa..d9fc3ba27 100644
--- a/NzbDrone.Core/Indexers/IndexerService.cs
+++ b/NzbDrone.Core/Indexers/IndexerService.cs
@@ -2,10 +2,12 @@
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Common.Serializer;
+using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Indexers.Newznab;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using Omu.ValueInjecter;
 
 namespace NzbDrone.Core.Indexers
@@ -35,15 +37,26 @@ namespace NzbDrone.Core.Indexers
     public class IndexerService : IIndexerService, IHandle<ApplicationStartedEvent>
     {
         private readonly IIndexerRepository _indexerRepository;
+        private readonly IConfigFileProvider _configFileProvider;
         private readonly Logger _logger;
 
         private readonly List<IIndexer> _indexers;
 
-        public IndexerService(IIndexerRepository indexerRepository, IEnumerable<IIndexer> indexers, Logger logger)
+        public IndexerService(IIndexerRepository indexerRepository, IEnumerable<IIndexer> indexers, IConfigFileProvider configFileProvider, Logger logger)
         {
             _indexerRepository = indexerRepository;
+            _configFileProvider = configFileProvider;
             _logger = logger;
-            _indexers = indexers.ToList();
+
+
+            if (!configFileProvider.Torrent)
+            {
+                _indexers = indexers.Where(c => c.Kind != IndexerKind.Torrent).ToList();
+            }
+            else
+            {
+                _indexers = indexers.ToList();
+            }
         }
 
         public List<Indexer> All()
@@ -153,10 +166,16 @@ namespace NzbDrone.Core.Indexers
 
             RemoveMissingImplementations();
 
-            if (!All().Any())
+            var definitions = _indexers.SelectMany(indexer => indexer.DefaultDefinitions);
+
+            var currentIndexer = All();
+
+            var newIndexers = definitions.Where(def => currentIndexer.All(c => c.Implementation != def.Implementation)).ToList();
+
+
+            if (newIndexers.Any())
             {
-                var definitions = _indexers.SelectMany(indexer => indexer.DefaultDefinitions);
-                _indexerRepository.InsertMany(definitions.ToList());
+                _indexerRepository.InsertMany(newIndexers);
             }
         }
 
diff --git a/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/NzbDrone.Core/Indexers/Newznab/Newznab.cs
index 51c49e26d..4341c22f7 100644
--- a/NzbDrone.Core/Indexers/Newznab/Newznab.cs
+++ b/NzbDrone.Core/Indexers/Newznab/Newznab.cs
@@ -123,6 +123,15 @@ namespace NzbDrone.Core.Indexers.Newznab
             }
         }
 
+        public override IndexerKind Kind
+        {
+            get
+            {
+                return IndexerKind.Usenet;
+            }
+        }
+
+
         private static string NewsnabifyTitle(string title)
         {
             return title.Replace("+", "%20");
diff --git a/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs b/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs
index 1e1376569..06c45ea1d 100644
--- a/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs
+++ b/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs
@@ -1,35 +1,37 @@
 using System;
-using System.Drawing;
 using System.Linq;
 using System.Xml.Linq;
-using NLog;
 using NzbDrone.Core.Parser.Model;
 
 namespace NzbDrone.Core.Indexers.Newznab
 {
-    public class NewznabParser : BasicRssParser
+    public class NewznabParser : RssParserBase
     {
-        private static readonly XNamespace NewznabNamespace = "http://www.newznab.com/DTD/2010/feeds/attributes/";
-
         protected override string GetNzbInfoUrl(XElement item)
         {
             return item.Comments().Replace("#comments", "");
         }
 
-        protected override ReportInfo PostProcessor(XElement item, ReportInfo currentResult)
+        protected override long GetSize(XElement item)
         {
-            if (currentResult != null)
+            var attributes = item.Elements("attr").ToList();
+            var sizeElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("size", StringComparison.CurrentCultureIgnoreCase));
+
+            if (sizeElement == null)
             {
-                var attributes = item.Elements(NewznabNamespace + "attr").ToList();
-                var sizeElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("size", StringComparison.CurrentCultureIgnoreCase));
-                var rageIdElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("rageid", StringComparison.CurrentCultureIgnoreCase));
 
-                if (sizeElement == null)
-                {
-                    throw new SizeParsingException("Unable to parse size from: {0} [{1}]", currentResult.Title, currentResult.Indexer);
-                }
+            }
 
-                currentResult.Size = Convert.ToInt64(sizeElement.Attribute("value").Value);
+            return Convert.ToInt64(sizeElement.Attribute("value").Value);
+        }
+
+        protected override ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult)
+        {
+            if (currentResult != null)
+            {
+                var attributes = item.Elements("attr").ToList();
+
+                var rageIdElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("rageid", StringComparison.CurrentCultureIgnoreCase));
 
                 if (rageIdElement != null)
                 {
diff --git a/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs b/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs
index 84c96535b..549e52f92 100644
--- a/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs
+++ b/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using NzbDrone.Common.Exceptions;
+using NzbDrone.Common.Exceptions;
 
 namespace NzbDrone.Core.Indexers.Newznab
 {
diff --git a/NzbDrone.Core/Indexers/NzbClub/NzbClub.cs b/NzbDrone.Core/Indexers/NzbClub/NzbClub.cs
deleted file mode 100644
index cb2bc8a5f..000000000
--- a/NzbDrone.Core/Indexers/NzbClub/NzbClub.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace NzbDrone.Core.Indexers.NzbClub
-{
-    public class NzbClub : IndexerBase
-    {
-        public override string Name
-        {
-            get { return "NzbClub"; }
-        }
-
-        public override bool EnableByDefault
-        {
-            get { return false; }
-        }
-
-        public override IParseFeed Parser
-        {
-            get
-            {
-                return new NzbClubParser();
-            }
-        }
-
-        public override IEnumerable<string> RecentFeed
-        {
-            get
-            {
-                return new[]
-                           {
-                               String.Format("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=102952&st=1&ns=1&q=%23a.b.teevee"),
-                               String.Format("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=5542&st=1&ns=1&q=")
-                           };
-            }
-        }
-
-        public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber)
-        {
-            var searchUrls = new List<string>();
-
-            foreach (var url in RecentFeed)
-            {
-                searchUrls.Add(String.Format("{0}+{1}+s{2:00}e{3:00}", url, seriesTitle, seasonNumber, episodeNumber));
-            }
-
-            return searchUrls;
-        }
-
-        public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset)
-        {
-            var searchUrls = new List<string>();
-
-            foreach (var url in RecentFeed)
-            {
-                searchUrls.Add(String.Format("{0}+{1}+s{2:00}", url, seriesTitle, seasonNumber));
-            }
-
-            return searchUrls;
-        }
-
-        public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date)
-        {
-            var searchUrls = new List<String>();
-
-            foreach (var url in RecentFeed)
-            {
-                searchUrls.Add(String.Format("{0}+{1}+{2:yyyy MM dd}", url, seriesTitle, date));
-            }
-
-            return searchUrls;
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/NzbClub/NzbClubParser.cs b/NzbDrone.Core/Indexers/NzbClub/NzbClubParser.cs
deleted file mode 100644
index 9c17b0ffa..000000000
--- a/NzbDrone.Core/Indexers/NzbClub/NzbClubParser.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Xml.Linq;
-using NLog;
-using NzbDrone.Core.Parser.Model;
-
-namespace NzbDrone.Core.Indexers.NzbClub
-{
-    public class NzbClubParser : BasicRssParser
-    {
-
-        private static readonly Regex SizeRegex = new Regex(@"(?:Size:)\s(?<size>\d+.\d+\s[g|m]i?[b])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
-        private readonly Logger logger;
-
-        public NzbClubParser()
-        {
-            logger = LogManager.GetCurrentClassLogger();
-        }
-
-
-        protected override ReportInfo PostProcessor(XElement item, ReportInfo currentResult)
-        {
-            if (currentResult != null)
-            {
-                var match = SizeRegex.Match(item.Description());
-
-                if (match.Success && match.Groups["size"].Success)
-                {
-                    currentResult.Size = GetReportSize(match.Groups["size"].Value);
-                }
-                else
-                {
-                   logger.Warn("Couldn't parse size from {0}", item.Description());
-                }
-            }
-
-            return currentResult;
-        }
-
-        protected override string GetTitle(XElement item)
-        {
-            var title = ParseHeader(item.Title());
-
-            if (String.IsNullOrWhiteSpace(title))
-                return item.Title();
-
-            return title;
-        }
-
-        protected override string GetNzbInfoUrl(XElement item)
-        {
-            return item.Links().First();
-        }
-
-        protected override string GetNzbUrl(XElement item)
-        {
-            var enclosure = item.Element("enclosure");
-
-            return enclosure.Attribute("url").Value;
-        }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs b/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs
index 8d8e9b4a9..15f6dbbe5 100644
--- a/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs
+++ b/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs
@@ -10,6 +10,14 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
             get { return "omgwtfnzbs"; }
         }
 
+        public override IndexerKind Kind
+        {
+            get
+            {
+                return IndexerKind.Usenet;
+            }
+        }
+
         public override IParseFeed Parser
         {
             get
diff --git a/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs b/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs
index a2369f81a..afaeb3890 100644
--- a/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs
+++ b/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs
@@ -1,11 +1,10 @@
 using System;
 using System.Text.RegularExpressions;
 using System.Xml.Linq;
-using NzbDrone.Core.Parser.Model;
 
 namespace NzbDrone.Core.Indexers.Omgwtfnzbs
 {
-    public class OmgwtfnzbsParser : BasicRssParser
+    public class OmgwtfnzbsParser : RssParserBase
     {
         protected override string GetNzbInfoUrl(XElement item)
         {
@@ -21,15 +20,10 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
             return String.Empty;
         }
 
-        protected override ReportInfo PostProcessor(XElement item, ReportInfo currentResult)
+        protected override long GetSize(XElement item)
         {
-            if (currentResult != null)
-            {
-                var sizeString = Regex.Match(item.Description(), @"(?:Size:\<\/b\>\s\d+\.)\d{1,2}\s\w{2}(?:\<br \/\>)", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value;
-                currentResult.Size = GetReportSize(sizeString);
-            }
-
-            return currentResult;
+            var sizeString = Regex.Match(item.Description(), @"(?:Size:\<\/b\>\s\d+\.)\d{1,2}\s\w{2}(?:\<br \/\>)", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value;
+            return ParseSize(sizeString);
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/BasicRssParser.cs b/NzbDrone.Core/Indexers/RssParserBase.cs
similarity index 63%
rename from NzbDrone.Core/Indexers/BasicRssParser.cs
rename to NzbDrone.Core/Indexers/RssParserBase.cs
index 84c61f296..485bc0140 100644
--- a/NzbDrone.Core/Indexers/BasicRssParser.cs
+++ b/NzbDrone.Core/Indexers/RssParserBase.cs
@@ -7,43 +7,45 @@ using System.Text.RegularExpressions;
 using System.Xml;
 using System.Xml.Linq;
 using NLog;
+using NzbDrone.Common.Instrumentation;
+using NzbDrone.Core.Indexers.Newznab;
 using NzbDrone.Core.Parser.Model;
 
 namespace NzbDrone.Core.Indexers
 {
-    public interface IParseFeed
-    {
-        IEnumerable<ReportInfo> Process(string xml, string url);
-    }
-
-    public class BasicRssParser : IParseFeed
+    public abstract class RssParserBase : IParseFeed
     {
         private readonly Logger _logger;
 
-        public BasicRssParser()
+        protected virtual ReleaseInfo CreateNewReleaseInfo()
+        {
+            return new ReleaseInfo();
+        }
+
+        protected RssParserBase()
         {
-            _logger = LogManager.GetCurrentClassLogger();
+            _logger = NzbDroneLogger.GetLogger(this);
         }
 
-        public IEnumerable<ReportInfo> Process(string xml, string url)
+        public IEnumerable<ReleaseInfo> Process(string xml, string url)
         {
             using (var xmlTextReader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { ProhibitDtd = false, IgnoreComments = true }))
             {
+
                 var document = XDocument.Load(xmlTextReader);
                 var items = document.Descendants("item");
 
-                var result = new List<ReportInfo>();
+                var result = new List<ReleaseInfo>();
 
                 foreach (var item in items)
                 {
                     try
                     {
-                        var reportInfo = ParseFeedItem(item);
+                        var reportInfo = ParseFeedItem(item.StripNameSpace(), url);
                         if (reportInfo != null)
                         {
-                            reportInfo.NzbUrl = GetNzbUrl(item);
-                            reportInfo.NzbInfoUrl = GetNzbInfoUrl(item);
-
+                            reportInfo.DownloadUrl = GetNzbUrl(item);
+                            reportInfo.InfoUrl = GetNzbInfoUrl(item);
                             result.Add(reportInfo);
                         }
                     }
@@ -58,6 +60,31 @@ namespace NzbDrone.Core.Indexers
             }
         }
 
+        private ReleaseInfo ParseFeedItem(XElement item, string url)
+        {
+            var title = GetTitle(item);
+
+            var reportInfo = CreateNewReleaseInfo();
+
+            reportInfo.Title = title;
+            reportInfo.PublishDate = item.PublishDate();
+            reportInfo.ReleaseGroup = ParseReleaseGroup(title);
+            reportInfo.DownloadUrl = GetNzbUrl(item);
+            reportInfo.InfoUrl = GetNzbInfoUrl(item);
+
+            try
+            {
+                reportInfo.Size = GetSize(item);
+            }
+            catch (Exception)
+            {
+                throw new SizeParsingException("Unable to parse size from: {0} [{1}]", reportInfo.Title, url);
+            }
+
+            _logger.Trace("Parsed: {0} from: {1}", reportInfo, item.Title());
+
+            return PostProcessor(item, reportInfo);
+        }
 
         protected virtual string GetTitle(XElement item)
         {
@@ -74,25 +101,14 @@ namespace NzbDrone.Core.Indexers
             return String.Empty;
         }
 
-        protected virtual ReportInfo PostProcessor(XElement item, ReportInfo currentResult)
+        protected abstract long GetSize(XElement item);
+
+        protected virtual ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult)
         {
             return currentResult;
         }
 
-        private ReportInfo ParseFeedItem(XElement item)
-        {
-            var title = GetTitle(item);
 
-            var reportInfo = new ReportInfo();
-
-            reportInfo.Title = title;
-            reportInfo.Age = DateTime.Now.Date.Subtract(item.PublishDate().Date).Days;
-            reportInfo.ReleaseGroup = ParseReleaseGroup(title);
-
-            _logger.Trace("Parsed: {0} from: {1}", reportInfo, item.Title());
-
-            return PostProcessor(item, reportInfo);
-        }
 
         public static string ParseReleaseGroup(string title)
         {
@@ -110,39 +126,15 @@ namespace NzbDrone.Core.Indexers
             if (@group.Length == title.Length)
                 return String.Empty;
 
-            return @group;
-        }
-
-        private static readonly Regex[] HeaderRegex = new[]
-                                                          {
-                                                                new Regex(@"(?:\[.+\]\-\[.+\]\-\[.+\]\-\[)(?<nzbTitle>.+)(?:\]\-.+)",
-                                                                        RegexOptions.IgnoreCase),
-                                                                
-                                                                new Regex(@"(?:\[.+\]\W+\[.+\]\W+\[.+\]\W+\"")(?<nzbTitle>.+)(?:\"".+)",
-                                                                        RegexOptions.IgnoreCase),
-                                                                    
-                                                                new Regex(@"(?:\[)(?<nzbTitle>.+)(?:\]\-.+)",
-                                                                        RegexOptions.IgnoreCase),
-                                                          };
-
-        public static string ParseHeader(string header)
-        {
-            foreach (var regex in HeaderRegex)
-            {
-                var match = regex.Matches(header);
-
-                if (match.Count != 0)
-                    return match[0].Groups["nzbTitle"].Value.Trim();
-            }
-
-            return header;
+            return @group.Trim('-', ' ', '[', ']');
         }
 
         private static readonly Regex ReportSizeRegex = new Regex(@"(?<value>\d+\.\d{1,2}|\d+\,\d+\.\d{1,2}|\d+)\W?(?<unit>GB|MB|GiB|MiB)",
                                                                   RegexOptions.IgnoreCase | RegexOptions.Compiled);
 
 
-        public static long GetReportSize(string sizeString)
+
+        public static long ParseSize(string sizeString)
         {
             var match = ReportSizeRegex.Matches(sizeString);
 
@@ -176,4 +168,4 @@ namespace NzbDrone.Core.Indexers
             return Convert.ToInt64(result);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/NzbDrone.Core/Indexers/RssSyncCommand.cs b/NzbDrone.Core/Indexers/RssSyncCommand.cs
index 04a7a123b..56b4532bf 100644
--- a/NzbDrone.Core/Indexers/RssSyncCommand.cs
+++ b/NzbDrone.Core/Indexers/RssSyncCommand.cs
@@ -1,9 +1,17 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Indexers
 {
-    public class RssSyncCommand : ICommand
+    public class RssSyncCommand : Command
     {
 
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
+
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/RssSyncService.cs b/NzbDrone.Core/Indexers/RssSyncService.cs
index baf414fac..5f9be30a9 100644
--- a/NzbDrone.Core/Indexers/RssSyncService.cs
+++ b/NzbDrone.Core/Indexers/RssSyncService.cs
@@ -1,8 +1,10 @@
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.DecisionEngine;
 using NzbDrone.Core.Download;
+using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Indexers
 {
@@ -32,13 +34,13 @@ namespace NzbDrone.Core.Indexers
 
         public void Sync()
         {
-            _logger.Info("Starting RSS Sync");
+            _logger.ProgressInfo("Starting RSS Sync");
 
             var reports = _rssFetcherAndParser.Fetch();
             var decisions = _downloadDecisionMaker.GetRssDecision(reports);
-            var qualifiedReports = _downloadApprovedReports.DownloadApproved(decisions);
+            var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
 
-            _logger.Info("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, qualifiedReports.Count());
+            _logger.ProgressInfo("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, downloaded.Count());
         }
 
         public void Execute(RssSyncCommand message)
diff --git a/NzbDrone.Core/Indexers/Wombles/Wombles.cs b/NzbDrone.Core/Indexers/Wombles/Wombles.cs
index adff820ec..95cd559de 100644
--- a/NzbDrone.Core/Indexers/Wombles/Wombles.cs
+++ b/NzbDrone.Core/Indexers/Wombles/Wombles.cs
@@ -10,6 +10,14 @@ namespace NzbDrone.Core.Indexers.Wombles
             get { return "WomblesIndex"; }
         }
 
+        public override IndexerKind Kind
+        {
+            get
+            {
+                return IndexerKind.Usenet;
+            }
+        }
+
         public override IParseFeed Parser
         {
             get
diff --git a/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs b/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs
index de2aa0a99..2b67dfe97 100644
--- a/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs
+++ b/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs
@@ -1,23 +1,17 @@
 using System.Xml.Linq;
-using NzbDrone.Core.Parser.Model;
 
 namespace NzbDrone.Core.Indexers.Wombles
 {
-    public class WomblesParser : BasicRssParser
+    public class WomblesParser : RssParserBase
     {
         protected override string GetNzbInfoUrl(XElement item)
         {
             return null;
         }
 
-        protected override ReportInfo PostProcessor(XElement item, ReportInfo currentResult)
+        protected override long GetSize(XElement item)
         {
-            if (currentResult != null)
-            {
-                currentResult.Size = 0;
-            }
-
-            return currentResult;
+            return 0;
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Indexers/XElementExtensions.cs b/NzbDrone.Core/Indexers/XElementExtensions.cs
index 8460c34d6..254c9ae6f 100644
--- a/NzbDrone.Core/Indexers/XElementExtensions.cs
+++ b/NzbDrone.Core/Indexers/XElementExtensions.cs
@@ -5,13 +5,13 @@ using System.Linq;
 using System.Text.RegularExpressions;
 using System.Xml.Linq;
 using NLog;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Core.Indexers
 {
     public static class XElementExtensions
     {
-
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
 
         private static readonly Regex RemoveTimeZoneRegex = new Regex(@"\s[A-Z]{2,4}$", RegexOptions.Compiled);
 
@@ -20,6 +20,21 @@ namespace NzbDrone.Core.Indexers
             return item.TryGetValue("title", "Unknown");
         }
 
+        public static XElement StripNameSpace(this XElement root)
+        {
+            var res = new XElement(
+               root.Name.LocalName,
+               root.HasElements ?
+                   root.Elements().Select(StripNameSpace) :
+                   (object)root.Value
+           );
+
+            res.ReplaceAttributes(
+                root.Attributes().Where(attr => (!attr.IsNamespaceDeclaration)));
+
+            return res;
+        }
+
         public static DateTime PublishDate(this XElement item)
         {
             string dateString = item.TryGetValue("pubDate");
@@ -32,7 +47,7 @@ namespace NzbDrone.Core.Indexers
                     dateString = RemoveTimeZoneRegex.Replace(dateString, "");
                     result = DateTime.Parse(dateString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal);
                 }
-                return result.ToUniversalTime();
+                return result.ToUniversalTime().Date;
             }
             catch (FormatException e)
             {
@@ -58,6 +73,11 @@ namespace NzbDrone.Core.Indexers
             return item.TryGetValue("comments");
         }
 
+        public static long Length(this XElement item)
+        {
+            return long.Parse(item.TryGetValue("length"));
+        }
+
         private static string TryGetValue(this XElement item, string elementName, string defaultValue = "")
         {
             var element = item.Element(elementName);
diff --git a/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs b/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs
index 19776e76d..986331002 100644
--- a/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs
+++ b/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs
@@ -1,8 +1,15 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Instrumentation.Commands
 {
-    public class ClearLogCommand : ICommand
+    public class ClearLogCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs
index 5d3228afb..1a331247e 100644
--- a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs
+++ b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs
@@ -1,8 +1,15 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Instrumentation.Commands
 {
-    public class DeleteLogFilesCommand : ICommand
+    public class DeleteLogFilesCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs b/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs
index c00b27020..c85427fc8 100644
--- a/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs
+++ b/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs
@@ -1,8 +1,8 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Instrumentation.Commands
 {
-    public class TrimLogCommand : ICommand
+    public class TrimLogCommand : Command
     {
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Instrumentation/DatabaseTarget.cs b/NzbDrone.Core/Instrumentation/DatabaseTarget.cs
index 779643240..673f4f9d6 100644
--- a/NzbDrone.Core/Instrumentation/DatabaseTarget.cs
+++ b/NzbDrone.Core/Instrumentation/DatabaseTarget.cs
@@ -3,8 +3,9 @@ using NLog.Config;
 using NLog;
 using NLog.Layouts;
 using NLog.Targets;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 
 namespace NzbDrone.Core.Instrumentation
 {
diff --git a/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs b/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs
index 5c98646ad..3a12cd057 100644
--- a/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs
+++ b/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs
@@ -1,12 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
+using System.IO;
 using NLog;
 using NzbDrone.Common;
 using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Instrumentation.Commands;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Instrumentation
 {
diff --git a/NzbDrone.Core/Instrumentation/LogRepository.cs b/NzbDrone.Core/Instrumentation/LogRepository.cs
index 0a00aa27b..8259b5cf7 100644
--- a/NzbDrone.Core/Instrumentation/LogRepository.cs
+++ b/NzbDrone.Core/Instrumentation/LogRepository.cs
@@ -1,6 +1,8 @@
 using System;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Instrumentation
 {
@@ -11,8 +13,8 @@ namespace NzbDrone.Core.Instrumentation
 
     public class LogRepository : BasicRepository<Log>, ILogRepository
     {
-        public LogRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public LogRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/Instrumentation/LogService.cs b/NzbDrone.Core/Instrumentation/LogService.cs
index 5e5717571..f46dbf5dd 100644
--- a/NzbDrone.Core/Instrumentation/LogService.cs
+++ b/NzbDrone.Core/Instrumentation/LogService.cs
@@ -1,7 +1,7 @@
-using System;
-using NzbDrone.Common.Messaging;
-using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Instrumentation.Commands;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Instrumentation
 {
diff --git a/NzbDrone.Core/Instrumentation/LoggerExtensions.cs b/NzbDrone.Core/Instrumentation/LoggerExtensions.cs
new file mode 100644
index 000000000..82ece28d2
--- /dev/null
+++ b/NzbDrone.Core/Instrumentation/LoggerExtensions.cs
@@ -0,0 +1,34 @@
+using System;
+using NLog;
+
+namespace NzbDrone.Core.Instrumentation
+{
+    public static class LoggerExtensions
+    {
+        public static void ProgressInfo(this Logger logger, string message, params object[] args)
+        {
+            var formattedMessage = String.Format(message, args);
+            LogProgressMessage(logger, LogLevel.Info, formattedMessage);
+        }
+
+        public static void ProgressDebug(this Logger logger, string message, params object[] args)
+        {
+            var formattedMessage = String.Format(message, args);
+            LogProgressMessage(logger, LogLevel.Debug, formattedMessage);
+        }
+
+        public static void ProgressTrace(this Logger logger, string message, params object[] args)
+        {
+            var formattedMessage = String.Format(message, args);
+            LogProgressMessage(logger, LogLevel.Trace, formattedMessage);
+        }
+
+        private static void LogProgressMessage(Logger logger, LogLevel level, string message)
+        {
+            var logEvent = new LogEventInfo(level, logger.Name, message);
+            logEvent.Properties.Add("Status", "");
+
+            logger.Log(logEvent);
+        }
+    }
+}
diff --git a/NzbDrone.Core/Instrumentation/SetLoggingLevel.cs b/NzbDrone.Core/Instrumentation/SetLoggingLevel.cs
index 251ef6f38..61412e8fb 100644
--- a/NzbDrone.Core/Instrumentation/SetLoggingLevel.cs
+++ b/NzbDrone.Core/Instrumentation/SetLoggingLevel.cs
@@ -2,10 +2,12 @@
 using System.Linq;
 using NLog;
 using NLog.Config;
-using NzbDrone.Common.Messaging;
+using NLog.Targets;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Configuration.Events;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 
 namespace NzbDrone.Core.Instrumentation
 {
@@ -28,10 +30,12 @@ namespace NzbDrone.Core.Instrumentation
             var minimumLogLevel = LogLevel.FromString(_configFileProvider.LogLevel);
 
             var rules = LogManager.Configuration.LoggingRules;
-            var rollingFileLogger = rules.Single(s => s.Targets.Any(t => t.Name == "rollingFileLogger"));
+            var rollingFileLogger = rules.Single(s => s.Targets.Any(t => t is FileTarget));
             rollingFileLogger.EnableLoggingForLevel(LogLevel.Trace);
 
             SetMinimumLogLevel(rollingFileLogger, minimumLogLevel);
+
+            LogManager.ReconfigExistingLoggers();
         }
 
         private void SetMinimumLogLevel(LoggingRule rule, LogLevel minimumLogLevel)
diff --git a/NzbDrone.Core/Jobs/JobRepository.cs b/NzbDrone.Core/Jobs/JobRepository.cs
index 700b8674e..8e2aa5858 100644
--- a/NzbDrone.Core/Jobs/JobRepository.cs
+++ b/NzbDrone.Core/Jobs/JobRepository.cs
@@ -1,7 +1,8 @@
 using System;
 using System.Linq;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Jobs
 {
@@ -15,8 +16,8 @@ namespace NzbDrone.Core.Jobs
     public class ScheduledTaskRepository : BasicRepository<ScheduledTask>, IScheduledTaskRepository
     {
 
-        public ScheduledTaskRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public ScheduledTaskRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/Jobs/Scheduler.cs b/NzbDrone.Core/Jobs/Scheduler.cs
index 20ed73489..2b8b42086 100644
--- a/NzbDrone.Core/Jobs/Scheduler.cs
+++ b/NzbDrone.Core/Jobs/Scheduler.cs
@@ -2,8 +2,10 @@
 using System.Threading;
 using System.Threading.Tasks;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using Timer = System.Timers.Timer;
 using NzbDrone.Common.TPL;
 
@@ -14,15 +16,15 @@ namespace NzbDrone.Core.Jobs
         IHandle<ApplicationShutdownRequested>
     {
         private readonly ITaskManager _taskManager;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly ICommandExecutor _commandExecutor;
         private readonly Logger _logger;
         private static readonly Timer Timer = new Timer();
         private static CancellationTokenSource _cancellationTokenSource;
 
-        public Scheduler(ITaskManager taskManager, IMessageAggregator messageAggregator, Logger logger)
+        public Scheduler(ITaskManager taskManager, ICommandExecutor commandExecutor, Logger logger)
         {
             _taskManager = taskManager;
-            _messageAggregator = messageAggregator;
+            _commandExecutor = commandExecutor;
             _logger = logger;
         }
 
@@ -52,7 +54,7 @@ namespace NzbDrone.Core.Jobs
 
                     try
                     {
-                        _messageAggregator.PublishCommand(task.TypeName);
+                        _commandExecutor.PublishCommand(task.TypeName);
                     }
                     catch (Exception e)
                     {
diff --git a/NzbDrone.Core/Jobs/TaskManager.cs b/NzbDrone.Core/Jobs/TaskManager.cs
index d77c8b0aa..af16df6f9 100644
--- a/NzbDrone.Core/Jobs/TaskManager.cs
+++ b/NzbDrone.Core/Jobs/TaskManager.cs
@@ -2,7 +2,6 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Configuration.Events;
 using NzbDrone.Core.DataAugmentation.Scene;
@@ -10,6 +9,9 @@ using NzbDrone.Core.Indexers;
 using NzbDrone.Core.Instrumentation.Commands;
 using NzbDrone.Core.Lifecycle;
 using NzbDrone.Core.MediaFiles.Commands;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands.Tracking;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Providers;
 using NzbDrone.Core.Tv.Commands;
 using NzbDrone.Core.Update.Commands;
@@ -49,7 +51,8 @@ namespace NzbDrone.Core.Jobs
                     new ScheduledTask{ Interval = 1, TypeName = typeof(DownloadedEpisodesScanCommand).FullName},
                     new ScheduledTask{ Interval = 60, TypeName = typeof(ApplicationUpdateCommand).FullName},
                     new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
-                    new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName}
+                    new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
+                    new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName}
                 };
 
             var currentTasks = _scheduledTaskRepository.All();
@@ -82,6 +85,7 @@ namespace NzbDrone.Core.Jobs
 
             if (scheduledTask != null)
             {
+                _logger.Trace("Updating last run time for: {0}", scheduledTask.TypeName);
                 _scheduledTaskRepository.SetLastExecutionTime(scheduledTask.Id, DateTime.UtcNow);
             }
         }
@@ -93,4 +97,4 @@ namespace NzbDrone.Core.Jobs
             _scheduledTaskRepository.Update(rss);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs b/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs
index 985986b05..f66622dd3 100644
--- a/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs
+++ b/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs
@@ -6,5 +6,4 @@ namespace NzbDrone.Core.Lifecycle
     {
 
     }
-
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/MediaCover/MediaCoverService.cs b/NzbDrone.Core/MediaCover/MediaCoverService.cs
index e3d247bfa..67b0d0b21 100644
--- a/NzbDrone.Core/MediaCover/MediaCoverService.cs
+++ b/NzbDrone.Core/MediaCover/MediaCoverService.cs
@@ -5,7 +5,8 @@ using System.Net;
 using NLog;
 using NzbDrone.Common;
 using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv;
 using NzbDrone.Core.Tv.Events;
 
diff --git a/NzbDrone.Core/MediaFiles/Commands/BackendCommandAttribute.cs b/NzbDrone.Core/MediaFiles/Commands/BackendCommandAttribute.cs
new file mode 100644
index 000000000..7e8347897
--- /dev/null
+++ b/NzbDrone.Core/MediaFiles/Commands/BackendCommandAttribute.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace NzbDrone.Core.MediaFiles.Commands
+{
+    public class BackendCommandAttribute : Attribute
+    {
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs b/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs
index b873dcc5f..a06cb29d7 100644
--- a/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs
+++ b/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs
@@ -1,8 +1,8 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.MediaFiles.Commands
 {
-    public class CleanMediaFileDb : ICommand
+    public class CleanMediaFileDb : Command
     {
         public int SeriesId { get; private set; }
 
diff --git a/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs b/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs
index b2d16f231..cb8d8c3a6 100644
--- a/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs
+++ b/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs
@@ -1,8 +1,8 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.MediaFiles.Commands
 {
-    public class CleanUpRecycleBinCommand : ICommand
+    public class CleanUpRecycleBinCommand : Command
     {
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs b/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs
index 0f03f2083..c9abe533a 100644
--- a/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs
+++ b/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs
@@ -1,11 +1,9 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.MediaFiles.Commands
 {
-    public class DownloadedEpisodesScanCommand : ICommand
+    public class DownloadedEpisodesScanCommand : Command
     {
-        public DownloadedEpisodesScanCommand()
-        {
-        }
+
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs b/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs
index 723c5d74b..b9a917ca3 100644
--- a/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs
+++ b/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs
@@ -1,11 +1,19 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.MediaFiles.Commands
 {
-    public class RenameSeasonCommand : ICommand
+    public class RenameSeasonCommand : Command
     {
-        public int SeriesId { get; private set; }
-        public int SeasonNumber { get; private set; }
+        public int SeriesId { get; set; }
+        public int SeasonNumber { get; set; }
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
 
         public RenameSeasonCommand(int seriesId, int seasonNumber)
         {
diff --git a/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs b/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs
index 7716c43c0..eb7e578d4 100644
--- a/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs
+++ b/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs
@@ -1,10 +1,22 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.MediaFiles.Commands
 {
-    public class RenameSeriesCommand : ICommand
+    public class RenameSeriesCommand : Command
     {
-        public int SeriesId { get; private set; }
+        public int SeriesId { get; set; }
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
+
+        public RenameSeriesCommand()
+        {
+        }
 
         public RenameSeriesCommand(int seriesId)
         {
diff --git a/NzbDrone.Core/MediaFiles/DiskScanService.cs b/NzbDrone.Core/MediaFiles/DiskScanService.cs
index 0fb6901fe..16b64789f 100644
--- a/NzbDrone.Core/MediaFiles/DiskScanService.cs
+++ b/NzbDrone.Core/MediaFiles/DiskScanService.cs
@@ -1,11 +1,13 @@
-using System.Collections.Generic;
-using System.IO;
+using System.IO;
 using System.Linq;
 using NLog;
 using NzbDrone.Common;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Instrumentation;
 using NzbDrone.Core.MediaFiles.Commands;
 using NzbDrone.Core.MediaFiles.EpisodeImport;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv;
 using NzbDrone.Core.Tv.Events;
 
@@ -20,39 +22,28 @@ namespace NzbDrone.Core.MediaFiles
         IDiskScanService,
         IHandle<SeriesUpdatedEvent>
     {
-        private readonly HashSet<string> _mediaExtensions;
-
-        private const string EXTENSIONS =
-            //XBMC
-            ".m4v .3gp .nsv .ts .ty .strm .rm .rmvb .m3u .ifo .mov .qt .divx .xvid .bivx .vob .nrg .img " +
-            ".iso .pva .wmv .asf .asx .ogm .m2v .avi .bin .dat .dvr-ms .mpg .mpeg .mp4 .mkv .avc .vp3 " +
-            ".svq3 .nuv .viv .dv .fli .flv .wpl " +
-            //Other
-            ".m2ts";
-
         private readonly IDiskProvider _diskProvider;
         private readonly IMakeImportDecision _importDecisionMaker;
         private readonly IImportApprovedEpisodes _importApprovedEpisodes;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly ICommandExecutor _commandExecutor;
         private readonly Logger _logger;
 
         public DiskScanService(IDiskProvider diskProvider,
                                 IMakeImportDecision importDecisionMaker,
                                 IImportApprovedEpisodes importApprovedEpisodes,
-                                IMessageAggregator messageAggregator, Logger logger)
+                                ICommandExecutor commandExecutor, Logger logger)
         {
             _diskProvider = diskProvider;
             _importDecisionMaker = importDecisionMaker;
             _importApprovedEpisodes = importApprovedEpisodes;
-            _messageAggregator = messageAggregator;
+            _commandExecutor = commandExecutor;
             _logger = logger;
-
-            _mediaExtensions = new HashSet<string>(EXTENSIONS.Split(' ').Select(c => c.ToLower()));
         }
 
         private void Scan(Series series)
         {
-            _messageAggregator.PublishCommand(new CleanMediaFileDb(series.Id));
+            _logger.ProgressInfo("Scanning disk for {0}", series.Title);
+            _commandExecutor.PublishCommand(new CleanMediaFileDb(series.Id));
 
             if (!_diskProvider.FolderExists(series.Path))
             {
@@ -73,7 +64,7 @@ namespace NzbDrone.Core.MediaFiles
             var searchOption = allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
             var filesOnDisk = _diskProvider.GetFiles(path, searchOption);
 
-            var mediaFileList = filesOnDisk.Where(c => _mediaExtensions.Contains(Path.GetExtension(c).ToLower())).ToList();
+            var mediaFileList = filesOnDisk.Where(c => MediaFileExtensions.Extensions.Contains(Path.GetExtension(c).ToLower())).ToList();
 
             _logger.Trace("{0} video files were found in {1}", mediaFileList.Count, path);
             return mediaFileList.ToArray();
diff --git a/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
index f5bf03915..55c6f4638 100644
--- a/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
+++ b/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
@@ -4,11 +4,12 @@ using System.IO;
 using System.Linq;
 using NLog;
 using NzbDrone.Common;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.MediaFiles.Commands;
 using NzbDrone.Core.MediaFiles.EpisodeImport;
 using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.Parser;
 using NzbDrone.Core.Tv;
 
@@ -51,13 +52,13 @@ namespace NzbDrone.Core.MediaFiles
 
             if (String.IsNullOrEmpty(downloadedEpisodesFolder))
             {
-                _logger.Warn("Downloaded Episodes Folder is not configured");
+                _logger.Warn("Drone Factory folder is not configured");
                 return;
             }
 
             if (!_diskProvider.FolderExists(downloadedEpisodesFolder))
             {
-                _logger.Warn("Downloaded Episodes Folder [{0}] doesn't exist.", downloadedEpisodesFolder);
+                _logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder);
                 return;
             }
 
@@ -112,7 +113,7 @@ namespace NzbDrone.Core.MediaFiles
 
             var videoFiles = _diskScanService.GetVideoFiles(subfolderInfo.FullName);
 
-            return ProcessFiles(videoFiles, series);
+            return ProcessFiles(series, videoFiles);
         }
 
         private void ProcessVideoFile(string videoFile)
@@ -125,16 +126,16 @@ namespace NzbDrone.Core.MediaFiles
                 return;
             }
 
-            if (_diskProvider.IsFileLocked(new FileInfo(videoFile)))
+            if (_diskProvider.IsFileLocked(videoFile))
             {
                 _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
                 return;
             }
 
-            ProcessFiles(new[] { videoFile }, series);
+            ProcessFiles(series, videoFile);
         }
 
-        private List<ImportDecision> ProcessFiles(IEnumerable<string> videoFiles, Series series)
+        private List<ImportDecision> ProcessFiles(Series series, params string[] videoFiles)
         {
             var decisions = _importDecisionMaker.GetImportDecisions(videoFiles, series, true);
             return _importApprovedEpisodes.Import(decisions, true);
diff --git a/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs
index ef230443b..91157d26a 100644
--- a/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs
+++ b/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs
@@ -3,8 +3,8 @@ using System.IO;
 using System.Linq;
 using NLog;
 using NzbDrone.Common;
-using NzbDrone.Common.Messaging;
-using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Organizer;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Tv;
@@ -21,19 +21,19 @@ namespace NzbDrone.Core.MediaFiles
     {
         private readonly IEpisodeService _episodeService;
         private readonly IBuildFileNames _buildFileNames;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly IDiskProvider _diskProvider;
         private readonly Logger _logger;
 
         public MoveEpisodeFiles(IEpisodeService episodeService,
                                 IBuildFileNames buildFileNames,
-                                IMessageAggregator messageAggregator,
+                                IEventAggregator eventAggregator,
                                 IDiskProvider diskProvider,
                                 Logger logger)
         {
             _episodeService = episodeService;
             _buildFileNames = buildFileNames;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _diskProvider = diskProvider;
             _logger = logger;
         }
diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs
index 733b8580d..a9a92bb87 100644
--- a/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs
+++ b/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs
@@ -4,8 +4,10 @@ using System.IO;
 using System.Linq;
 using NLog;
 using NzbDrone.Common;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.MediaFiles.EpisodeImport
 {
@@ -19,19 +21,19 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
         private readonly IUpgradeMediaFiles _episodeFileUpgrader;
         private readonly IMediaFileService _mediaFileService;
         private readonly IDiskProvider _diskProvider;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly Logger _logger;
 
         public ImportApprovedEpisodes(IUpgradeMediaFiles episodeFileUpgrader,
                                       IMediaFileService mediaFileService,
                                       IDiskProvider diskProvider,
-                                      IMessageAggregator messageAggregator,
+                                      IEventAggregator eventAggregator,
                                       Logger logger)
         {
             _episodeFileUpgrader = episodeFileUpgrader;
             _mediaFileService = mediaFileService;
             _diskProvider = diskProvider;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _logger = logger;
         }
 
@@ -69,8 +71,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
                     {
                         episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath());
                         episodeFile.Path = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode);
-                        _messageAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile));
-                        _messageAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode));
+                        _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile));
+                        _eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode));
                     }
 
                     _mediaFileService.Add(episodeFile);
diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs
index 9c499d8fb..609a962cd 100644
--- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs
+++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs
@@ -23,22 +23,33 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
         {
             try
             {
+                if (localEpisode.ExistingFile)
+                {
+                    _logger.Trace("Skipping free space check for existing episode");
+                    return true;
+                }
+
                 var path = Directory.GetParent(localEpisode.Series.Path);
                 var freeSpace = _diskProvider.GetAvailableSpace(path.FullName);
 
+                if (!freeSpace.HasValue)
+                {
+                    _logger.Trace("Free space check returned an invalid result for: {0}", path);
+                    return true;
+                }
+
                 if (freeSpace < localEpisode.Size + 100.Megabytes())
                 {
                     _logger.Warn("Not enough free space to import: {0}", localEpisode);
                     return false;
                 }
-
-                return true;
             }
             catch (Exception ex)
             {
                 _logger.ErrorException("Unable to check free disk space while importing: " + localEpisode.Path, ex);
-                throw;
             }
+
+            return true;
         }
     }
 }
diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotInUseSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotInUseSpecification.cs
index 5fd2f453b..1da911a60 100644
--- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotInUseSpecification.cs
+++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotInUseSpecification.cs
@@ -1,5 +1,4 @@
-using System.IO;
-using NLog;
+using NLog;
 using NzbDrone.Common;
 using NzbDrone.Core.Parser.Model;
 
@@ -20,13 +19,13 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
 
         public bool IsSatisfiedBy(LocalEpisode localEpisode)
         {
-            if (_diskProvider.IsParent(localEpisode.Series.Path, localEpisode.Path))
+            if (localEpisode.ExistingFile)
             {
                 _logger.Trace("{0} is in series folder, skipping in use check", localEpisode.Path);
                 return true;
             }
 
-            if (_diskProvider.IsFileLocked(new FileInfo(localEpisode.Path)))
+            if (_diskProvider.IsFileLocked(localEpisode.Path))
             {
                 _logger.Trace("{0} is in use");
                 return false;
diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs
index 51561b005..3d2901974 100644
--- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs
+++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs
@@ -1,6 +1,7 @@
 using System;
 using System.IO;
 using NLog;
+using NzbDrone.Core.MediaFiles.MediaInfo;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Providers;
 using NzbDrone.Core.Tv;
diff --git a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs
index 0f1e66d3a..b8212e12c 100644
--- a/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs
+++ b/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
 
         public bool IsSatisfiedBy(LocalEpisode localEpisode)
         {
-            if (_diskProvider.IsParent(localEpisode.Series.Path, localEpisode.Path))
+            if (localEpisode.ExistingFile)
             {
                 _logger.Trace("{0} is in series folder, unpacking check", localEpisode.Path);
                 return true;
diff --git a/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs b/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs
new file mode 100644
index 000000000..9416e4d4d
--- /dev/null
+++ b/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using NzbDrone.Core.Qualities;
+
+
+namespace NzbDrone.Core.MediaFiles
+{
+    public static class MediaFileExtensions
+    {
+        private static Dictionary<String, Quality> _fileExtensions;
+
+        static MediaFileExtensions()
+        {
+            _fileExtensions = new Dictionary<String, Quality>
+            {
+                { ".m4v", Quality.SDTV },
+                { ".3gp", Quality.SDTV },
+                { ".nsv", Quality.SDTV },
+                { ".ty", Quality.SDTV },
+                { ".strm", Quality.SDTV },
+                { ".rm", Quality.SDTV },
+                { ".rmvb", Quality.SDTV },
+                { ".m3u", Quality.SDTV },
+                { ".ifo", Quality.SDTV },
+                { ".mov", Quality.SDTV },
+                { ".qt", Quality.SDTV },
+                { ".divx", Quality.SDTV },
+                { ".xvid", Quality.SDTV },
+                { ".bivx", Quality.SDTV },
+                { ".nrg", Quality.SDTV },
+                { ".pva", Quality.SDTV },
+                { ".wmv", Quality.SDTV },
+                { ".asf", Quality.SDTV },
+                { ".asx", Quality.SDTV },
+                { ".ogm", Quality.SDTV },
+                { ".m2v", Quality.SDTV },
+                { ".avi", Quality.SDTV },
+                { ".bin", Quality.SDTV },
+                { ".dat", Quality.SDTV },
+                { ".dvr-ms", Quality.SDTV },
+                { ".mpg", Quality.SDTV },
+                { ".mpeg", Quality.SDTV },
+                { ".mp4", Quality.SDTV },
+                { ".avc", Quality.SDTV },
+                { ".vp3", Quality.SDTV },
+                { ".svq3", Quality.SDTV },
+                { ".nuv", Quality.SDTV },
+                { ".viv", Quality.SDTV },
+                { ".dv", Quality.SDTV },
+                { ".fli", Quality.SDTV },
+                { ".flv", Quality.SDTV },
+                { ".wpl", Quality.SDTV },
+
+                //DVD
+                { ".img", Quality.DVD },
+                { ".iso", Quality.DVD },
+                { ".vob", Quality.DVD },
+
+                //HD
+                { ".mkv", Quality.HDTV720p },
+                { ".ts", Quality.HDTV720p },
+
+                //Bluray
+                { ".m2ts", Quality.Bluray720p }
+            };
+        }
+
+        public static HashSet<String> Extensions
+        {
+            get { return new HashSet<String>(_fileExtensions.Keys); }
+        }
+
+        public static Quality GetQualityForExtension(string extension)
+        {
+            if (_fileExtensions.ContainsKey(extension))
+            {
+                return _fileExtensions[extension];
+            }
+
+            return Quality.Unknown;
+        }
+    }
+}
diff --git a/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/NzbDrone.Core/MediaFiles/MediaFileRepository.cs
index 406bd9669..7a0839594 100644
--- a/NzbDrone.Core/MediaFiles/MediaFileRepository.cs
+++ b/NzbDrone.Core/MediaFiles/MediaFileRepository.cs
@@ -1,7 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.MediaFiles
 {
@@ -16,8 +18,8 @@ namespace NzbDrone.Core.MediaFiles
 
     public class MediaFileRepository : BasicRepository<EpisodeFile>, IMediaFileRepository
     {
-        public MediaFileRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public MediaFileRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/MediaFiles/MediaFileService.cs b/NzbDrone.Core/MediaFiles/MediaFileService.cs
index 54501c859..b963b408e 100644
--- a/NzbDrone.Core/MediaFiles/MediaFileService.cs
+++ b/NzbDrone.Core/MediaFiles/MediaFileService.cs
@@ -1,9 +1,9 @@
-using System;
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv.Events;
 using NzbDrone.Common;
 
@@ -24,21 +24,21 @@ namespace NzbDrone.Core.MediaFiles
 
     public class MediaFileService : IMediaFileService, IHandleAsync<SeriesDeletedEvent>
     {
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly IMediaFileRepository _mediaFileRepository;
         private readonly Logger _logger;
 
-        public MediaFileService(IMediaFileRepository mediaFileRepository, IMessageAggregator messageAggregator, Logger logger)
+        public MediaFileService(IMediaFileRepository mediaFileRepository, IEventAggregator eventAggregator, Logger logger)
         {
             _mediaFileRepository = mediaFileRepository;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _logger = logger;
         }
 
         public EpisodeFile Add(EpisodeFile episodeFile)
         {
             var addedFile = _mediaFileRepository.Insert(episodeFile);
-            _messageAggregator.PublishEvent(new EpisodeFileAddedEvent(addedFile));
+            _eventAggregator.PublishEvent(new EpisodeFileAddedEvent(addedFile));
             return addedFile;
         }
 
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.MediaFiles
         {
             _mediaFileRepository.Delete(episodeFile);
 
-            _messageAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, forUpgrade));
+            _eventAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, forUpgrade));
         }
 
         public bool Exists(string path)
diff --git a/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs b/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs
index b72f03b4f..26c4d1905 100644
--- a/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs
+++ b/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs
@@ -2,8 +2,9 @@ using System;
 using System.Linq;
 using NLog;
 using NzbDrone.Common;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.MediaFiles.Commands;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.MediaFiles
diff --git a/NzbDrone.Core/Providers/VideoFileInfoReader.cs b/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
similarity index 93%
rename from NzbDrone.Core/Providers/VideoFileInfoReader.cs
rename to NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
index 1aa8c121e..a2f1143e2 100644
--- a/NzbDrone.Core/Providers/VideoFileInfoReader.cs
+++ b/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs
@@ -5,7 +5,7 @@ using NLog;
 using NzbDrone.Common;
 using NzbDrone.Core.Model;
 
-namespace NzbDrone.Core.Providers
+namespace NzbDrone.Core.MediaFiles.MediaInfo
 {
     public interface IVideoFileInfoReader
     {
@@ -31,7 +31,7 @@ namespace NzbDrone.Core.Providers
             if (!_diskProvider.FileExists(filename))
                 throw new FileNotFoundException("Media file does not exist: " + filename);
 
-            var mediaInfo = new MediaInfo();
+            var mediaInfo = new MediaInfoLib.MediaInfo();
 
             try
             {
@@ -112,10 +112,10 @@ namespace NzbDrone.Core.Providers
 
         public TimeSpan GetRunTime(string filename)
         {
-            var mediaInfo = new MediaInfo();
-
+            MediaInfoLib.MediaInfo mediaInfo = null;
             try
             {
+                mediaInfo = new MediaInfoLib.MediaInfo();
                 _logger.Trace("Getting media info from {0}", filename);
 
                 mediaInfo.Option("ParseSpeed", "0.2");
@@ -133,7 +133,13 @@ namespace NzbDrone.Core.Providers
             catch (Exception ex)
             {
                 _logger.ErrorException("Unable to parse media info from file: " + filename, ex);
-                mediaInfo.Close();
+            }
+            finally
+            {
+                if (mediaInfo != null)
+                {
+                    mediaInfo.Close();
+                }
             }
 
             return new TimeSpan();
diff --git a/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs b/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs
index ff3866b39..00ff120e2 100644
--- a/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs
+++ b/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs
@@ -3,9 +3,12 @@ using System.IO;
 using NLog;
 using NzbDrone.Common;
 using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.MediaFiles.Commands;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv.Events;
 
 namespace NzbDrone.Core.MediaFiles
@@ -23,7 +26,7 @@ namespace NzbDrone.Core.MediaFiles
         private readonly IDiskProvider _diskProvider;
         private readonly IConfigService _configService;
 
-        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger logger =  NzbDroneLogger.GetLogger();
 
         public RecycleBinProvider(IDiskProvider diskProvider, IConfigService configService)
         {
diff --git a/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs b/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs
index 7912039e7..501963445 100644
--- a/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs
+++ b/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs
@@ -2,9 +2,12 @@
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Instrumentation;
 using NzbDrone.Core.MediaFiles.Commands;
 using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.MediaFiles
@@ -14,19 +17,19 @@ namespace NzbDrone.Core.MediaFiles
         private readonly ISeriesService _seriesService;
         private readonly IMediaFileService _mediaFileService;
         private readonly IMoveEpisodeFiles _episodeFileMover;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly Logger _logger;
 
         public RenameEpisodeFileService(ISeriesService seriesService,
                                         IMediaFileService mediaFileService,
                                         IMoveEpisodeFiles episodeFileMover,
-                                        IMessageAggregator messageAggregator,
+                                        IEventAggregator eventAggregator,
                                         Logger logger)
         {
             _seriesService = seriesService;
             _mediaFileService = mediaFileService;
             _episodeFileMover = episodeFileMover;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _logger = logger;
         }
 
@@ -58,7 +61,7 @@ namespace NzbDrone.Core.MediaFiles
 
             if (renamed.Any())
             {
-                _messageAggregator.PublishEvent(new SeriesRenamedEvent(series));
+                _eventAggregator.PublishEvent(new SeriesRenamedEvent(series));
             }
         }
 
@@ -67,9 +70,9 @@ namespace NzbDrone.Core.MediaFiles
             var series = _seriesService.GetSeries(message.SeriesId);
             var episodeFiles = _mediaFileService.GetFilesBySeason(message.SeriesId, message.SeasonNumber);
 
-            _logger.Info("Renaming {0} files for {1} season {2}", episodeFiles.Count, series.Title, message.SeasonNumber);
+            _logger.ProgressInfo("Renaming {0} files for {1} season {2}", episodeFiles.Count, series.Title, message.SeasonNumber);
             RenameFiles(episodeFiles, series);
-            _logger.Debug("Episode Fies renamed for {0} season {1}", series.Title, message.SeasonNumber);
+            _logger.ProgressInfo("Episode Fies renamed for {0} season {1}", series.Title, message.SeasonNumber);
         }
 
         public void Execute(RenameSeriesCommand message)
@@ -77,9 +80,9 @@ namespace NzbDrone.Core.MediaFiles
             var series = _seriesService.GetSeries(message.SeriesId);
             var episodeFiles = _mediaFileService.GetFilesBySeries(message.SeriesId);
 
-            _logger.Info("Renaming {0} files for {1}", episodeFiles.Count, series.Title);
+            _logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title);
             RenameFiles(episodeFiles, series);
-            _logger.Debug("Episode Fies renamed for {0}", series.Title);
+            _logger.ProgressInfo("Episode Fies renamed for {0}", series.Title);
         }
     }
 }
diff --git a/NzbDrone.Core/Messaging/Commands/BackendCommandAttribute.cs b/NzbDrone.Core/Messaging/Commands/BackendCommandAttribute.cs
new file mode 100644
index 000000000..b40f64f9c
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/BackendCommandAttribute.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace NzbDrone.Core.Messaging.Commands
+{
+    public class BackendCommandAttribute : Attribute
+    {
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Messaging/Commands/Command.cs b/NzbDrone.Core/Messaging/Commands/Command.cs
new file mode 100644
index 000000000..516464381
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/Command.cs
@@ -0,0 +1,82 @@
+using System;
+using FluentMigrator.Runner;
+using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging.Commands.Tracking;
+
+namespace NzbDrone.Core.Messaging.Commands
+{
+    public abstract class Command : ModelBase, IMessage
+    {
+        private static readonly object Mutex = new object();
+        private static int _idCounter;
+        private readonly StopWatch _stopWatch;
+
+        public CommandStatus State { get; private set; }
+        public DateTime StateChangeTime { get; private set; }
+
+        public virtual bool SendUpdatesToClient
+        {
+            get
+            {
+                return false;
+            }
+        }
+
+        public TimeSpan Runtime
+        {
+            get
+            {
+                return _stopWatch.ElapsedTime();
+            }
+        }
+
+        public Exception Exception { get; private set; }
+        public string Message { get; private set; }
+
+        public string Name { get; private set; }
+
+        protected Command()
+        {
+            Name = GetType().Name.Replace("Command", "");
+            StateChangeTime = DateTime.UtcNow;
+            State = CommandStatus.Pending;
+            _stopWatch = new StopWatch();
+
+            lock (Mutex)
+            {
+                Id = ++_idCounter;
+            }
+        }
+
+        public void Start()
+        {
+            _stopWatch.Start();
+            StateChangeTime = DateTime.UtcNow;
+            State = CommandStatus.Running;
+            SetMessage("Starting");
+        }
+
+        public void Failed(Exception exception)
+        {
+            _stopWatch.Stop();
+            StateChangeTime = DateTime.UtcNow;
+            State = CommandStatus.Failed;
+            Exception = exception;
+            SetMessage("Failed");
+        }
+
+        public void Completed()
+        {
+            _stopWatch.Stop();
+            StateChangeTime = DateTime.UtcNow;
+            State = CommandStatus.Completed;
+            SetMessage("Completed");
+        }
+
+        public void SetMessage(string message)
+        {
+            Message = message;
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Messaging/Commands/CommandEqualityComparer.cs b/NzbDrone.Core/Messaging/Commands/CommandEqualityComparer.cs
new file mode 100644
index 000000000..2b24895f1
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/CommandEqualityComparer.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NzbDrone.Core.Messaging.Commands
+{
+    public class CommandEqualityComparer : IEqualityComparer<Command>
+    {
+        public static readonly CommandEqualityComparer Instance = new CommandEqualityComparer();
+
+        private CommandEqualityComparer()
+        {
+
+        }
+
+        public bool Equals(Command x, Command y)
+        {
+            if(x.GetType() != y.GetType()) return false;
+
+            var xProperties = x.GetType().GetProperties();
+            var yProperties = y.GetType().GetProperties();
+
+            foreach (var xProperty in xProperties)
+            {
+                if (xProperty.Name == "Id")
+                {
+                    continue;
+                }
+
+                var yProperty = yProperties.Single(p => p.Name == xProperty.Name);
+
+                var xValue = xProperty.GetValue(x, null);
+                var yValue = yProperty.GetValue(y, null);
+
+                if (xValue == null && yValue == null)
+                {
+                    return true;
+                }
+
+                if (xValue == null || yValue == null)
+                {
+                    return false;
+                }
+
+                if (!xValue.Equals(yValue))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        public int GetHashCode(Command obj)
+        {
+            return obj.Id.GetHashCode();
+        }
+    }
+}
diff --git a/NzbDrone.Core/Messaging/Commands/CommandExecutor.cs b/NzbDrone.Core/Messaging/Commands/CommandExecutor.cs
new file mode 100644
index 000000000..dd3175304
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/CommandExecutor.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using NLog;
+using NzbDrone.Common;
+using NzbDrone.Common.EnsureThat;
+using NzbDrone.Common.Serializer;
+using NzbDrone.Common.TPL;
+using NzbDrone.Core.Messaging.Commands.Tracking;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.ProgressMessaging;
+
+namespace NzbDrone.Core.Messaging.Commands
+{
+    public class CommandExecutor : ICommandExecutor
+    {
+        private readonly Logger _logger;
+        private readonly IServiceFactory _serviceFactory;
+        private readonly ITrackCommands _trackCommands;
+        private readonly IEventAggregator _eventAggregator;
+        private readonly TaskFactory _taskFactory;
+
+        public CommandExecutor(Logger logger, IServiceFactory serviceFactory, ITrackCommands trackCommands, IEventAggregator eventAggregator)
+        {
+            var scheduler = new LimitedConcurrencyLevelTaskScheduler(3);
+
+            _logger = logger;
+            _serviceFactory = serviceFactory;
+            _trackCommands = trackCommands;
+            _eventAggregator = eventAggregator;
+            _taskFactory = new TaskFactory(scheduler);
+        }
+
+        public void PublishCommand<TCommand>(TCommand command) where TCommand : Command
+        {
+            Ensure.That(() => command).IsNotNull();
+
+            _logger.Trace("Publishing {0}", command.GetType().Name);
+
+            if (_trackCommands.FindExisting(command) != null)
+            {
+                _logger.Debug("Command is already in progress: {0}", command.GetType().Name);
+                return;
+            }
+
+            _trackCommands.Store(command);
+
+            ExecuteCommand<TCommand>(command);
+        }
+
+        public void PublishCommand(string commandTypeName)
+        {
+            dynamic command = GetCommand(commandTypeName);
+            PublishCommand(command);
+        }
+
+        public Command PublishCommandAsync<TCommand>(TCommand command) where TCommand : Command
+        {
+            Ensure.That(() => command).IsNotNull();
+
+            _logger.Trace("Publishing {0}", command.GetType().Name);
+
+            var existingCommand = _trackCommands.FindExisting(command);
+
+            if (existingCommand != null)
+            {
+                _logger.Debug("Command is already in progress: {0}", command.GetType().Name);
+                return existingCommand;
+            }
+
+            _trackCommands.Store(command);
+
+            _taskFactory.StartNew(() => ExecuteCommand<TCommand>(command)
+                , TaskCreationOptions.PreferFairness)
+                .LogExceptions();
+
+            return command;
+        }
+
+        public Command PublishCommandAsync(string commandTypeName)
+        {
+            dynamic command = GetCommand(commandTypeName);
+            return PublishCommandAsync(command);
+        }
+
+        private dynamic GetCommand(string commandTypeName)
+        {
+            var commandType = _serviceFactory.GetImplementations(typeof(Command))
+                .Single(c => c.FullName.Equals(commandTypeName, StringComparison.InvariantCultureIgnoreCase));
+
+            return Json.Deserialize("{}", commandType);
+        }
+
+        private void ExecuteCommand<TCommand>(Command command) where TCommand : Command
+        {
+            var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType());
+            var handler = (IExecute<TCommand>)_serviceFactory.Build(handlerContract);
+
+            _logger.Trace("{0} -> {1}", command.GetType().Name, handler.GetType().Name);
+
+            try
+            {
+                _trackCommands.Start(command);
+                BroadcastCommandUpdate(command);
+
+                if (!MappedDiagnosticsContext.Contains("CommandId") && command.SendUpdatesToClient)
+                {
+                    MappedDiagnosticsContext.Set("CommandId", command.Id.ToString());
+                }
+
+                handler.Execute((TCommand)command);
+                _trackCommands.Completed(command);
+            }
+            catch (Exception e)
+            {
+                _trackCommands.Failed(command, e);
+                throw;
+            }
+            finally
+            {
+                BroadcastCommandUpdate(command);
+                _eventAggregator.PublishEvent(new CommandExecutedEvent(command));
+
+                if (MappedDiagnosticsContext.Get("CommandId") == command.Id.ToString())
+                {
+                    MappedDiagnosticsContext.Remove("CommandId");
+                }
+            }
+
+            _logger.Trace("{0} <- {1} [{2}]", command.GetType().Name, handler.GetType().Name, command.Runtime.ToString(""));
+        }
+
+
+        private void BroadcastCommandUpdate(Command command)
+        {
+            if (command.SendUpdatesToClient)
+            {
+                _eventAggregator.PublishEvent(new CommandUpdatedEvent(command));
+            }
+        }
+    }
+}
diff --git a/NzbDrone.Core/Messaging/Commands/ICommandExecutor.cs b/NzbDrone.Core/Messaging/Commands/ICommandExecutor.cs
new file mode 100644
index 000000000..45d300fcd
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/ICommandExecutor.cs
@@ -0,0 +1,10 @@
+namespace NzbDrone.Core.Messaging.Commands
+{
+    public interface ICommandExecutor
+    {
+        void PublishCommand<TCommand>(TCommand command) where TCommand : Command;
+        void PublishCommand(string commandTypeName);
+        Command PublishCommandAsync<TCommand>(TCommand command) where TCommand : Command;
+        Command PublishCommandAsync(string commandTypeName);
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/IExecute.cs b/NzbDrone.Core/Messaging/Commands/IExecute.cs
similarity index 61%
rename from NzbDrone.Common/Messaging/IExecute.cs
rename to NzbDrone.Core/Messaging/Commands/IExecute.cs
index a065e0828..4e058e4d0 100644
--- a/NzbDrone.Common/Messaging/IExecute.cs
+++ b/NzbDrone.Core/Messaging/Commands/IExecute.cs
@@ -1,6 +1,6 @@
-namespace NzbDrone.Common.Messaging
+namespace NzbDrone.Core.Messaging.Commands
 {
-    public interface IExecute<TCommand> : IProcessMessage<TCommand> where TCommand : ICommand
+    public interface IExecute<TCommand> : IProcessMessage<TCommand> where TCommand : Command
     {
         void Execute(TCommand message);
     }
diff --git a/NzbDrone.Core/Messaging/Commands/TestCommand.cs b/NzbDrone.Core/Messaging/Commands/TestCommand.cs
new file mode 100644
index 000000000..616c99ee7
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/TestCommand.cs
@@ -0,0 +1,20 @@
+namespace NzbDrone.Core.Messaging.Commands
+{
+    public class TestCommand : Command
+    {
+        public int Duration { get; set; }
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
+        
+        public TestCommand()
+        {
+            Duration = 4000;
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Messaging/Commands/TestCommandExecutor.cs b/NzbDrone.Core/Messaging/Commands/TestCommandExecutor.cs
new file mode 100644
index 000000000..8e0ebfb71
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/TestCommandExecutor.cs
@@ -0,0 +1,23 @@
+using System.Threading;
+using NLog;
+using NzbDrone.Core.Instrumentation;
+
+namespace NzbDrone.Core.Messaging.Commands
+{
+    public class TestCommandExecutor : IExecute<TestCommand>
+    {
+        private readonly Logger _logger;
+
+        public TestCommandExecutor(Logger logger)
+        {
+            _logger = logger;
+        }
+
+        public void Execute(TestCommand message)
+        {
+            _logger.ProgressInfo("Starting Test command. duration {0}", message.Duration);
+            Thread.Sleep(message.Duration);
+            _logger.ProgressInfo("Completed Test command");
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Messaging/Commands/Tracking/CommandStatus.cs b/NzbDrone.Core/Messaging/Commands/Tracking/CommandStatus.cs
new file mode 100644
index 000000000..26208e9dd
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/Tracking/CommandStatus.cs
@@ -0,0 +1,10 @@
+namespace NzbDrone.Core.Messaging.Commands.Tracking
+{
+    public enum CommandStatus
+    {
+        Pending,
+        Running,
+        Completed,
+        Failed
+    }
+}
diff --git a/NzbDrone.Core/Messaging/Commands/Tracking/CommandTrackingService.cs b/NzbDrone.Core/Messaging/Commands/Tracking/CommandTrackingService.cs
new file mode 100644
index 000000000..3e5a6c619
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/Tracking/CommandTrackingService.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NzbDrone.Common.Cache;
+
+namespace NzbDrone.Core.Messaging.Commands.Tracking
+{
+    public interface ITrackCommands
+    {
+        Command GetById(int id);
+        Command GetById(string id);
+        void Completed(Command trackedCommand);
+        void Failed(Command trackedCommand, Exception e);
+        IEnumerable<Command> RunningCommands();
+        Command FindExisting(Command command);
+        void Store(Command command);
+        void Start(Command command);
+    }
+
+    public class CommandTrackingService : ITrackCommands, IExecute<TrackedCommandCleanupCommand>
+    {
+        private readonly ICached<Command> _cache;
+
+        public CommandTrackingService(ICacheManger cacheManger)
+        {
+            _cache = cacheManger.GetCache<Command>(GetType());
+        }
+
+        public Command GetById(int id)
+        {
+            return _cache.Find(id.ToString());
+        }
+
+        public Command GetById(string id)
+        {
+            return _cache.Find(id);
+        }
+
+        public void Start(Command command)
+        {
+            command.Start();
+        }
+
+        public void Completed(Command trackedCommand)
+        {
+            trackedCommand.Completed();
+        }
+
+        public void Failed(Command trackedCommand, Exception e)
+        {
+            trackedCommand.Failed(e);
+        }
+
+        public IEnumerable<Command> RunningCommands()
+        {
+            return _cache.Values.Where(c => c.State == CommandStatus.Running);
+        }
+
+        public Command FindExisting(Command command)
+        {
+            return RunningCommands().Where(c => c.GetType() == command.GetType())
+                .SingleOrDefault(t => CommandEqualityComparer.Instance.Equals(t, command));
+        }
+
+        public void Store(Command command)
+        {
+            if (command.GetType() == typeof(TrackedCommandCleanupCommand))
+            {
+                return;
+            }
+
+            _cache.Set(command.Id.ToString(), command);
+        }
+
+        public void Execute(TrackedCommandCleanupCommand message)
+        {
+            var old = _cache.Values.Where(c => c.State != CommandStatus.Running && c.StateChangeTime < DateTime.UtcNow.AddMinutes(-5));
+
+            foreach (var trackedCommand in old)
+            {
+                _cache.Remove(trackedCommand.Id.ToString());
+            }
+        }
+    }
+}
diff --git a/NzbDrone.Core/Messaging/Commands/Tracking/ExistingCommand.cs b/NzbDrone.Core/Messaging/Commands/Tracking/ExistingCommand.cs
new file mode 100644
index 000000000..4fa174d3c
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/Tracking/ExistingCommand.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace NzbDrone.Core.Messaging.Commands.Tracking
+{
+    public class ExistingCommand
+    {
+        public Boolean Existing { get; set; }
+        public Command Command { get; set; }
+
+        public ExistingCommand(Boolean exisitng, Command trackedCommand)
+        {
+            Existing = exisitng;
+            Command = trackedCommand;
+        }
+    }
+}
diff --git a/NzbDrone.Core/Messaging/Commands/Tracking/TrackedCommandCleanupCommand.cs b/NzbDrone.Core/Messaging/Commands/Tracking/TrackedCommandCleanupCommand.cs
new file mode 100644
index 000000000..744b8a60f
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Commands/Tracking/TrackedCommandCleanupCommand.cs
@@ -0,0 +1,7 @@
+namespace NzbDrone.Core.Messaging.Commands.Tracking
+{
+    public class TrackedCommandCleanupCommand : Command
+    {
+ 
+    }
+}
diff --git a/NzbDrone.Core/Messaging/Events/CommandCreatedEvent.cs b/NzbDrone.Core/Messaging/Events/CommandCreatedEvent.cs
new file mode 100644
index 000000000..5224e2671
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Events/CommandCreatedEvent.cs
@@ -0,0 +1,15 @@
+using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.Messaging.Events
+{
+    public class CommandCreatedEvent : IEvent
+    {
+        public Command Command { get; private set; }
+
+        public CommandCreatedEvent(Command trackedCommand)
+        {
+            Command = trackedCommand;
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Messaging/Events/CommandExecutedEvent.cs b/NzbDrone.Core/Messaging/Events/CommandExecutedEvent.cs
new file mode 100644
index 000000000..83fb2abdb
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Events/CommandExecutedEvent.cs
@@ -0,0 +1,15 @@
+using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.Messaging.Events
+{
+    public class CommandExecutedEvent : IEvent
+    {
+        public Command Command { get; private set; }
+
+        public CommandExecutedEvent(Command trackedCommand)
+        {
+            Command = trackedCommand;
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/MessageAggregator.cs b/NzbDrone.Core/Messaging/Events/EventAggregator.cs
similarity index 50%
rename from NzbDrone.Common/Messaging/MessageAggregator.cs
rename to NzbDrone.Core/Messaging/Events/EventAggregator.cs
index 725f84ccf..7dbe1a331 100644
--- a/NzbDrone.Common/Messaging/MessageAggregator.cs
+++ b/NzbDrone.Core/Messaging/Events/EventAggregator.cs
@@ -1,25 +1,25 @@
-using System;
-using System.Diagnostics;
-using System.Linq;
+using System;
 using System.Threading.Tasks;
 using NLog;
+using NzbDrone.Common;
 using NzbDrone.Common.EnsureThat;
-using NzbDrone.Common.Serializer;
+using NzbDrone.Common.Messaging;
 using NzbDrone.Common.TPL;
 
-namespace NzbDrone.Common.Messaging
+namespace NzbDrone.Core.Messaging.Events
 {
-    public class MessageAggregator : IMessageAggregator
+    public class EventAggregator : IEventAggregator
     {
         private readonly Logger _logger;
         private readonly IServiceFactory _serviceFactory;
         private readonly TaskFactory _taskFactory;
 
-        public MessageAggregator(Logger logger, IServiceFactory serviceFactory)
+        public EventAggregator(Logger logger, IServiceFactory serviceFactory)
         {
+            var scheduler = new LimitedConcurrencyLevelTaskScheduler(3);
+
             _logger = logger;
             _serviceFactory = serviceFactory;
-            var scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
             _taskFactory = new TaskFactory(scheduler);
         }
 
@@ -36,9 +36,9 @@ namespace NzbDrone.Common.Messaging
             {
                 try
                 {
-                    _logger.Debug("{0} -> {1}", eventName, handler.GetType().Name);
+                    _logger.Trace("{0} -> {1}", eventName, handler.GetType().Name);
                     handler.Handle(@event);
-                    _logger.Debug("{0} <- {1}", eventName, handler.GetType().Name);
+                    _logger.Trace("{0} <- {1}", eventName, handler.GetType().Name);
                 }
                 catch (Exception e)
                 {
@@ -52,15 +52,14 @@ namespace NzbDrone.Common.Messaging
 
                 _taskFactory.StartNew(() =>
                 {
-                    _logger.Debug("{0} ~> {1}", eventName, handlerLocal.GetType().Name);
+                    _logger.Trace("{0} ~> {1}", eventName, handlerLocal.GetType().Name);
                     handlerLocal.HandleAsync(@event);
-                    _logger.Debug("{0} <~ {1}", eventName, handlerLocal.GetType().Name);
+                    _logger.Trace("{0} <~ {1}", eventName, handlerLocal.GetType().Name);
                 }, TaskCreationOptions.PreferFairness)
                 .LogExceptions();
             }
         }
 
-
         private static string GetEventName(Type eventType)
         {
             if (!eventType.IsGenericType)
@@ -70,48 +69,5 @@ namespace NzbDrone.Common.Messaging
 
             return string.Format("{0}<{1}>", eventType.Name.Remove(eventType.Name.IndexOf('`')), eventType.GetGenericArguments()[0].Name);
         }
-
-
-        public void PublishCommand<TCommand>(TCommand command) where TCommand : class, ICommand
-        {
-            Ensure.That(() => command).IsNotNull();
-
-            var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType());
-
-            _logger.Trace("Publishing {0}", command.GetType().Name);
-
-            var handler = (IExecute<TCommand>)_serviceFactory.Build(handlerContract);
-
-            _logger.Debug("{0} -> {1}", command.GetType().Name, handler.GetType().Name);
-
-            var sw = Stopwatch.StartNew();
-
-            try
-            {
-                handler.Execute(command);
-                sw.Stop();
-                PublishEvent(new CommandCompletedEvent(command));
-            }
-            catch (Exception e)
-            {
-                PublishEvent(new CommandFailedEvent(command, e));
-                throw;
-            }
-            finally
-            {
-                PublishEvent(new CommandExecutedEvent(command));
-            }
-
-            _logger.Debug("{0} <- {1} [{2}]", command.GetType().Name, handler.GetType().Name, sw.Elapsed.ToString(""));
-        }
-
-        public void PublishCommand(string commandTypeName)
-        {
-            var commandType = _serviceFactory.GetImplementations(typeof(ICommand))
-                .Single(c => c.FullName.Equals(commandTypeName, StringComparison.InvariantCultureIgnoreCase));
-
-            dynamic command = Json.Deserialize("{}", commandType);
-            PublishCommand(command);
-        }
     }
 }
diff --git a/NzbDrone.Core/Messaging/Events/IEventAggregator.cs b/NzbDrone.Core/Messaging/Events/IEventAggregator.cs
new file mode 100644
index 000000000..9df32d66d
--- /dev/null
+++ b/NzbDrone.Core/Messaging/Events/IEventAggregator.cs
@@ -0,0 +1,9 @@
+using NzbDrone.Common.Messaging;
+
+namespace NzbDrone.Core.Messaging.Events
+{
+    public interface IEventAggregator
+    {
+        void PublishEvent<TEvent>(TEvent @event) where TEvent : class,  IEvent;
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Common/Messaging/IHandle.cs b/NzbDrone.Core/Messaging/Events/IHandle.cs
similarity index 79%
rename from NzbDrone.Common/Messaging/IHandle.cs
rename to NzbDrone.Core/Messaging/Events/IHandle.cs
index ae40448d4..291e099d2 100644
--- a/NzbDrone.Common/Messaging/IHandle.cs
+++ b/NzbDrone.Core/Messaging/Events/IHandle.cs
@@ -1,4 +1,6 @@
-namespace NzbDrone.Common.Messaging
+using NzbDrone.Common.Messaging;
+
+namespace NzbDrone.Core.Messaging.Events
 {
     public interface IHandle<TEvent> : IProcessMessage<TEvent> where TEvent : IEvent
     {
diff --git a/NzbDrone.Common/Messaging/IProcessMessage.cs b/NzbDrone.Core/Messaging/IProcessMessage.cs
similarity index 86%
rename from NzbDrone.Common/Messaging/IProcessMessage.cs
rename to NzbDrone.Core/Messaging/IProcessMessage.cs
index b008d9e9b..2207e803d 100644
--- a/NzbDrone.Common/Messaging/IProcessMessage.cs
+++ b/NzbDrone.Core/Messaging/IProcessMessage.cs
@@ -1,10 +1,9 @@
-namespace NzbDrone.Common.Messaging
+namespace NzbDrone.Core.Messaging
 {
     public interface IProcessMessage { }
 
     public interface IProcessMessageAsync : IProcessMessage { }
 
-
     public interface IProcessMessage<TMessage> : IProcessMessage { }
 
     public interface IProcessMessageAsync<TMessage> : IProcessMessageAsync { }
diff --git a/NzbDrone.Core/MetadataSource/Trakt/TraktCommunicationException.cs b/NzbDrone.Core/MetadataSource/Trakt/TraktCommunicationException.cs
deleted file mode 100644
index d09dad66c..000000000
--- a/NzbDrone.Core/MetadataSource/Trakt/TraktCommunicationException.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace NzbDrone.Core.MetadataSource.Trakt
-{
-   public class TraktCommunicationException : Exception
-    {
-       public TraktCommunicationException(string message, Exception innerException) : base(message, innerException)
-       {
-       }
-    }
-}
diff --git a/NzbDrone.Core/MetadataSource/Trakt/TraktException.cs b/NzbDrone.Core/MetadataSource/Trakt/TraktException.cs
new file mode 100644
index 000000000..999122301
--- /dev/null
+++ b/NzbDrone.Core/MetadataSource/Trakt/TraktException.cs
@@ -0,0 +1,15 @@
+using System.Net;
+
+namespace NzbDrone.Core.MetadataSource.Trakt
+{
+    public class TraktException : NzbDroneClientException
+    {
+        public TraktException(string message) : base(HttpStatusCode.ServiceUnavailable, message)
+        {
+        }
+
+        public TraktException(string message, params object[] args) : base(HttpStatusCode.ServiceUnavailable, message, args)
+        {
+        }
+    }
+}
diff --git a/NzbDrone.Core/MetadataSource/TraktProxy.cs b/NzbDrone.Core/MetadataSource/TraktProxy.cs
index 2c18b5594..f37cc8d6b 100644
--- a/NzbDrone.Core/MetadataSource/TraktProxy.cs
+++ b/NzbDrone.Core/MetadataSource/TraktProxy.cs
@@ -2,7 +2,9 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Net;
 using System.Text.RegularExpressions;
+using NLog;
 using NzbDrone.Common;
 using NzbDrone.Core.MediaCover;
 using NzbDrone.Core.MetadataSource.Trakt;
@@ -15,26 +17,33 @@ namespace NzbDrone.Core.MetadataSource
 {
     public class TraktProxy : ISearchForNewSeries, IProvideSeriesInfo
     {
-        public List<Series> SearchForNewSeries(string title)
-        {
-            var client = BuildClient("search", "shows");
-            var restRequest = new RestRequest(GetSearchTerm(title));
-            var response = client.ExecuteAndValidate<List<SearchShow>>(restRequest);
-
-            return response.Select(MapSearchSeries).ToList();
-        }
-
-
+        private readonly Logger _logger;
         private static readonly Regex InvalidSearchCharRegex = new Regex(@"[^a-zA-Z0-9\s-\.]", RegexOptions.Compiled);
 
-        private static string GetSearchTerm(string phrase)
+        public TraktProxy(Logger logger)
         {
-            phrase = phrase.RemoveAccent().ToLower();
-            phrase = phrase.Replace("&", "and");
-            phrase = InvalidSearchCharRegex.Replace(phrase, string.Empty);
-            phrase = phrase.CleanSpaces().Replace(" ", "+");
+            _logger = logger;
+        }
 
-            return phrase;
+        public List<Series> SearchForNewSeries(string title)
+        {
+            try
+            {
+                var client = BuildClient("search", "shows");
+                var restRequest = new RestRequest(GetSearchTerm(title) +"/30/seasons");
+                var response = client.ExecuteAndValidate<List<Show>>(restRequest);
+
+                return response.Select(MapSeries).ToList();
+            }
+            catch (WebException ex)
+            {
+                throw new TraktException("Search for '{0}' failed. Unable to communicate with Trakt.", title);
+            }
+            catch (Exception ex)
+            {
+                _logger.WarnException(ex.Message, ex);
+                throw new TraktException("Search for '{0}' failed. Invalid response received from Trakt.", title);
+            }
         }
 
         public Tuple<Series, List<Episode>> GetSeriesInfo(int tvDbSeriesId)
@@ -43,7 +52,6 @@ namespace NzbDrone.Core.MetadataSource
             var restRequest = new RestRequest(tvDbSeriesId.ToString() + "/extended");
             var response = client.ExecuteAndValidate<Show>(restRequest);
 
-
             var episodes = response.seasons.SelectMany(c => c.episodes).Select(MapEpisode).ToList();
             var series = MapSeries(response);
 
@@ -63,6 +71,7 @@ namespace NzbDrone.Core.MetadataSource
             series.ImdbId = show.imdb_id;
             series.Title = show.title;
             series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title);
+            series.Year = show.year;
             series.FirstAired = FromIso(show.first_aired_iso);
             series.Overview = show.overview;
             series.Runtime = show.runtime;
@@ -71,6 +80,10 @@ namespace NzbDrone.Core.MetadataSource
             series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", "");
             series.Status = GetSeriesStatus(show.status);
 
+            series.Seasons = show.seasons.Select(s => new Tv.Season
+            {
+                SeasonNumber = s.season
+            }).OrderByDescending(s => s.SeasonNumber).ToList();
             series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.images.banner });
             series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Poster, Url = GetPosterThumbnailUrl(show.images.poster) });
             series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Fanart, Url = show.images.fanart });
@@ -141,6 +154,12 @@ namespace NzbDrone.Core.MetadataSource
         {
             DateTime result;
 
+            //Todo: Remove this when DST ends and/or trakt fixes DST airings in EST/EDT
+            if (iso != null && iso.EndsWith("-05:00"))
+            {
+                iso = iso.Replace("-05:00", "-04:00");
+            }
+
             if (!DateTime.TryParse(iso, out result))
                 return null;
 
@@ -158,6 +177,14 @@ namespace NzbDrone.Core.MetadataSource
             return match.Captures[0].Value;
         }
 
+        private static string GetSearchTerm(string phrase)
+        {
+            phrase = phrase.RemoveAccent().ToLower();
+            phrase = phrase.Replace("&", "and");
+            phrase = InvalidSearchCharRegex.Replace(phrase, string.Empty);
+            phrase = phrase.CleanSpaces().Replace(" ", "+");
 
+            return phrase;
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/NzbDrone.Core/Notifications/Email/EmailService.cs b/NzbDrone.Core/Notifications/Email/EmailService.cs
index 38c37086d..5625a9785 100644
--- a/NzbDrone.Core/Notifications/Email/EmailService.cs
+++ b/NzbDrone.Core/Notifications/Email/EmailService.cs
@@ -2,7 +2,8 @@
 using System.Net;
 using System.Net.Mail;
 using NLog;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using Omu.ValueInjecter;
 
 namespace NzbDrone.Core.Notifications.Email
diff --git a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs
index 258884788..3d9ec3797 100644
--- a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs
+++ b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs
@@ -1,9 +1,17 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Notifications.Email
 {
-    public class TestEmailCommand : ICommand
+    public class TestEmailCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         public string Server { get; set; }
         public int Port { get; set; }
         public bool Ssl { get; set; }
diff --git a/NzbDrone.Core/Notifications/Growl/GrowlService.cs b/NzbDrone.Core/Notifications/Growl/GrowlService.cs
index deed4b369..5ec00df7c 100644
--- a/NzbDrone.Core/Notifications/Growl/GrowlService.cs
+++ b/NzbDrone.Core/Notifications/Growl/GrowlService.cs
@@ -3,7 +3,9 @@ using System.Collections.Generic;
 using System.Linq;
 using Growl.Connector;
 using NLog;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using GrowlNotification = Growl.Connector.Notification;
 
 namespace NzbDrone.Core.Notifications.Growl
@@ -15,7 +17,7 @@ namespace NzbDrone.Core.Notifications.Growl
 
     public class GrowlService : IGrowlService, IExecute<TestGrowlCommand>
     {
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
 
         private readonly Application _growlApplication = new Application("NzbDrone");
         private GrowlConnector _growlConnector;
@@ -24,7 +26,7 @@ namespace NzbDrone.Core.Notifications.Growl
         public GrowlService()
         {
             _notificationTypes = GetNotificationTypes();
-            _growlApplication.Icon = "https://github.com/NzbDrone/NzbDrone/raw/master/NzbDrone.Core/NzbDrone.jpg";
+            _growlApplication.Icon = "https://raw.github.com/NzbDrone/NzbDrone/master/Logo/64.png";
         }
 
         public void SendNotification(string title, string message, string notificationTypeName, string hostname, int port, string password)
diff --git a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs
index 35890fff9..c1d880541 100644
--- a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs
+++ b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs
@@ -1,9 +1,16 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Notifications.Growl
 {
-    public class TestGrowlCommand : ICommand
+    public class TestGrowlCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
         public string Host { get; set; }
         public int Port { get; set; }
         public string Password { get; set; }
diff --git a/NzbDrone.Core/Notifications/NotificationRepository.cs b/NzbDrone.Core/Notifications/NotificationRepository.cs
index f9f9feb0d..79dedcf34 100644
--- a/NzbDrone.Core/Notifications/NotificationRepository.cs
+++ b/NzbDrone.Core/Notifications/NotificationRepository.cs
@@ -1,7 +1,9 @@
 using System;
 using System.Linq;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Notifications
 {
@@ -13,8 +15,8 @@ namespace NzbDrone.Core.Notifications
 
     public class NotificationRepository : BasicRepository<NotificationDefinition>, INotificationRepository
     {
-        public NotificationRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public NotificationRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/Notifications/NotificationService.cs b/NzbDrone.Core/Notifications/NotificationService.cs
index d48398898..366129433 100644
--- a/NzbDrone.Core/Notifications/NotificationService.cs
+++ b/NzbDrone.Core/Notifications/NotificationService.cs
@@ -3,10 +3,11 @@ using System.Collections.Generic;
 using System.Linq;
 using NLog;
 using NzbDrone.Common.Composition;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Common.Serializer;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv;
 using Omu.ValueInjecter;
 
diff --git a/NzbDrone.Core/Notifications/Plex/PlexService.cs b/NzbDrone.Core/Notifications/Plex/PlexService.cs
index 15e01420c..5a5bc82e3 100644
--- a/NzbDrone.Core/Notifications/Plex/PlexService.cs
+++ b/NzbDrone.Core/Notifications/Plex/PlexService.cs
@@ -4,7 +4,8 @@ using System.Linq;
 using System.Xml.Linq;
 using NLog;
 using NzbDrone.Common;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Notifications.Plex
 {
diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs
index 6df162ab4..6de213789 100644
--- a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs
+++ b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs
@@ -1,9 +1,16 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Notifications.Plex
 {
-    public class TestPlexClientCommand : ICommand
+    public class TestPlexClientCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
         public string Host { get; set; }
         public int Port { get; set; }
         public string Username { get; set; }
diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs
index 49089afea..19fac0641 100644
--- a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs
+++ b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs
@@ -1,9 +1,17 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Notifications.Plex
 {
-    public class TestPlexServerCommand : ICommand
+    public class TestPlexServerCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         public string Host { get; set; }
         public int Port { get; set; }
     }
diff --git a/NzbDrone.Core/Notifications/Prowl/ProwlService.cs b/NzbDrone.Core/Notifications/Prowl/ProwlService.cs
index bf850215f..e8c080f75 100644
--- a/NzbDrone.Core/Notifications/Prowl/ProwlService.cs
+++ b/NzbDrone.Core/Notifications/Prowl/ProwlService.cs
@@ -1,6 +1,7 @@
 using System;
 using NLog;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using Prowlin;
 
 namespace NzbDrone.Core.Notifications.Prowl
diff --git a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs
index e58bf5a9c..50d02677e 100644
--- a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs
+++ b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs
@@ -1,9 +1,16 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Notifications.Prowl
 {
-    public class TestProwlCommand : ICommand
+    public class TestProwlCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
         public string ApiKey { get; set; }
         public int Priority { get; set; }
     }
diff --git a/NzbDrone.Core/Notifications/Pushover/PushoverService.cs b/NzbDrone.Core/Notifications/Pushover/PushoverService.cs
index cab9639bb..7e5725711 100644
--- a/NzbDrone.Core/Notifications/Pushover/PushoverService.cs
+++ b/NzbDrone.Core/Notifications/Pushover/PushoverService.cs
@@ -1,4 +1,5 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using RestSharp;
 using NzbDrone.Core.Rest;
 
diff --git a/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs b/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs
index 31bc034f9..13e232401 100644
--- a/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs
+++ b/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs
@@ -1,9 +1,17 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Notifications.Pushover
 {
-    public class TestPushoverCommand : ICommand
+    public class TestPushoverCommand : Command
     {
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
         public string UserKey { get; set; }
         public int Priority { get; set; }
     }
diff --git a/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs b/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs
index af5fe2366..1d7cb5377 100644
--- a/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs
+++ b/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
             var parameters = new JObject(
                                         new JProperty("title", title),
                                         new JProperty("message", message),
-                                        new JProperty("image", "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png"),
+                                        new JProperty("image", "https://raw.github.com/NzbDrone/NzbDrone/master/Logo/64.png"),
                                         new JProperty("displaytime", settings.DisplayTime * 1000));
 
             var postJson = BuildJsonRequest("GUI.ShowNotification", parameters);
diff --git a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs
index 56eb75ee8..cb8acb361 100644
--- a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs
+++ b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs
@@ -1,9 +1,17 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Notifications.Xbmc
 {
-    public class TestXbmcCommand : ICommand
+    public class TestXbmcCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         public string Host { get; set; }
         public int Port { get; set; }
         public string Username { get; set; }
diff --git a/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs b/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs
index cc0f9bf3c..bbc8a237a 100644
--- a/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs
+++ b/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs
@@ -5,7 +5,9 @@ using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 using NLog;
 using NzbDrone.Common;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.Tv;
 using NzbDrone.Core.Model.Xbmc;
 
@@ -21,7 +23,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
 
     public class XbmcService : IXbmcService, IExecute<TestXbmcCommand>
     {
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
         private readonly IHttpProvider _httpProvider;
         private readonly IEnumerable<IApiProvider> _apiProviders;
 
diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj
index 793db0f0a..dae681ba9 100644
--- a/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/NzbDrone.Core/NzbDrone.Core.csproj
@@ -123,6 +123,7 @@
     <Compile Include="Configuration\Events\ConfigFileSavedEvent.cs" />
     <Compile Include="Configuration\Events\ConfigSavedEvent.cs" />
     <Compile Include="Configuration\IConfigService.cs" />
+    <Compile Include="Configuration\InvalidConfigFileException.cs" />
     <Compile Include="DataAugmentation\DailySeries\DailySeriesDataProxy.cs" />
     <Compile Include="DataAugmentation\DailySeries\DailySeriesService.cs" />
     <Compile Include="DataAugmentation\Scene\SceneMapping.cs" />
@@ -130,7 +131,6 @@
     <Compile Include="DataAugmentation\Scene\SceneMappingProxy.cs" />
     <Compile Include="DataAugmentation\Scene\SceneMappingRepository.cs" />
     <Compile Include="DataAugmentation\Scene\UpdateSceneMappingCommand.cs" />
-    <Compile Include="Datastore\CachedBasicRepository.cs" />
     <Compile Include="Datastore\ConnectionStringFactory.cs" />
     <Compile Include="Datastore\Converters\BooleanIntConverter.cs" />
     <Compile Include="Datastore\Converters\QualityIntConverter.cs" />
@@ -160,6 +160,10 @@
     <Compile Include="Datastore\Migration\015_add_air_date_as_string.cs" />
     <Compile Include="Datastore\Migration\016_updated_imported_history_item.cs" />
     <Compile Include="Datastore\Migration\017_reset_scene_names.cs" />
+    <Compile Include="Datastore\Migration\018_remove_duplicates.cs" />
+    <Compile Include="Datastore\Migration\019_restore_unique_constraints.cs" />
+    <Compile Include="Datastore\Migration\020_add_year_and_seasons_to_series.cs" />
+    <Compile Include="Datastore\Migration\021_drop_seasons_table.cs" />
     <Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
     <Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
     <Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
@@ -169,6 +173,8 @@
     <Compile Include="Datastore\Migration\Framework\NzbDroneMigrationBase.cs" />
     <Compile Include="Datastore\MigrationType.cs" />
     <Compile Include="Datastore\Migration\Framework\SQLiteAlter.cs" />
+    <Compile Include="Datastore\Migration\Framework\SQLiteColumn.cs" />
+    <Compile Include="Datastore\Migration\Framework\SQLiteIndex.cs" />
     <Compile Include="Datastore\Migration\Framework\SQLiteMigrationHelper.cs" />
     <Compile Include="Datastore\ModelBase.cs" />
     <Compile Include="Datastore\BasicRepository.cs" />
@@ -177,10 +183,13 @@
     <Compile Include="Datastore\PagingSpecExtensions.cs" />
     <Compile Include="Datastore\RelationshipExtensions.cs" />
     <Compile Include="Datastore\TableMapping.cs" />
-    <Compile Include="DecisionEngine\DownloadDecision.cs" />
+    <Compile Include="DecisionEngine\Specifications\DownloadDecision.cs" />
     <Compile Include="DecisionEngine\IRejectWithReason.cs" />
     <Compile Include="DecisionEngine\IDecisionEngineSpecification.cs" />
+    <Compile Include="DecisionEngine\Specifications\CutoffSpecification.cs" />
     <Compile Include="DecisionEngine\Specifications\NotRestrictedReleaseSpecification.cs" />
+    <Compile Include="DecisionEngine\Specifications\NotSampleSpecification.cs" />
+    <Compile Include="DecisionEngine\Specifications\RssSync\ProperSpecification.cs" />
     <Compile Include="DecisionEngine\Specifications\Search\SeasonMatchSpecification.cs" />
     <Compile Include="DecisionEngine\Specifications\Search\DailyEpisodeMatchSpecification.cs" />
     <Compile Include="DecisionEngine\Specifications\AcceptableSizeSpecification.cs" />
@@ -207,10 +216,14 @@
     <Compile Include="IndexerSearch\EpisodeSearchCommand.cs" />
     <Compile Include="IndexerSearch\SeasonSearchCommand.cs" />
     <Compile Include="IndexerSearch\SeasonSearchService.cs" />
+    <Compile Include="Indexers\BasicTorrentRssParser.cs" />
+    <Compile Include="Indexers\DownloadProtocols.cs" />
+    <Compile Include="Indexers\Eztv\Eztv.cs" />
     <Compile Include="Indexers\FetchAndParseRssService.cs" />
     <Compile Include="Indexers\IIndexer.cs" />
     <Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" />
     <Compile Include="Indexers\IndexerWithSetting.cs" />
+    <Compile Include="Indexers\IParseFeed.cs" />
     <Compile Include="Indexers\Newznab\SizeParsingException.cs" />
     <Compile Include="Indexers\NullSetting.cs" />
     <Compile Include="Indexers\RssSyncCommand.cs" />
@@ -219,6 +232,32 @@
     <Compile Include="Instrumentation\Commands\DeleteLogFilesCommand.cs" />
     <Compile Include="Instrumentation\Commands\TrimLogCommand.cs" />
     <Compile Include="Instrumentation\DeleteLogFilesService.cs" />
+    <Compile Include="MediaFiles\MediaFileExtensions.cs" />
+    <Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" />
+    <Compile Include="Messaging\Commands\CommandExecutor.cs" />
+    <Compile Include="Messaging\Commands\ICommandExecutor.cs" />
+    <Compile Include="Messaging\Commands\IExecute.cs" />
+    <Compile Include="Messaging\Commands\Tracking\CommandStatus.cs" />
+    <Compile Include="Messaging\Commands\Tracking\CommandTrackingService.cs" />
+    <Compile Include="Messaging\Commands\Tracking\ExistingCommand.cs" />
+    <Compile Include="Messaging\Commands\Tracking\TrackedCommandCleanupCommand.cs" />
+    <Compile Include="Messaging\Events\EventAggregator.cs" />
+    <Compile Include="Messaging\Events\IEventAggregator.cs" />
+    <Compile Include="Messaging\Events\IHandle.cs" />
+    <Compile Include="MetadataSource\Trakt\TraktException.cs" />
+    <Compile Include="NzbDroneClientException.cs" />
+    <Compile Include="Instrumentation\LoggerExtensions.cs" />
+    <Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
+    <Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
+    <Compile Include="Messaging\Commands\Command.cs" />
+    <Compile Include="Messaging\Commands\CommandEqualityComparer.cs" />
+    <Compile Include="Messaging\Commands\TestCommand.cs" />
+    <Compile Include="Messaging\Commands\TestCommandExecutor.cs" />
+    <Compile Include="Messaging\Events\CommandCreatedEvent.cs" />
+    <Compile Include="Messaging\Events\CommandExecutedEvent.cs" />
+    <Compile Include="Messaging\IProcessMessage.cs" />
+    <Compile Include="ProgressMessaging\CommandUpdatedEvent.cs" />
+    <Compile Include="ProgressMessaging\ProgressMessageTarget.cs" />
     <Compile Include="Instrumentation\SetLoggingLevel.cs" />
     <Compile Include="Jobs\TaskManager.cs" />
     <Compile Include="Lifecycle\ApplicationShutdownRequested.cs" />
@@ -244,7 +283,6 @@
     <Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
     <Compile Include="MediaFiles\SameFilenameException.cs" />
     <Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
-    <Compile Include="MetadataSource\Trakt\TraktCommunicationException.cs" />
     <Compile Include="Notifications\Email\TestEmailCommand.cs" />
     <Compile Include="Notifications\Growl\GrowlSettings.cs" />
     <Compile Include="Notifications\Growl\TestGrowlCommand.cs" />
@@ -262,7 +300,7 @@
     <Compile Include="IndexerSearch\Definitions\SingleEpisodeSearchCriteria.cs" />
     <Compile Include="IndexerSearch\NzbSearchService.cs" />
     <Compile Include="IndexerSearch\SearchAndDownloadService.cs" />
-    <Compile Include="Indexers\BasicRssParser.cs" />
+    <Compile Include="Indexers\RssParserBase.cs" />
     <Compile Include="Indexers\RssSyncService.cs" />
     <Compile Include="Indexers\IndexerBase.cs" />
     <Compile Include="Indexers\IndexerDefinition.cs" />
@@ -271,8 +309,6 @@
     <Compile Include="Indexers\Newznab\Newznab.cs" />
     <Compile Include="Indexers\Newznab\NewznabSettings.cs" />
     <Compile Include="Indexers\Newznab\NewznabParser.cs" />
-    <Compile Include="Indexers\NzbClub\NzbClub.cs" />
-    <Compile Include="Indexers\NzbClub\NzbClubParser.cs" />
     <Compile Include="Indexers\Omgwtfnzbs\Omgwtfnzbs.cs" />
     <Compile Include="Indexers\Omgwtfnzbs\OmgwtfnzbsParser.cs" />
     <Compile Include="Indexers\IIndexerSetting.cs" />
@@ -341,7 +377,8 @@
     <Compile Include="Parser\Model\LocalEpisode.cs" />
     <Compile Include="Parser\Model\ParsedEpisodeInfo.cs" />
     <Compile Include="Parser\Model\RemoteEpisode.cs" />
-    <Compile Include="Parser\Model\ReportInfo.cs" />
+    <Compile Include="Parser\Model\ReleaseInfo.cs" />
+    <Compile Include="Parser\Model\TorrentInfo.cs" />
     <Compile Include="Parser\Parser.cs" />
     <Compile Include="Parser\ParsingService.cs" />
     <Compile Include="Parser\QualityParser.cs" />
@@ -385,10 +422,8 @@
     <Compile Include="Model\Xem\XemValues.cs" />
     <Compile Include="MediaCover\MediaCoverService.cs" />
     <Compile Include="Download\Clients\Nzbget\NzbgetClient.cs" />
-    <Compile Include="Providers\VideoFileInfoReader.cs" />
     <Compile Include="Download\Clients\PneumaticClient.cs" />
     <Compile Include="MediaFiles\RecycleBinProvider.cs" />
-    <Compile Include="Tv\SeasonService.cs" />
     <Compile Include="SeriesStats\SeriesStatistics.cs" />
     <Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
     <Compile Include="Tv\SeriesTypes.cs" />
@@ -533,9 +568,6 @@
   <ItemGroup>
     <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
   </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="NzbDrone.jpg" />
-  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
       <Project>{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}</Project>
diff --git a/NzbDrone.Core/NzbDrone.Core.ncrunchproject b/NzbDrone.Core/NzbDrone.Core.ncrunchproject
index 73e1ebbb9..b2eed192e 100644
--- a/NzbDrone.Core/NzbDrone.Core.ncrunchproject
+++ b/NzbDrone.Core/NzbDrone.Core.ncrunchproject
@@ -1,8 +1,8 @@
 <ProjectConfiguration>
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
-  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,10 +12,12 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration></UseBuildConfiguration>
-  <UseBuildPlatform />
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
   <ProxyProcessPath></ProxyProcessPath>
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
   <HiddenWarnings>PostBuildEventDisabled</HiddenWarnings>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.Core/NzbDrone.jpg b/NzbDrone.Core/NzbDrone.jpg
deleted file mode 100644
index 4e544b00f..000000000
Binary files a/NzbDrone.Core/NzbDrone.jpg and /dev/null differ
diff --git a/NzbDrone.Core/NzbDroneClientException.cs b/NzbDrone.Core/NzbDroneClientException.cs
new file mode 100644
index 000000000..cc94caffc
--- /dev/null
+++ b/NzbDrone.Core/NzbDroneClientException.cs
@@ -0,0 +1,21 @@
+using System.Net;
+using NzbDrone.Common.Exceptions;
+
+namespace NzbDrone.Core
+{
+    public class NzbDroneClientException : NzbDroneException
+    {
+        public HttpStatusCode StatusCode { get; private set; }
+
+        public NzbDroneClientException(HttpStatusCode statusCode, string message, params object[] args) : base(message, args)
+        {
+            StatusCode = statusCode;
+        }
+
+        public NzbDroneClientException(HttpStatusCode statusCode, string message)
+            : base(message)
+        {
+            StatusCode = statusCode;
+        }
+    }
+}
diff --git a/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/NzbDrone.Core/Parser/Model/LocalEpisode.cs
index 3936ca4b7..06f620a96 100644
--- a/NzbDrone.Core/Parser/Model/LocalEpisode.cs
+++ b/NzbDrone.Core/Parser/Model/LocalEpisode.cs
@@ -7,12 +7,13 @@ namespace NzbDrone.Core.Parser.Model
 {
     public class LocalEpisode
     {
-        public string Path { get; set; }
+        public String Path { get; set; }
         public Int64 Size { get; set; }
         public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
         public Series Series { get; set; }
         public List<Episode> Episodes { get; set; }
         public QualityModel Quality { get; set; }
+        public Boolean ExistingFile { get; set; }
         
         public int SeasonNumber 
         { 
diff --git a/NzbDrone.Core/Parser/Model/ReleaseInfo.cs b/NzbDrone.Core/Parser/Model/ReleaseInfo.cs
new file mode 100644
index 000000000..e5b3d9ed8
--- /dev/null
+++ b/NzbDrone.Core/Parser/Model/ReleaseInfo.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace NzbDrone.Core.Parser.Model
+{
+    public class ReleaseInfo
+    {
+        public string Title { get; set; }
+        public long Size { get; set; }
+        public string DownloadUrl { get; set; }
+        public string InfoUrl { get; set; }
+        public string CommentUrl { get; set; }
+        public String Indexer { get; set; }
+
+        public DateTime PublishDate { get; set; }
+
+        public int Age
+        {
+            get
+            {
+                return DateTime.UtcNow.Subtract(PublishDate).Days;
+            }
+        }
+
+        public string ReleaseGroup { get; set; }
+        public int TvRageId { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Parser/Model/RemoteEpisode.cs b/NzbDrone.Core/Parser/Model/RemoteEpisode.cs
index 70b001511..ac394d0d8 100644
--- a/NzbDrone.Core/Parser/Model/RemoteEpisode.cs
+++ b/NzbDrone.Core/Parser/Model/RemoteEpisode.cs
@@ -7,7 +7,7 @@ namespace NzbDrone.Core.Parser.Model
 {
     public class RemoteEpisode
     {
-        public ReportInfo Report { get; set; }
+        public ReleaseInfo Release { get; set; }
 
         public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
 
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Parser.Model
 
         public override string ToString()
         {
-            return Report.Title;
+            return Release.Title;
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Parser/Model/ReportInfo.cs b/NzbDrone.Core/Parser/Model/ReportInfo.cs
deleted file mode 100644
index d357d7d66..000000000
--- a/NzbDrone.Core/Parser/Model/ReportInfo.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-
-namespace NzbDrone.Core.Parser.Model
-{
-    public class ReportInfo
-    {
-        public string Title { get; set; }
-        public long Size { get; set; }
-        public string NzbUrl { get; set; }
-        public string NzbInfoUrl { get; set; }
-        public String Indexer { get; set; }
-        public int Age { get; set; }
-        public string ReleaseGroup { get; set; }
-        public int TvRageId { get; set; }
-    }
-}
\ No newline at end of file
diff --git a/NzbDrone.Core/Parser/Model/TorrentInfo.cs b/NzbDrone.Core/Parser/Model/TorrentInfo.cs
new file mode 100644
index 000000000..434967b2b
--- /dev/null
+++ b/NzbDrone.Core/Parser/Model/TorrentInfo.cs
@@ -0,0 +1,8 @@
+namespace NzbDrone.Core.Parser.Model
+{
+    public class TorrentInfo : ReleaseInfo
+    {
+        public string MagnetUrl { get; set; }
+        public string InfoHash { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Parser/Parser.cs b/NzbDrone.Core/Parser/Parser.cs
index 3178b5084..85ef1be7c 100644
--- a/NzbDrone.Core/Parser/Parser.cs
+++ b/NzbDrone.Core/Parser/Parser.cs
@@ -4,16 +4,14 @@ using System.IO;
 using System.Linq;
 using System.Text.RegularExpressions;
 using NLog;
-using NzbDrone.Common;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Parser.Model;
-using NzbDrone.Core.Qualities;
-using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Parser
 {
     public static class Parser
     {
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
 
         private static readonly Regex[] ReportTitleRegex = new[]
             {
diff --git a/NzbDrone.Core/Parser/ParsingService.cs b/NzbDrone.Core/Parser/ParsingService.cs
index 44113b8a4..b678c5eeb 100644
--- a/NzbDrone.Core/Parser/ParsingService.cs
+++ b/NzbDrone.Core/Parser/ParsingService.cs
@@ -1,6 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Linq;
 using NLog;
+using NzbDrone.Common;
+using NzbDrone.Core.DataAugmentation.Scene;
+using NzbDrone.Core.IndexerSearch.Definitions;
 using NzbDrone.Core.Parser.Model;
 using NzbDrone.Core.Tv;
 
@@ -8,30 +12,33 @@ namespace NzbDrone.Core.Parser
 {
     public interface IParsingService
     {
-        LocalEpisode GetEpisodes(string filename, Series series);
         LocalEpisode GetEpisodes(string filename, Series series, bool sceneSource);
         Series GetSeries(string title);
-        RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId);
+        RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null);
+        List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
     }
 
     public class ParsingService : IParsingService
     {
         private readonly IEpisodeService _episodeService;
         private readonly ISeriesService _seriesService;
+        private readonly IDiskProvider _diskProvider;
+        private readonly ISceneMappingService _sceneMappingService;
         private readonly Logger _logger;
 
-        public ParsingService(IEpisodeService episodeService, ISeriesService seriesService, Logger logger)
+        public ParsingService(IEpisodeService episodeService,
+                              ISeriesService seriesService,
+                              IDiskProvider diskProvider,
+                              ISceneMappingService sceneMappingService,
+                              Logger logger)
         {
             _episodeService = episodeService;
             _seriesService = seriesService;
+            _diskProvider = diskProvider;
+            _sceneMappingService = sceneMappingService;
             _logger = logger;
         }
 
-        public LocalEpisode GetEpisodes(string filename, Series series)
-        {
-            return GetEpisodes(filename, series, false);
-        }
-
         public LocalEpisode GetEpisodes(string filename, Series series, bool sceneSource)
         {
             var parsedEpisodeInfo = Parser.ParsePath(filename);
@@ -54,14 +61,14 @@ namespace NzbDrone.Core.Parser
                 Quality = parsedEpisodeInfo.Quality,
                 Episodes = episodes,
                 Path = filename,
-                ParsedEpisodeInfo = parsedEpisodeInfo
+                ParsedEpisodeInfo = parsedEpisodeInfo,
+                ExistingFile = _diskProvider.IsParent(series.Path, filename)
             };
         }
 
         public Series GetSeries(string title)
         {
             var searchTitle = title;
-
             var parsedEpisodeInfo = Parser.ParseTitle(title);
 
             if (parsedEpisodeInfo != null)
@@ -72,34 +79,28 @@ namespace NzbDrone.Core.Parser
             return _seriesService.FindByTitle(searchTitle);
         }
 
-        public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId)
+        public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null)
         {
             var remoteEpisode = new RemoteEpisode
                 {
                     ParsedEpisodeInfo = parsedEpisodeInfo,
                 };
 
-            var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
-
-            if (series == null && tvRageId > 0)
-            {
-                series = _seriesService.FindByTvRageId(tvRageId);
-                //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import
-            }
+            var series = searchCriteria == null ? GetSeries(parsedEpisodeInfo, tvRageId) :
+                                                  GetSeries(parsedEpisodeInfo, tvRageId, searchCriteria);
 
             if (series == null)
             {
-                _logger.Trace("No matching series {0}", parsedEpisodeInfo.SeriesTitle);
                 return remoteEpisode;
             }
 
             remoteEpisode.Series = series;
-            remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, true);
+            remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, true, searchCriteria);
 
             return remoteEpisode;
         }
 
-        private List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource)
+        public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null)
         {
             var result = new List<Episode>();
 
@@ -108,10 +109,10 @@ namespace NzbDrone.Core.Parser
                 if (series.SeriesType == SeriesTypes.Standard)
                 {
                     _logger.Warn("Found daily-style episode for non-daily series: {0}.", series);
-                    return new List<Episode>();
+                    return null;
                 }
-                
-                var episodeInfo = _episodeService.GetEpisode(series.Id, parsedEpisodeInfo.AirDate.Value);
+
+                var episodeInfo = GetDailyEpisode(series, parsedEpisodeInfo.AirDate.Value, searchCriteria);
 
                 if (episodeInfo != null)
                 {
@@ -130,7 +131,16 @@ namespace NzbDrone.Core.Parser
 
                 if (series.UseSceneNumbering && sceneSource)
                 {
-                    episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber, true);
+                    if (searchCriteria != null)
+                    {
+                        episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SceneSeasonNumber == parsedEpisodeInfo.SeasonNumber &&
+                                                                          e.SceneEpisodeNumber == episodeNumber);
+                    }
+
+                    if (episodeInfo == null)
+                    {
+                        episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber, true);
+                    }
 
                     if (episodeInfo != null)
                     {
@@ -143,15 +153,22 @@ namespace NzbDrone.Core.Parser
                     }
                 }
 
+                if (episodeInfo == null && searchCriteria != null)
+                {
+                    episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == parsedEpisodeInfo.SeasonNumber &&
+                                                                          e.EpisodeNumber == episodeNumber);
+                }
+
                 if (episodeInfo == null)
                 {
-                    episodeInfo = _episodeService.GetEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber);
+                    episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber);
                 }
 
                 if (episodeInfo != null)
                 {
                     result.Add(episodeInfo);
                 }
+
                 else
                 {
                     _logger.Debug("Unable to find {0}", parsedEpisodeInfo);
@@ -160,5 +177,68 @@ namespace NzbDrone.Core.Parser
 
             return result;
         }
+
+        private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria)
+        {           
+            var tvdbId = _sceneMappingService.GetTvDbId(parsedEpisodeInfo.SeriesTitle);
+
+            if (tvdbId.HasValue)
+            {
+                if (searchCriteria.Series.TvdbId == tvdbId)
+                {
+                    return searchCriteria.Series;
+                }
+            }
+
+            if (parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle() == searchCriteria.Series.CleanTitle)
+            {
+                return searchCriteria.Series;
+            }
+
+            if (tvRageId == searchCriteria.Series.TvRageId)
+            {
+                //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import
+                return searchCriteria.Series;
+            }
+
+            return GetSeries(parsedEpisodeInfo, tvRageId);
+        }
+
+        private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId)
+        {
+            var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
+
+            if (series == null && tvRageId > 0)
+            {
+                //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import
+                series = _seriesService.FindByTvRageId(tvRageId);
+            }
+
+            if (series == null)
+            {
+                _logger.Trace("No matching series {0}", parsedEpisodeInfo.SeriesTitle);
+                return null;
+            }
+
+            return series;
+        }
+
+        private Episode GetDailyEpisode(Series series, DateTime airDate, SearchCriteriaBase searchCriteria)
+        {
+            Episode episodeInfo = null;
+
+            if (searchCriteria != null)
+            {
+                episodeInfo = searchCriteria.Episodes.SingleOrDefault(
+                    e => e.AirDate == airDate.ToString(Episode.AIR_DATE_FORMAT));
+            }
+
+            if (episodeInfo == null)
+            {
+                episodeInfo = _episodeService.FindEpisode(series.Id, airDate);
+            }
+
+            return episodeInfo;
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Parser/QualityParser.cs b/NzbDrone.Core/Parser/QualityParser.cs
index 8cd3077e2..bbfd7ed3b 100644
--- a/NzbDrone.Core/Parser/QualityParser.cs
+++ b/NzbDrone.Core/Parser/QualityParser.cs
@@ -1,11 +1,9 @@
 using System;
-using System.Collections.Generic;
 using System.IO;
-using System.Linq;
-using System.Text;
 using System.Text.RegularExpressions;
 using NLog;
 using NzbDrone.Common;
+using NzbDrone.Core.MediaFiles;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
@@ -152,33 +150,7 @@ namespace NzbDrone.Core.Parser
             {
                 try
                 {
-                    switch (Path.GetExtension(name).ToLower())
-                    {
-                        case ".avi":
-                        case ".xvid":
-                        case ".divx":
-                        case ".wmv":
-                        case ".mp4":
-                        case ".mpg":
-                        case ".mpeg":
-                        case ".mov":
-                        case ".rm":
-                        case ".rmvb":
-                        case ".flv":
-                        case ".dvr-ms":
-                        case ".ogm":
-                        case ".strm":
-                            {
-                                result.Quality = Quality.SDTV;
-                                break;
-                            }
-                        case ".mkv":
-                        case ".ts":
-                            {
-                                result.Quality = Quality.HDTV720p;
-                                break;
-                            }
-                    }
+                    result.Quality = MediaFileExtensions.GetQualityForExtension(Path.GetExtension(name));
                 }
                 catch (ArgumentException)
                 {
diff --git a/NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs b/NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs
new file mode 100644
index 000000000..49c20b0b2
--- /dev/null
+++ b/NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs
@@ -0,0 +1,68 @@
+using NLog.Config;
+using NLog;
+using NLog.Targets;
+using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Commands.Tracking;
+using NzbDrone.Core.Messaging.Events;
+
+namespace NzbDrone.Core.ProgressMessaging
+{
+
+    public class ProgressMessageTarget : Target, IHandle<ApplicationStartedEvent>
+    {
+        private readonly IEventAggregator _eventAggregator;
+        private readonly ITrackCommands _trackCommands;
+        private static LoggingRule _rule;
+
+        public ProgressMessageTarget(IEventAggregator eventAggregator, ITrackCommands trackCommands)
+        {
+            _eventAggregator = eventAggregator;
+            _trackCommands = trackCommands;
+        }
+
+        protected override void Write(LogEventInfo logEvent)
+        {
+            var command = GetCurrentCommand();
+
+            if (IsClientMessage(logEvent, command))
+            {
+                command.SetMessage(logEvent.FormattedMessage);
+                _eventAggregator.PublishEvent(new CommandUpdatedEvent(command));
+            }
+        }
+
+
+        private Command GetCurrentCommand()
+        {
+            var commandId = MappedDiagnosticsContext.Get("CommandId");
+
+            if (string.IsNullOrWhiteSpace(commandId))
+            {
+                return null;
+            }
+
+            return _trackCommands.GetById(commandId);
+        }
+
+        private bool IsClientMessage(LogEventInfo logEvent, Command command)
+        {
+            if (command == null || !command.SendUpdatesToClient)
+            {
+                return false;
+            }
+
+            return logEvent.Properties.ContainsKey("Status");
+        }
+
+        public void Handle(ApplicationStartedEvent message)
+        {
+            _rule = new LoggingRule("*", LogLevel.Trace, this);
+
+            LogManager.Configuration.AddTarget("ProgressMessagingLogger", this);
+            LogManager.Configuration.LoggingRules.Add(_rule);
+            LogManager.ReconfigExistingLoggers();
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs b/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs
index da4b4b53f..a30c0c70d 100644
--- a/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs
+++ b/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs
@@ -1,10 +1,10 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Providers
 {
-    public class UpdateXemMappingsCommand : ICommand
+    public class UpdateXemMappingsCommand : Command
     {
-        public int? SeriesId { get; private set; }
+        public int? SeriesId { get; set; }
 
         public UpdateXemMappingsCommand(int? seriesId)
         {
diff --git a/NzbDrone.Core/Providers/XemCommunicationProvider.cs b/NzbDrone.Core/Providers/XemCommunicationProvider.cs
index 803b74e65..5c0e07132 100644
--- a/NzbDrone.Core/Providers/XemCommunicationProvider.cs
+++ b/NzbDrone.Core/Providers/XemCommunicationProvider.cs
@@ -5,6 +5,7 @@ using NLog;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 using NzbDrone.Common;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Model.Xem;
 
 namespace NzbDrone.Core.Providers
@@ -20,7 +21,7 @@ namespace NzbDrone.Core.Providers
     {
         private readonly IHttpProvider _httpProvider;
 
-        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger _logger =  NzbDroneLogger.GetLogger();
 
         private const string XEM_BASE_URL = "http://thexem.de/map/";
 
diff --git a/NzbDrone.Core/Providers/XemProvider.cs b/NzbDrone.Core/Providers/XemProvider.cs
index 931793dfb..e66000afc 100644
--- a/NzbDrone.Core/Providers/XemProvider.cs
+++ b/NzbDrone.Core/Providers/XemProvider.cs
@@ -3,8 +3,11 @@ using System.Collections.Generic;
 using System.Linq;
 using NLog;
 using NzbDrone.Common.Cache;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv;
 using NzbDrone.Core.Tv.Events;
 
@@ -25,7 +28,7 @@ namespace NzbDrone.Core.Providers
         private readonly ISeriesService _seriesService;
         private readonly ICached<bool> _cache;
 
-        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger _logger =  NzbDroneLogger.GetLogger();
 
         public XemProvider(IEpisodeService episodeService,
                            IXemCommunicationProvider xemCommunicationProvider,
@@ -131,8 +134,7 @@ namespace NzbDrone.Core.Providers
 
             catch (Exception ex)
             {
-                //TODO: We should increase this back to warn when caching is in place
-                _logger.TraceException("Error updating scene numbering mappings for: " + series, ex);
+                _logger.ErrorException("Error updating scene numbering mappings for: " + series, ex);
             }
         }
 
diff --git a/NzbDrone.Core/Qualities/QualityProfileRepository.cs b/NzbDrone.Core/Qualities/QualityProfileRepository.cs
index fcb70f3ff..64e36b70b 100644
--- a/NzbDrone.Core/Qualities/QualityProfileRepository.cs
+++ b/NzbDrone.Core/Qualities/QualityProfileRepository.cs
@@ -1,5 +1,7 @@
-using NzbDrone.Common.Messaging;
-using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Qualities
 {
@@ -10,8 +12,8 @@ namespace NzbDrone.Core.Qualities
 
     public class QualityProfileRepository : BasicRepository<QualityProfile>, IQualityProfileRepository
     {
-        public QualityProfileRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public QualityProfileRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
     }
diff --git a/NzbDrone.Core/Qualities/QualityProfileService.cs b/NzbDrone.Core/Qualities/QualityProfileService.cs
index fb96cee99..de79e36e9 100644
--- a/NzbDrone.Core/Qualities/QualityProfileService.cs
+++ b/NzbDrone.Core/Qualities/QualityProfileService.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv;
 
 
diff --git a/NzbDrone.Core/Qualities/QualitySizeRepository.cs b/NzbDrone.Core/Qualities/QualitySizeRepository.cs
index ef4bb4138..84a21355d 100644
--- a/NzbDrone.Core/Qualities/QualitySizeRepository.cs
+++ b/NzbDrone.Core/Qualities/QualitySizeRepository.cs
@@ -1,7 +1,9 @@
 using System;
 using System.Linq;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Qualities
 {
@@ -12,8 +14,8 @@ namespace NzbDrone.Core.Qualities
 
     public class QualitySizeRepository : BasicRepository<QualitySize>, IQualitySizeRepository
     {
-        public QualitySizeRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public QualitySizeRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/Qualities/QualitySizeService.cs b/NzbDrone.Core/Qualities/QualitySizeService.cs
index d3dce3199..c7719ef8b 100644
--- a/NzbDrone.Core/Qualities/QualitySizeService.cs
+++ b/NzbDrone.Core/Qualities/QualitySizeService.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 
 namespace NzbDrone.Core.Qualities
 {
diff --git a/NzbDrone.Core/Rest/RestSharpExtensions.cs b/NzbDrone.Core/Rest/RestSharpExtensions.cs
index b0c09368c..33b5e681b 100644
--- a/NzbDrone.Core/Rest/RestSharpExtensions.cs
+++ b/NzbDrone.Core/Rest/RestSharpExtensions.cs
@@ -1,6 +1,7 @@
 using System.Net;
 using NLog;
 using NzbDrone.Common.EnsureThat;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Common.Serializer;
 using RestSharp;
 
@@ -8,7 +9,7 @@ namespace NzbDrone.Core.Rest
 {
     public static class RestSharpExtensions
     {
-        private static readonly Logger Logger = LogManager.GetLogger("RestExtensions");
+        private static readonly Logger Logger = NzbDroneLogger.GetLogger();
 
         public static IRestResponse ValidateResponse(this IRestResponse response, IRestClient restClient)
         {
@@ -22,7 +23,7 @@ namespace NzbDrone.Core.Rest
             Ensure.That(() => response.Request).IsNotNull();
             Ensure.That(() => restClient).IsNotNull();
 
-            Logger.Trace("Validating Responses from [{0}] [{1}] status: [{2}] body:[{3}]", response.Request.Method, restClient.BuildUri(response.Request), response.StatusCode, response.Content);
+            Logger.Trace("Validating Responses from [{0}] [{1}] status: [{2}]", response.Request.Method, restClient.BuildUri(response.Request), response.StatusCode);
 
             if (response.ResponseUri == null)
             {
diff --git a/NzbDrone.Core/RootFolders/RootFolderService.cs b/NzbDrone.Core/RootFolders/RootFolderService.cs
index 160cd3c9f..9e441d91d 100644
--- a/NzbDrone.Core/RootFolders/RootFolderService.cs
+++ b/NzbDrone.Core/RootFolders/RootFolderService.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.IO;
 using NLog;
 using NzbDrone.Common;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Tv;
@@ -23,12 +24,15 @@ namespace NzbDrone.Core.RootFolders
 
     public class RootFolderService : IRootFolderService
     {
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger = NzbDroneLogger.GetLogger();
         private readonly IBasicRepository<RootFolder> _rootFolderRepository;
         private readonly IDiskProvider _diskProvider;
         private readonly ISeriesRepository _seriesRepository;
         private readonly IConfigService _configService;
 
+        private static readonly HashSet<string> SpecialFolders = new HashSet<string> { "$recycle.bin", "system volume information", "recycler" };
+
+
         public RootFolderService(IBasicRepository<RootFolder> rootFolderRepository,
                                  IDiskProvider diskProvider,
                                  ISeriesRepository seriesRepository,
@@ -118,7 +122,7 @@ namespace NzbDrone.Core.RootFolders
 
             if (Path.GetPathRoot(path).Equals(path, StringComparison.InvariantCultureIgnoreCase))
             {
-                var setToRemove = _diskProvider.SpecialFolders;
+                var setToRemove = SpecialFolders;
                 results.RemoveAll(x => setToRemove.Contains(new DirectoryInfo(x.Path.ToLowerInvariant()).Name));
             }
 
diff --git a/NzbDrone.Core/SeriesStats/SeriesStatistics.cs b/NzbDrone.Core/SeriesStats/SeriesStatistics.cs
index 24893eb26..170985718 100644
--- a/NzbDrone.Core/SeriesStats/SeriesStatistics.cs
+++ b/NzbDrone.Core/SeriesStats/SeriesStatistics.cs
@@ -6,7 +6,6 @@ namespace NzbDrone.Core.SeriesStats
     public class SeriesStatistics : ResultSet
     {
         public int SeriesId { get; set; }
-        public int SeasonCount { get; set; }
         public string NextAiringString { get; set; }
         public int EpisodeFileCount { get; set; }
         public int EpisodeCount { get; set; }
diff --git a/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs
index eece02c35..09244dc06 100644
--- a/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs
+++ b/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs
@@ -54,10 +54,9 @@ namespace NzbDrone.Core.SeriesStats
         {
             return @"SELECT
                      SeriesId,
-                     SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR Episodes.EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount,
-                     SUM(CASE WHEN Episodes.EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount,
-                     MAX(Episodes.SeasonNumber) as SeasonCount,
-                     MIN(CASE WHEN AirDateUtc < @currentDate THEN NULL ELSE AirDateUtc END) AS NextAiringString
+                     SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount,
+                     SUM(CASE WHEN EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount,
+                     MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString
                      FROM Episodes";
         }
 
diff --git a/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs b/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs
index f38af963c..8ccc3a2b4 100644
--- a/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs
+++ b/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs
@@ -1,14 +1,26 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Tv.Commands
 {
-    public class RefreshSeriesCommand : ICommand
+    public class RefreshSeriesCommand : Command
     {
-        public int? SeriesId { get; private set; }
+        public int? SeriesId { get; set; }
+
+        public RefreshSeriesCommand()
+        {
+        }
 
         public RefreshSeriesCommand(int? seriesId)
         {
             SeriesId = seriesId;
         }
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Tv/EpisodeRepository.cs b/NzbDrone.Core/Tv/EpisodeRepository.cs
index 280dec96e..cd12016ed 100644
--- a/NzbDrone.Core/Tv/EpisodeRepository.cs
+++ b/NzbDrone.Core/Tv/EpisodeRepository.cs
@@ -2,8 +2,9 @@
 using System.Collections.Generic;
 using System.Linq;
 using Marr.Data.QGen;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 
 
 namespace NzbDrone.Core.Tv
@@ -31,8 +32,8 @@ namespace NzbDrone.Core.Tv
     {
         private readonly IDatabase _database;
 
-        public EpisodeRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public EpisodeRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
             _database = database;
         }
diff --git a/NzbDrone.Core/Tv/EpisodeService.cs b/NzbDrone.Core/Tv/EpisodeService.cs
index dce584fa4..8d3f3ccd2 100644
--- a/NzbDrone.Core/Tv/EpisodeService.cs
+++ b/NzbDrone.Core/Tv/EpisodeService.cs
@@ -2,10 +2,12 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Datastore;
 using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv.Events;
 
 namespace NzbDrone.Core.Tv
@@ -40,7 +42,7 @@ namespace NzbDrone.Core.Tv
         IHandleAsync<SeriesDeletedEvent>
     {
 
-        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger logger =  NzbDroneLogger.GetLogger();
 
         private readonly IEpisodeRepository _episodeRepository;
         private readonly IConfigService _configService;
@@ -128,7 +130,7 @@ namespace NzbDrone.Core.Tv
             var episode = _episodeRepository.Get(episodeId);
             _episodeRepository.SetMonitoredFlat(episode, monitored);
 
-            logger.Info("Monitored flag for Episode:{0} was set to {1}", episodeId, monitored);
+            logger.Debug("Monitored flag for Episode:{0} was set to {1}", episodeId, monitored);
         }
 
         public void SetEpisodeMonitoredBySeason(int seriesId, int seasonNumber, bool monitored)
diff --git a/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/NzbDrone.Core/Tv/RefreshEpisodeService.cs
index c62d8a5d2..5c4beb1b0 100644
--- a/NzbDrone.Core/Tv/RefreshEpisodeService.cs
+++ b/NzbDrone.Core/Tv/RefreshEpisodeService.cs
@@ -2,7 +2,8 @@
 using System.Collections.Generic;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Tv.Events;
 
 namespace NzbDrone.Core.Tv
@@ -15,27 +16,24 @@ namespace NzbDrone.Core.Tv
     public class RefreshEpisodeService : IRefreshEpisodeService
     {
         private readonly IEpisodeService _episodeService;
-        private readonly ISeasonService _seasonService;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly Logger _logger;
 
-        public RefreshEpisodeService(IEpisodeService episodeService,
-            ISeasonService seasonService, IMessageAggregator messageAggregator, Logger logger)
+        public RefreshEpisodeService(IEpisodeService episodeService, IEventAggregator eventAggregator, Logger logger)
         {
             _episodeService = episodeService;
-            _seasonService = seasonService;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _logger = logger;
         }
 
         public void RefreshEpisodeInfo(Series series, IEnumerable<Episode> remoteEpisodes)
         {
-            _logger.Info("Starting series info refresh for: {0}", series);
+            _logger.Info("Starting episode info refresh for: {0}", series);
             var successCount = 0;
             var failCount = 0;
 
             var existingEpisodes = _episodeService.GetEpisodeBySeries(series.Id);
-            var seasons = _seasonService.GetSeasonsBySeries(series.Id);
+            var seasons = series.Seasons;
 
             var updateList = new List<Episode>();
             var newList = new List<Episode>();
@@ -44,7 +42,7 @@ namespace NzbDrone.Core.Tv
             {
                 try
                 {
-                    var episodeToUpdate = existingEpisodes.SingleOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber);
+                    var episodeToUpdate = existingEpisodes.FirstOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber);
 
                     if (episodeToUpdate != null)
                     {
@@ -88,17 +86,17 @@ namespace NzbDrone.Core.Tv
 
             if (newList.Any())
             {
-                _messageAggregator.PublishEvent(new EpisodeInfoAddedEvent(newList, series));
+                _eventAggregator.PublishEvent(new EpisodeInfoAddedEvent(newList, series));
             }
 
             if (updateList.Any())
             {
-                _messageAggregator.PublishEvent(new EpisodeInfoUpdatedEvent(updateList));
+                _eventAggregator.PublishEvent(new EpisodeInfoUpdatedEvent(updateList));
             }
 
             if (existingEpisodes.Any())
             {
-                _messageAggregator.PublishEvent(new EpisodeInfoDeletedEvent(updateList));
+                _eventAggregator.PublishEvent(new EpisodeInfoDeletedEvent(updateList));
             }
 
             if (failCount != 0)
@@ -114,11 +112,6 @@ namespace NzbDrone.Core.Tv
 
         private static bool GetMonitoredStatus(Episode episode, IEnumerable<Season> seasons)
         {
-            if (episode.SeasonNumber == 0)
-            {
-                return false;
-            }
-
             if (episode.EpisodeNumber == 0 && episode.SeasonNumber != 1)
             {
                 return false;
@@ -132,7 +125,7 @@ namespace NzbDrone.Core.Tv
         {
             var groups =
                 allEpisodes.Where(c => c.AirDateUtc.HasValue)
-                    .GroupBy(e => new { e.SeriesId, e.AirDate })
+                    .GroupBy(e => new { e.SeasonNumber, e.AirDate })
                     .Where(g => g.Count() > 1)
                     .ToList();
 
diff --git a/NzbDrone.Core/Tv/RefreshSeriesService.cs b/NzbDrone.Core/Tv/RefreshSeriesService.cs
index bbdb8e472..d6607dda5 100644
--- a/NzbDrone.Core/Tv/RefreshSeriesService.cs
+++ b/NzbDrone.Core/Tv/RefreshSeriesService.cs
@@ -1,9 +1,13 @@
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using NLog;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.DataAugmentation.DailySeries;
+using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.MetadataSource;
 using NzbDrone.Core.Tv.Commands;
 using NzbDrone.Core.Tv.Events;
@@ -16,53 +20,23 @@ namespace NzbDrone.Core.Tv
         private readonly IProvideSeriesInfo _seriesInfo;
         private readonly ISeriesService _seriesService;
         private readonly IRefreshEpisodeService _refreshEpisodeService;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly IDailySeriesService _dailySeriesService;
         private readonly Logger _logger;
 
-        public RefreshSeriesService(IProvideSeriesInfo seriesInfo, ISeriesService seriesService, IRefreshEpisodeService refreshEpisodeService, IMessageAggregator messageAggregator, IDailySeriesService dailySeriesService, Logger logger)
+        public RefreshSeriesService(IProvideSeriesInfo seriesInfo, ISeriesService seriesService, IRefreshEpisodeService refreshEpisodeService, IEventAggregator eventAggregator, IDailySeriesService dailySeriesService, Logger logger)
         {
             _seriesInfo = seriesInfo;
             _seriesService = seriesService;
             _refreshEpisodeService = refreshEpisodeService;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _dailySeriesService = dailySeriesService;
             _logger = logger;
         }
 
-
-        public void Execute(RefreshSeriesCommand message)
-        {
-            if (message.SeriesId.HasValue)
-            {
-                var series = _seriesService.GetSeries(message.SeriesId.Value);
-                RefreshSeriesInfo(series);
-            }
-            else
-            {
-                var allSeries = _seriesService.GetAllSeries().OrderBy(c => c.LastInfoSync).ToList();
-
-                foreach (var series in allSeries)
-                {
-                    try
-                    {
-                        RefreshSeriesInfo(series);
-                    }
-                    catch (Exception e)
-                    {
-                        _logger.ErrorException("Couldn't refresh info for {0}".Inject(series), e);
-                    }
-                }
-            }
-        }
-
-        public void HandleAsync(SeriesAddedEvent message)
-        {
-            RefreshSeriesInfo(message.Series);
-        }
-
         private void RefreshSeriesInfo(Series series)
         {
+            _logger.ProgressInfo("Updating Info for {0}", series.Title);
             var tuple = _seriesInfo.GetSeriesInfo(series.TvdbId);
 
             var seriesInfo = tuple.Item1;
@@ -93,10 +67,65 @@ namespace NzbDrone.Core.Tv
                 _logger.WarnException("Couldn't update series path for " + series.Path, e);
             }
 
+            series.Seasons = UpdateSeasons(series, seriesInfo);
+
             _seriesService.UpdateSeries(series);
             _refreshEpisodeService.RefreshEpisodeInfo(series, tuple.Item2);
 
-            _messageAggregator.PublishEvent(new SeriesUpdatedEvent(series));
+            _logger.Debug("Finished series refresh for {0}", series.Title);
+            _eventAggregator.PublishEvent(new SeriesUpdatedEvent(series));
+        }
+
+        private List<Season> UpdateSeasons(Series series, Series seriesInfo)
+        {
+            foreach (var season in seriesInfo.Seasons)
+            {
+                var existingSeason = series.Seasons.SingleOrDefault(s => s.SeasonNumber == season.SeasonNumber);
+
+                //Todo: Should this should use the previous season's monitored state?
+                if (existingSeason == null)
+                {
+                    _logger.Trace("New season ({0}) for series: [{1}] {2}, setting monitored to true", season.SeasonNumber, series.TvdbId, series.Title);
+                    season.Monitored = true;
+                }
+
+                else
+                {
+                    season.Monitored = existingSeason.Monitored;
+                }
+            }
+
+            return seriesInfo.Seasons;
+        }
+
+        public void Execute(RefreshSeriesCommand message)
+        {
+            if (message.SeriesId.HasValue)
+            {
+                var series = _seriesService.GetSeries(message.SeriesId.Value);
+                RefreshSeriesInfo(series);
+            }
+            else
+            {
+                var allSeries = _seriesService.GetAllSeries().OrderBy(c => c.LastInfoSync).ToList();
+
+                foreach (var series in allSeries)
+                {
+                    try
+                    {
+                        RefreshSeriesInfo(series);
+                    }
+                    catch (Exception e)
+                    {
+                        _logger.ErrorException("Couldn't refresh info for {0}".Inject(series), e);
+                    }
+                }
+            }
+        }
+
+        public void HandleAsync(SeriesAddedEvent message)
+        {
+            RefreshSeriesInfo(message.Series);
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Tv/Season.cs b/NzbDrone.Core/Tv/Season.cs
index be36fcc86..5073ea2b4 100644
--- a/NzbDrone.Core/Tv/Season.cs
+++ b/NzbDrone.Core/Tv/Season.cs
@@ -1,15 +1,11 @@
 using System;
-using System.Collections.Generic;
 using NzbDrone.Core.Datastore;
 
 namespace NzbDrone.Core.Tv
 {
-    public class Season : ModelBase
+    public class Season : IEmbeddedDocument
     {
-        public int SeriesId { get; set; }
         public int SeasonNumber { get; set; }
         public Boolean Monitored { get; set; }
-
-        public List<Episode> Episodes { get; set; }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Tv/SeasonRepository.cs b/NzbDrone.Core/Tv/SeasonRepository.cs
index f580c8d94..470c2e5ca 100644
--- a/NzbDrone.Core/Tv/SeasonRepository.cs
+++ b/NzbDrone.Core/Tv/SeasonRepository.cs
@@ -1,49 +1,27 @@
 using System.Collections.Generic;
 using System.Linq;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 
 
 namespace NzbDrone.Core.Tv
 {
-    public interface ISeasonRepository : IBasicRepository<Season>
+    public interface ISeasonRepository : IBasicRepository<Series>
     {
-        IList<int> GetSeasonNumbers(int seriesId);
-        Season Get(int seriesId, int seasonNumber);
-        bool IsMonitored(int seriesId, int seasonNumber);
         List<Season> GetSeasonBySeries(int seriesId);
     }
 
-    public class SeasonRepository : BasicRepository<Season>, ISeasonRepository
+    public class SeasonRepository : BasicRepository<Series>, ISeasonRepository
     {
-
-        public SeasonRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
-        {
-        }
-
-        public IList<int> GetSeasonNumbers(int seriesId)
-        {
-            return Query.Where(c => c.SeriesId == seriesId).Select(c => c.SeasonNumber).ToList();
-        }
-
-        public Season Get(int seriesId, int seasonNumber)
-        {
-            return Query.Single(s => s.SeriesId == seriesId && s.SeasonNumber == seasonNumber);
-        }
-
-        public bool IsMonitored(int seriesId, int seasonNumber)
+        public SeasonRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
-            var season = Query.SingleOrDefault(s => s.SeriesId == seriesId && s.SeasonNumber == seasonNumber);
-
-            if (season == null) return true;
-
-            return season.Monitored;
         }
 
         public List<Season> GetSeasonBySeries(int seriesId)
         {
-            return Query.Where(s => s.SeriesId == seriesId);
+            return Query.Single(s => s.Id == seriesId).Seasons;
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Tv/SeasonService.cs b/NzbDrone.Core/Tv/SeasonService.cs
deleted file mode 100644
index 3432f389d..000000000
Binary files a/NzbDrone.Core/Tv/SeasonService.cs and /dev/null differ
diff --git a/NzbDrone.Core/Tv/Series.cs b/NzbDrone.Core/Tv/Series.cs
index 55a6adb73..107162aaf 100644
--- a/NzbDrone.Core/Tv/Series.cs
+++ b/NzbDrone.Core/Tv/Series.cs
@@ -34,12 +34,15 @@ namespace NzbDrone.Core.Tv
         public bool UseSceneNumbering { get; set; }
         public string TitleSlug { get; set; }
         public string Path { get; set; }
+        public int Year { get; set; }
 
         public string RootFolderPath { get; set; }
 
         public DateTime? FirstAired { get; set; }
         public LazyLoaded<QualityProfile> QualityProfile { get; set; }
 
+        public List<Season> Seasons { get; set; }
+
         public override string ToString()
         {
             return string.Format("[{0}][{1}]", TvdbId, Title.NullSafe());
diff --git a/NzbDrone.Core/Tv/SeriesRepository.cs b/NzbDrone.Core/Tv/SeriesRepository.cs
index 83fac8844..161ebbac2 100644
--- a/NzbDrone.Core/Tv/SeriesRepository.cs
+++ b/NzbDrone.Core/Tv/SeriesRepository.cs
@@ -1,8 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
+
 
 namespace NzbDrone.Core.Tv
 {
@@ -20,8 +22,8 @@ namespace NzbDrone.Core.Tv
 
     public class SeriesRepository : BasicRepository<Series>, ISeriesRepository
     {
-        public SeriesRepository(IDatabase database, IMessageAggregator messageAggregator)
-            : base(database, messageAggregator)
+        public SeriesRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
         {
         }
 
diff --git a/NzbDrone.Core/Tv/SeriesService.cs b/NzbDrone.Core/Tv/SeriesService.cs
index 7c6dba091..9e8d7ba99 100644
--- a/NzbDrone.Core/Tv/SeriesService.cs
+++ b/NzbDrone.Core/Tv/SeriesService.cs
@@ -4,9 +4,10 @@ using System.IO;
 using System.Linq;
 using NLog;
 using NzbDrone.Common.EnsureThat;
-using NzbDrone.Common.Messaging;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.DataAugmentation.Scene;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.Organizer;
 using NzbDrone.Core.Tv.Events;
 
@@ -14,10 +15,8 @@ namespace NzbDrone.Core.Tv
 {
     public interface ISeriesService
     {
-        bool IsMonitored(int id);
         Series GetSeries(int seriesId);
         Series AddSeries(Series newSeries);
-        void UpdateFromSeriesEditor(IList<Series> editedSeries);
         Series FindByTvdbId(int tvdbId);
         Series FindByTvRageId(int tvRageId);
         Series FindByTitle(string title);
@@ -26,37 +25,32 @@ namespace NzbDrone.Core.Tv
         List<Series> GetAllSeries();
         Series UpdateSeries(Series series);
         bool SeriesPathExists(string folder);
-        List<Series> GetSeriesInList(IEnumerable<int> seriesIds);
-        Series FindBySlug(string slug);
-        List<String> GetSeriesPaths();
     }
 
     public class SeriesService : ISeriesService
     {
         private readonly ISeriesRepository _seriesRepository;
         private readonly IConfigService _configService;
-        private readonly IMessageAggregator _messageAggregator;
+        private readonly IEventAggregator _eventAggregator;
         private readonly ISceneMappingService _sceneMappingService;
+        private readonly IEpisodeService _episodeService;
         private readonly Logger _logger;
 
         public SeriesService(ISeriesRepository seriesRepository,
                              IConfigService configServiceService,
-                             IMessageAggregator messageAggregator,
+                             IEventAggregator eventAggregator,
                              ISceneMappingService sceneMappingService,
+                             IEpisodeService episodeService,
                              Logger logger)
         {
             _seriesRepository = seriesRepository;
             _configService = configServiceService;
-            _messageAggregator = messageAggregator;
+            _eventAggregator = eventAggregator;
             _sceneMappingService = sceneMappingService;
+            _episodeService = episodeService;
             _logger = logger;
         }
 
-        public bool IsMonitored(int id)
-        {
-            return _seriesRepository.Get(id).Monitored;
-        }
-
         public Series GetSeries(int seriesId)
         {
             return _seriesRepository.Get(seriesId);
@@ -80,29 +74,11 @@ namespace NzbDrone.Core.Tv
             newSeries.SeasonFolder = _configService.UseSeasonFolder;
 
             _seriesRepository.Insert(newSeries);
-            _messageAggregator.PublishEvent(new SeriesAddedEvent(newSeries));
+            _eventAggregator.PublishEvent(new SeriesAddedEvent(newSeries));
 
             return newSeries;
         }
 
-        public void UpdateFromSeriesEditor(IList<Series> editedSeries)
-        {
-            var allSeries = _seriesRepository.All();
-
-            foreach (var series in allSeries)
-            {
-                //Only update parameters that can be changed in MassEdit
-                var edited = editedSeries.Single(s => s.Id == series.Id);
-                series.QualityProfileId = edited.QualityProfileId;
-                series.Monitored = edited.Monitored;
-                series.SeasonFolder = edited.SeasonFolder;
-                series.Path = edited.Path;
-
-                _seriesRepository.Update(series);
-            }
-
-        }
-
         public Series FindByTvdbId(int tvdbId)
         {
             return _seriesRepository.FindByTvdbId(tvdbId);
@@ -113,17 +89,6 @@ namespace NzbDrone.Core.Tv
             return _seriesRepository.FindByTvRageId(tvRageId);
         }
 
-        public Series FindBySlug(string slug)
-        {
-            var series = _seriesRepository.FindBySlug(slug);
-            return series;
-        }
-
-        public List<string> GetSeriesPaths()
-        {
-            return _seriesRepository.GetSeriesPaths();
-        }
-
         public Series FindByTitle(string title)
         {
             var tvdbId = _sceneMappingService.GetTvDbId(title);
@@ -145,7 +110,7 @@ namespace NzbDrone.Core.Tv
         {
             var series = _seriesRepository.Get(seriesId);
             _seriesRepository.Delete(seriesId);
-            _messageAggregator.PublishEvent(new SeriesDeletedEvent(series, deleteFiles));
+            _eventAggregator.PublishEvent(new SeriesDeletedEvent(series, deleteFiles));
         }
 
         public List<Series> GetAllSeries()
@@ -155,6 +120,18 @@ namespace NzbDrone.Core.Tv
 
         public Series UpdateSeries(Series series)
         {
+            var storedSeries = GetSeries(series.Id);
+            
+            foreach (var season in series.Seasons)
+            {
+                var storedSeason = storedSeries.Seasons.SingleOrDefault(s => s.SeasonNumber == season.SeasonNumber);
+
+                if (storedSeason != null && season.Monitored != storedSeason.Monitored)
+                {
+                    _episodeService.SetEpisodeMonitoredBySeason(series.Id, season.SeasonNumber, season.Monitored);
+                }
+            }
+
             return _seriesRepository.Update(series);
         }
 
@@ -162,10 +139,5 @@ namespace NzbDrone.Core.Tv
         {
             return _seriesRepository.SeriesPathExists(folder);
         }
-
-        public List<Series> GetSeriesInList(IEnumerable<int> seriesIds)
-        {
-            return _seriesRepository.Get(seriesIds).ToList();
-        }
     }
 }
diff --git a/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs b/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs
index bbbf6a1f3..d961c8e80 100644
--- a/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs
+++ b/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs
@@ -1,8 +1,15 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 
 namespace NzbDrone.Core.Update.Commands
 {
-    public class ApplicationUpdateCommand : ICommand
+    public class ApplicationUpdateCommand : Command
     {
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Core/Update/InstallUpdateService.cs b/NzbDrone.Core/Update/InstallUpdateService.cs
index d204bcab3..fd74f199c 100644
--- a/NzbDrone.Core/Update/InstallUpdateService.cs
+++ b/NzbDrone.Core/Update/InstallUpdateService.cs
@@ -3,8 +3,10 @@ using System.IO;
 using NLog;
 using NzbDrone.Common;
 using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.Update.Commands;
+using NzbDrone.Core.Instrumentation;
 
 namespace NzbDrone.Core.Update
 {
@@ -36,6 +38,7 @@ namespace NzbDrone.Core.Update
 
         public void Execute(ApplicationUpdateCommand message)
         {
+            _logger.ProgressDebug("Checking for updates");
             var latestAvailable = _checkUpdateService.AvailableUpdate();
 
             if (latestAvailable != null)
@@ -58,11 +61,11 @@ namespace NzbDrone.Core.Update
                     _diskProvider.DeleteFolder(updateSandboxFolder, true);
                 }
 
-                _logger.Info("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination);
+                _logger.ProgressInfo("Downloading Updated {0} [{1}]", updatePackage.Version, updatePackage.Branch);
+                _logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination);
                 _httpProvider.DownloadFile(updatePackage.Url, packageDestination);
-                _logger.Info("Download completed for update package from [{0}]", updatePackage.FileName);
 
-                _logger.Info("Extracting Update package");
+                _logger.ProgressInfo("Extracting Update package");
                 _archiveService.Extract(packageDestination, updateSandboxFolder);
                 _logger.Info("Update package extracted successfully");
 
@@ -72,6 +75,8 @@ namespace NzbDrone.Core.Update
 
                 _logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath());
 
+                _logger.ProgressInfo("NzbDrone will restart shortly.");
+
                 _processProvider.Start(_appFolderInfo.GetUpdateClientExePath(), _processProvider.GetCurrentProcess().Id.ToString());
             }
             catch (Exception ex)
diff --git a/NzbDrone.Core/Update/UpdateCheckService.cs b/NzbDrone.Core/Update/UpdateCheckService.cs
index 313f1ef58..32cb64be2 100644
--- a/NzbDrone.Core/Update/UpdateCheckService.cs
+++ b/NzbDrone.Core/Update/UpdateCheckService.cs
@@ -1,5 +1,7 @@
 using NLog;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.Instrumentation;
 
 namespace NzbDrone.Core.Update
 {
@@ -8,28 +10,30 @@ namespace NzbDrone.Core.Update
         UpdatePackage AvailableUpdate();
     }
 
-
     public class CheckUpdateService : ICheckUpdateService
     {
         private readonly IUpdatePackageProvider _updatePackageProvider;
+        private readonly IConfigFileProvider _configFileProvider;
 
         private readonly Logger _logger;
 
 
-        public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, Logger logger)
+        public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, IConfigFileProvider configFileProvider, Logger logger)
         {
             _updatePackageProvider = updatePackageProvider;
+            _configFileProvider = configFileProvider;
             _logger = logger;
         }
 
         public UpdatePackage AvailableUpdate()
         {
-            var latestAvailable = _updatePackageProvider.GetLatestUpdate();
+            if (OsInfo.IsLinux) return null;
+
+            var latestAvailable = _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version);
 
             if (latestAvailable == null)
             {
-                _logger.Debug("No update available.");
-                return null;
+                _logger.ProgressDebug("No update available.");
             }
 
             return latestAvailable;
diff --git a/NzbDrone.Core/Update/UpdatePackage.cs b/NzbDrone.Core/Update/UpdatePackage.cs
index ce038ac99..70ca40b87 100644
--- a/NzbDrone.Core/Update/UpdatePackage.cs
+++ b/NzbDrone.Core/Update/UpdatePackage.cs
@@ -1,13 +1,14 @@
 using System;
 using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
 
 namespace NzbDrone.Core.Update
 {
     public class UpdatePackage
     {
-        public String Id { get; set; }
+        public string Id { get; set; }
 
-        [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))]
+        [JsonConverter(typeof(VersionConverter))]
         public Version Version { get; set; }
 
         public String Branch { get; set; }
diff --git a/NzbDrone.Core/Update/UpdatePackageAvailable.cs b/NzbDrone.Core/Update/UpdatePackageAvailable.cs
index 1d76e22d7..c6b99b446 100644
--- a/NzbDrone.Core/Update/UpdatePackageAvailable.cs
+++ b/NzbDrone.Core/Update/UpdatePackageAvailable.cs
@@ -1,7 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
 
 namespace NzbDrone.Core.Update
 {
diff --git a/NzbDrone.Core/Update/UpdatePackageProvider.cs b/NzbDrone.Core/Update/UpdatePackageProvider.cs
index e045a89fd..c6b4b64c0 100644
--- a/NzbDrone.Core/Update/UpdatePackageProvider.cs
+++ b/NzbDrone.Core/Update/UpdatePackageProvider.cs
@@ -1,37 +1,27 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-using Newtonsoft.Json;
-using NLog;
 using NzbDrone.Common;
-using NzbDrone.Common.EnvironmentInfo;
-using NzbDrone.Core.Configuration;
+using RestSharp;
+using NzbDrone.Core.Rest;
 
 namespace NzbDrone.Core.Update
 {
     public interface IUpdatePackageProvider
     {
-        UpdatePackage GetLatestUpdate();
+        UpdatePackage GetLatestUpdate(string branch, Version currentVersion);
     }
 
     public class UpdatePackageProvider : IUpdatePackageProvider
     {
-        private readonly IConfigFileProvider _configFileProvider;
-        private readonly IHttpProvider _httpProvider;
-        private readonly Logger _logger;
-
-        public UpdatePackageProvider(IConfigFileProvider configFileProvider, IHttpProvider httpProvider, Logger logger)
+        public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
         {
-            _configFileProvider = configFileProvider;
-            _httpProvider = httpProvider;
-            _logger = logger;
-        }
+            var restClient = new RestClient(Services.RootUrl);
 
-        public UpdatePackage GetLatestUpdate()
-        {
-            var url = String.Format("{0}/v1/update/{1}?version={2}", Services.RootUrl, _configFileProvider.Branch, BuildInfo.Version);
-            var update = JsonConvert.DeserializeObject<UpdatePackageAvailable>(_httpProvider.DownloadString(url));
+            var request = new RestRequest("/v1/update/{branch}");
+
+            request.AddParameter("version", currentVersion);
+            request.AddUrlSegment("branch", branch);
+
+            var update = restClient.ExecuteAndValidate<UpdatePackageAvailable>(request);
 
             if (!update.Available) return null;
 
diff --git a/NzbDrone.Core/progressmessaging/CommandUpdatedEvent.cs b/NzbDrone.Core/progressmessaging/CommandUpdatedEvent.cs
new file mode 100644
index 000000000..2d8c93269
--- /dev/null
+++ b/NzbDrone.Core/progressmessaging/CommandUpdatedEvent.cs
@@ -0,0 +1,15 @@
+using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.ProgressMessaging
+{
+    public class CommandUpdatedEvent : IEvent
+    {
+        public Command Command { get; set; }
+
+        public CommandUpdatedEvent(Command command)
+        {
+            Command = command;
+        }
+    }
+}
diff --git a/NzbDrone.Host/Bootstrap.cs b/NzbDrone.Host/Bootstrap.cs
index e4b592fd1..fb1d23eec 100644
--- a/NzbDrone.Host/Bootstrap.cs
+++ b/NzbDrone.Host/Bootstrap.cs
@@ -1,5 +1,4 @@
 using System.Reflection;
-using NLog;
 using NzbDrone.Common.Composition;
 using NzbDrone.Common.EnvironmentInfo;
 using NzbDrone.Common.Instrumentation;
@@ -12,7 +11,7 @@ namespace NzbDrone.Host
     {
         public static IContainer Start(StartupArguments args, IUserAlert userAlert)
         {
-            var logger = LogManager.GetLogger("AppMain");
+            var logger = NzbDroneLogger.GetLogger();
 
             GlobalExceptionHandlers.Register();
             IgnoreCertErrorPolicy.Register();
diff --git a/NzbDrone.Host/MainAppContainerBuilder.cs b/NzbDrone.Host/MainAppContainerBuilder.cs
index e12fc9153..299de7925 100644
--- a/NzbDrone.Host/MainAppContainerBuilder.cs
+++ b/NzbDrone.Host/MainAppContainerBuilder.cs
@@ -1,13 +1,11 @@
-using System;
-using Nancy.Bootstrapper;
+using Nancy.Bootstrapper;
 using NzbDrone.Api;
-using NzbDrone.Api.SignalR;
 using NzbDrone.Common.Composition;
 using NzbDrone.Common.EnvironmentInfo;
 using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Organizer;
 using NzbDrone.Core.RootFolders;
-using NzbDrone.Host.Owin;
+using NzbDrone.SignalR;
 
 namespace NzbDrone.Host
 {
@@ -19,7 +17,7 @@ namespace NzbDrone.Host
         }
 
         private MainAppContainerBuilder(StartupArguments args)
-            : base(args, "NzbDrone.Host", "NzbDrone.Common", "NzbDrone.Core", "NzbDrone.Api")
+            : base(args, "NzbDrone.Host", "NzbDrone.Common", "NzbDrone.Core", "NzbDrone.Api", "NzbDrone.SignalR")
         {
 
             AutoRegisterImplementations<NzbDronePersistentConnection>();
diff --git a/NzbDrone.Host/NLog.config b/NzbDrone.Host/NLog.config
deleted file mode 100644
index 65103a066..000000000
--- a/NzbDrone.Host/NLog.config
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
-      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      autoReload="true"
-      internalLogLevel="Info"
-      throwExceptions="true"
-      internalLogToConsole="true"
-      internalLogToConsoleError="true">
-  <extensions>
-    <add assembly="NzbDrone.Core"/>
-    <add assembly="NzbDrone.Common"/>
-  </extensions>
-  <targets>
-    <target xsi:type="ColoredConsole" name="consoleLogger" layout="[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"/>
-    <target xsi:type="NLogViewer" name="udpTarget" address="udp://127.0.0.1:20480" includeCallSite="true" includeSourceInfo="true" includeNLogData="true" includeNdc="true">
-      <parameter>
-        <name>Exception</name>
-        <layout>${exception:format=ToString}</layout>
-      </parameter>
-    </target>
-    <target xsi:type="File" name="rollingFileLogger" fileName="${appLog}" autoFlush="true" keepFileOpen="false"
-           concurrentWrites="false" concurrentWriteAttemptDelay="50" concurrentWriteAttempts ="10"
-            archiveAboveSize="1024000" maxArchiveFiles="5" enableFileDelete="true" archiveNumbering ="Rolling"
-            layout="${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"/>
-  </targets>
-  <rules>
-    <logger name="*" minlevel="Trace" writeTo="consoleLogger"/>
-    <logger name="*" minlevel="Off" writeTo="udpTarget"/>
-    <logger name="*" minlevel="Info" writeTo="rollingFileLogger"/>
-  </rules>
-</nlog>
\ No newline at end of file
diff --git a/NzbDrone.Host/NzbDrone.Host.csproj b/NzbDrone.Host/NzbDrone.Host.csproj
index c625626ea..7ecf58a13 100644
--- a/NzbDrone.Host/NzbDrone.Host.csproj
+++ b/NzbDrone.Host/NzbDrone.Host.csproj
@@ -125,6 +125,7 @@
     <Compile Include="Owin\NlogTextWriter.cs" />
     <Compile Include="Owin\OwinServiceProvider.cs" />
     <Compile Include="Owin\OwinTraceOutputFactory.cs" />
+    <Compile Include="Owin\PortInUseException.cs" />
     <Compile Include="PlatformValidation.cs" />
     <Compile Include="MainAppContainerBuilder.cs" />
     <Compile Include="ApplicationModes.cs" />
@@ -141,10 +142,6 @@
   </ItemGroup>
   <ItemGroup>
     <None Include="app.config" />
-    <Content Include="NLog.config">
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
-      <SubType>Designer</SubType>
-    </Content>
     <None Include="NLog.xsd">
       <SubType>Designer</SubType>
     </None>
@@ -188,6 +185,13 @@
       <Project>{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}</Project>
       <Name>NzbDrone.Core</Name>
     </ProjectReference>
+    <ProjectReference Include="..\NzbDrone.SignalR\NzbDrone.SignalR.csproj">
+      <Project>{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}</Project>
+      <Name>NzbDrone.SignalR</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="SignalR\" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
diff --git a/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs b/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs
index ba9a436fa..cf4396edf 100644
--- a/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs
+++ b/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs
@@ -1,32 +1,25 @@
-using System.Collections.Generic;
+using System;
 using Microsoft.AspNet.SignalR;
-using NzbDrone.Api.SignalR;
 using NzbDrone.Common.Composition;
+using NzbDrone.SignalR;
 using Owin;
 
 namespace NzbDrone.Host.Owin.MiddleWare
 {
     public class SignalRMiddleWare : IOwinMiddleWare
     {
-        private readonly IEnumerable<NzbDronePersistentConnection> _persistentConnections;
-
         public int Order { get { return 0; } }
 
-        public SignalRMiddleWare(IEnumerable<NzbDronePersistentConnection> persistentConnections, IContainer container)
+        public SignalRMiddleWare(IContainer container)
         {
-            _persistentConnections = persistentConnections;
-
             SignalrDependencyResolver.Register(container);
+
+            GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(300000);
         }
 
         public void Attach(IAppBuilder appBuilder)
         {
-            foreach (var nzbDronePersistentConnection in _persistentConnections)
-            {
-                var url = string.Format("signalr/{0}", nzbDronePersistentConnection.Resource.Trim('/'));
-                appBuilder.MapConnection(url, nzbDronePersistentConnection.GetType(), new ConnectionConfiguration { EnableCrossDomain = true });
-            }
-
+            appBuilder.MapConnection("signalr", typeof(NzbDronePersistentConnection), new ConnectionConfiguration { EnableCrossDomain = true });
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Host/Owin/NlogTextWriter.cs b/NzbDrone.Host/Owin/NlogTextWriter.cs
index d4cd4da7d..d5b4d04aa 100644
--- a/NzbDrone.Host/Owin/NlogTextWriter.cs
+++ b/NzbDrone.Host/Owin/NlogTextWriter.cs
@@ -1,12 +1,13 @@
 using System.IO;
 using System.Text;
 using NLog;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Host.Owin
 {
     public class NlogTextWriter : TextWriter
     {
-        private readonly Logger logger = LogManager.GetCurrentClassLogger();
+        private readonly Logger _logger = NzbDroneLogger.GetLogger();
 
 
         public override Encoding Encoding
@@ -17,25 +18,23 @@ namespace NzbDrone.Host.Owin
             }
         }
 
-        public override void Write(char value)
+        public override void Write(char[] buffer, int index, int count)
         {
-            logger.Trace(value);
+            Write(buffer);
         }
-
         public override void Write(char[] buffer)
         {
-            logger.Trace(buffer);
+            Write(new string(buffer));
         }
 
         public override void Write(string value)
         {
-            logger.Trace(value);
+            _logger.Trace(value);
         }
 
-        public override void Write(char[] buffer, int index, int count)
+        public override void Write(char value)
         {
-            logger.Trace(buffer);
+            _logger.Trace(value);
         }
-
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Host/Owin/OwinHostController.cs b/NzbDrone.Host/Owin/OwinHostController.cs
index 9968b22bb..b4d8d24ba 100644
--- a/NzbDrone.Host/Owin/OwinHostController.cs
+++ b/NzbDrone.Host/Owin/OwinHostController.cs
@@ -1,6 +1,8 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Net;
+using System.Reflection;
 using Microsoft.Owin.Hosting;
 using NLog;
 using NzbDrone.Common.EnvironmentInfo;
@@ -50,7 +52,26 @@ namespace NzbDrone.Host.Owin
 
             _logger.Info("starting server on {0}", _urlAclAdapter.UrlAcl);
 
-            _host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp);
+            try
+            {
+                _host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp);
+            }
+            catch (TargetInvocationException ex)
+            {
+                if (ex.InnerException == null)
+                {
+                    throw;
+                }
+
+                if (ex.InnerException is HttpListenerException)
+                {
+                    throw new PortInUseException("Port {0} is already in use, please ensure NzbDrone is not already running.",
+                             ex,
+                             _configFileProvider.Port);
+                }
+
+                throw ex.InnerException;
+            }
         }
 
         private void BuildApp(IAppBuilder appBuilder)
diff --git a/NzbDrone.Host/Owin/PortInUseException.cs b/NzbDrone.Host/Owin/PortInUseException.cs
new file mode 100644
index 000000000..5c6d7a542
--- /dev/null
+++ b/NzbDrone.Host/Owin/PortInUseException.cs
@@ -0,0 +1,12 @@
+using System;
+using NzbDrone.Common.Exceptions;
+
+namespace NzbDrone.Host.Owin
+{
+    public class PortInUseException : NzbDroneException
+    {
+        public PortInUseException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
+        {
+        }
+    }
+}
diff --git a/NzbDrone.Host/PlatformValidation.cs b/NzbDrone.Host/PlatformValidation.cs
index 978a579ae..703918aca 100644
--- a/NzbDrone.Host/PlatformValidation.cs
+++ b/NzbDrone.Host/PlatformValidation.cs
@@ -3,12 +3,13 @@ using System.Diagnostics;
 using System.Reflection;
 using NLog;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Host
 {
     public static class PlatformValidation
     {
-        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
 
         public static bool IsValidate(IUserAlert userAlert)
         {
diff --git a/NzbDrone.Integration.Test/Client/ClientBase.cs b/NzbDrone.Integration.Test/Client/ClientBase.cs
index 05ca16711..20e35117f 100644
--- a/NzbDrone.Integration.Test/Client/ClientBase.cs
+++ b/NzbDrone.Integration.Test/Client/ClientBase.cs
@@ -2,6 +2,7 @@
 using System.Net;
 using FluentAssertions;
 using NLog;
+using NzbDrone.Api;
 using NzbDrone.Api.REST;
 using NzbDrone.Common.Serializer;
 using RestSharp;
@@ -35,6 +36,17 @@ namespace NzbDrone.Integration.Test.Client
             return Get<List<TResource>>(request);
         }
 
+        public PagingResource<TResource> GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir)
+        {
+            var request = BuildRequest();
+            request.AddParameter("page", pageNumber);
+            request.AddParameter("pageSize", pageSize);
+            request.AddParameter("sortKey", sortKey);
+            request.AddParameter("sortDir", sortDir);
+            return Get<PagingResource<TResource>>(request);
+
+        }
+
         public TResource Post(TResource body)
         {
             var request = BuildRequest();
@@ -74,7 +86,7 @@ namespace NzbDrone.Integration.Test.Client
             return Post<List<dynamic>>(request, HttpStatusCode.BadRequest);
         }
 
-        protected RestRequest BuildRequest(string command = "")
+        public RestRequest BuildRequest(string command = "")
         {
             return new RestRequest(_resource + "/" + command.Trim('/'))
                 {
@@ -82,7 +94,7 @@ namespace NzbDrone.Integration.Test.Client
                 };
         }
 
-        protected T Get<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.OK) where T : class, new()
+        public T Get<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.OK) where T : class, new()
         {
             request.Method = Method.GET;
             return Execute<T>(request, statusCode);
diff --git a/NzbDrone.Integration.Test/Client/EpisodeClient.cs b/NzbDrone.Integration.Test/Client/EpisodeClient.cs
index 675c38bd8..8cc89af4e 100644
--- a/NzbDrone.Integration.Test/Client/EpisodeClient.cs
+++ b/NzbDrone.Integration.Test/Client/EpisodeClient.cs
@@ -1,7 +1,5 @@
 using System.Collections.Generic;
-using System.Net;
 using NzbDrone.Api.Episodes;
-using NzbDrone.Api.Series;
 using RestSharp;
 
 namespace NzbDrone.Integration.Test.Client
diff --git a/NzbDrone.Integration.Test/Client/SeasonClient.cs b/NzbDrone.Integration.Test/Client/SeasonClient.cs
deleted file mode 100644
index af0572a70..000000000
--- a/NzbDrone.Integration.Test/Client/SeasonClient.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Collections.Generic;
-using System.Net;
-using NzbDrone.Api.Episodes;
-using NzbDrone.Api.Seasons;
-using RestSharp;
-
-namespace NzbDrone.Integration.Test.Client
-{
-    public class SeasonClient : ClientBase<SeasonResource>
-    {
-        public SeasonClient(IRestClient restClient)
-            : base(restClient)
-        {
-        }
-
-        public List<SeasonResource> GetSeasonsInSeries(int seriesId)
-        {
-            var request = BuildRequest("?seriesId=" + seriesId.ToString());
-            return Get<List<SeasonResource>>(request);
-        }
-    }
-}
diff --git a/NzbDrone.Integration.Test/CommandIntegerationTests.cs b/NzbDrone.Integration.Test/CommandIntegerationTests.cs
index c1e06bac1..1dbe33b67 100644
--- a/NzbDrone.Integration.Test/CommandIntegerationTests.cs
+++ b/NzbDrone.Integration.Test/CommandIntegerationTests.cs
@@ -1,15 +1,40 @@
-using NUnit.Framework;
+using System.Net;
+using FluentAssertions;
+using NUnit.Framework;
 using NzbDrone.Api.Commands;
+using NzbDrone.Common.Serializer;
+using RestSharp;
 
 namespace NzbDrone.Integration.Test
 {
     [TestFixture]
+    [Ignore]
     public class CommandIntegrationTest : IntegrationTest
     {
         [Test]
         public void should_be_able_to_run_rss_sync()
         {
-            Commands.Post(new CommandResource {Command = "rsssync"});
+            var request = new RestRequest("command")
+            {
+                RequestFormat = DataFormat.Json,
+                Method = Method.POST
+            };
+
+            request.AddBody(new CommandResource { Name = "rsssync" });
+
+            var restClient = new RestClient("http://localhost:8989/api");
+            var response = restClient.Execute(request);
+
+            if (response.ErrorException != null)
+            {
+                throw response.ErrorException;
+            }
+
+            response.ErrorMessage.Should().BeBlank();
+            response.StatusCode.Should().Be(HttpStatusCode.Created);
+
+            var trackedCommand = Json.Deserialize<CommandResource>(response.Content);
+            trackedCommand.Id.Should().NotBe(0);
         }
     }
 }
\ No newline at end of file
diff --git a/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs b/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs
index f4d7fe8c1..681e510ef 100644
--- a/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs
+++ b/NzbDrone.Integration.Test/EpisodeIntegrationTests.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Threading;
+using System.Threading;
 using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Api.Series;
diff --git a/NzbDrone.Integration.Test/HistoryIntegrationTest.cs b/NzbDrone.Integration.Test/HistoryIntegrationTest.cs
new file mode 100644
index 000000000..8c5a6c1be
--- /dev/null
+++ b/NzbDrone.Integration.Test/HistoryIntegrationTest.cs
@@ -0,0 +1,21 @@
+using FluentAssertions;
+using NUnit.Framework;
+
+namespace NzbDrone.Integration.Test
+{
+    [TestFixture]
+    public class HistoryIntegrationTest : IntegrationTest
+    {
+        [Test]
+        public void history_should_be_empty()
+        {
+            var history = History.GetPaged(1, 15, "date", "desc");
+
+            history.Records.Count.Should().Be(0);
+            history.Page.Should().Be(1);
+            history.PageSize.Should().Be(15);
+            history.Records.Should().BeEmpty();
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Integration.Test/IntegrationTest.cs b/NzbDrone.Integration.Test/IntegrationTest.cs
index b858e4ff3..a0b5b3bcd 100644
--- a/NzbDrone.Integration.Test/IntegrationTest.cs
+++ b/NzbDrone.Integration.Test/IntegrationTest.cs
@@ -1,10 +1,10 @@
-using System.Runtime.CompilerServices;
-using NLog;
+using NLog;
 using NLog.Config;
 using NLog.Targets;
 using NUnit.Framework;
 using NzbDrone.Api.Commands;
 using NzbDrone.Api.Config;
+using NzbDrone.Api.History;
 using NzbDrone.Api.RootFolders;
 using NzbDrone.Common.EnvironmentInfo;
 using NzbDrone.Integration.Test.Client;
@@ -23,9 +23,9 @@ namespace NzbDrone.Integration.Test
         protected ClientBase<RootFolderResource> RootFolders;
         protected ClientBase<CommandResource> Commands;
         protected ReleaseClient Releases;
+        protected ClientBase<HistoryResource> History;
         protected IndexerClient Indexers;
         protected EpisodeClient Episodes;
-        protected SeasonClient Seasons;
         protected ClientBase<NamingConfigResource> NamingConfig;
 
         private NzbDroneRunner _runner;
@@ -59,9 +59,9 @@ namespace NzbDrone.Integration.Test
             Releases = new ReleaseClient(RestClient);
             RootFolders = new ClientBase<RootFolderResource>(RestClient);
             Commands = new ClientBase<CommandResource>(RestClient);
+            History = new ClientBase<HistoryResource>(RestClient);
             Indexers = new IndexerClient(RestClient);
             Episodes = new EpisodeClient(RestClient);
-            Seasons = new SeasonClient(RestClient);
             NamingConfig = new ClientBase<NamingConfigResource>(RestClient, "config/naming");
         }
 
@@ -72,5 +72,4 @@ namespace NzbDrone.Integration.Test
             _runner.KillAll();
         }
     }
-
 }
diff --git a/NzbDrone.Integration.Test/NamingConfigTests.cs b/NzbDrone.Integration.Test/NamingConfigTests.cs
index 573eaf2ed..07592193e 100644
--- a/NzbDrone.Integration.Test/NamingConfigTests.cs
+++ b/NzbDrone.Integration.Test/NamingConfigTests.cs
@@ -1,6 +1,4 @@
-using System;
-using System.IO;
-using FluentAssertions;
+using FluentAssertions;
 using NUnit.Framework;
 
 namespace NzbDrone.Integration.Test
diff --git a/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
index 5762f6877..9bba4d240 100644
--- a/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
+++ b/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
@@ -94,14 +94,13 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Client\ClientBase.cs" />
-    <Compile Include="Client\SeasonClient.cs" />
     <Compile Include="Client\EpisodeClient.cs" />
     <Compile Include="Client\IndexerClient.cs" />
     <Compile Include="Client\ReleaseClient.cs" />
     <Compile Include="Client\SeriesClient.cs" />
     <Compile Include="CommandIntegerationTests.cs" />
+    <Compile Include="HistoryIntegrationTest.cs" />
     <Compile Include="NamingConfigTests.cs" />
-    <Compile Include="SeasonIntegrationTests.cs" />
     <Compile Include="EpisodeIntegrationTests.cs" />
     <Compile Include="IndexerIntegrationFixture.cs" />
     <Compile Include="IntegrationTestDirectoryInfo.cs" />
diff --git a/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs b/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs
index 8af6f77e4..b9a86674e 100644
--- a/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs
+++ b/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs
@@ -22,7 +22,7 @@ namespace NzbDrone.Integration.Test
         {
             releaseResource.Age.Should().BeGreaterOrEqualTo(-1);
             releaseResource.Title.Should().NotBeBlank();
-            releaseResource.NzbUrl.Should().NotBeBlank();
+            releaseResource.DownloadUrl.Should().NotBeBlank();
             releaseResource.SeriesTitle.Should().NotBeBlank();
             //TODO: uncomment these after moving to restsharp for rss
             //releaseResource.NzbInfoUrl.Should().NotBeBlank();
diff --git a/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs b/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs
index 8a888fffa..0b7647fdf 100644
--- a/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs
+++ b/NzbDrone.Integration.Test/RootFolderIntegrationTest.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.IO;
 using FluentAssertions;
 using Microsoft.AspNet.SignalR.Client;
 using Microsoft.AspNet.SignalR.Client.Transports;
diff --git a/NzbDrone.Integration.Test/SeasonIntegrationTests.cs b/NzbDrone.Integration.Test/SeasonIntegrationTests.cs
deleted file mode 100644
index 7a2458b4f..000000000
--- a/NzbDrone.Integration.Test/SeasonIntegrationTests.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Threading;
-using FluentAssertions;
-using NUnit.Framework;
-using NzbDrone.Api.Series;
-using System.Linq;
-using NzbDrone.Test.Common;
-
-namespace NzbDrone.Integration.Test
-{
-    [TestFixture]
-    public class SeasonIntegrationTests : IntegrationTest
-    {
-        private SeriesResource GivenSeriesWithEpisodes()
-        {
-            var series = Series.Lookup("archer").First();
-
-            series.QualityProfileId = 1;
-            series.Path = @"C:\Test\Archer".AsOsAgnostic();
-
-            series = Series.Post(series);
-
-            while (true)
-            {
-                if (Seasons.GetSeasonsInSeries(series.Id).Count > 0)
-                {
-                    return series;
-                }
-
-                Thread.Sleep(1000);
-            }
-        }
-
-        [Test]
-        public void should_be_able_to_get_all_seasons_in_series()
-        {
-            var series = GivenSeriesWithEpisodes();
-            Seasons.GetSeasonsInSeries(series.Id).Count.Should().BeGreaterThan(0);
-        }
-
-        [Test]
-        public void should_be_able_to_get_a_single_season()
-        {
-            var series = GivenSeriesWithEpisodes();
-            var seasons = Seasons.GetSeasonsInSeries(series.Id);
-
-            Seasons.Get(seasons.First().Id).Should().NotBeNull();
-        }
-
-        [Test]
-        public void should_be_able_to_set_monitor_status_via_api()
-        {
-            var series = GivenSeriesWithEpisodes();
-            var seasons = Seasons.GetSeasonsInSeries(series.Id);
-            var updatedSeason = seasons.First();
-            updatedSeason.Monitored = false;
-
-            Seasons.Put(updatedSeason).Monitored.Should().BeFalse();
-        }
-    }
-}
diff --git a/NzbDrone.SignalR/BroadcastSignalRMessage.cs b/NzbDrone.SignalR/BroadcastSignalRMessage.cs
new file mode 100644
index 000000000..d989affc3
--- /dev/null
+++ b/NzbDrone.SignalR/BroadcastSignalRMessage.cs
@@ -0,0 +1,14 @@
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.SignalR
+{
+    public class BroadcastSignalRMessage : Command
+    {
+        public SignalRMessage Body { get; private set; }
+
+        public BroadcastSignalRMessage(SignalRMessage body)
+        {
+            Body = body;
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Api/SignalR/NoOpPerformanceCounterManager.cs b/NzbDrone.SignalR/NoOpPerformanceCounterManager.cs
similarity index 99%
rename from NzbDrone.Api/SignalR/NoOpPerformanceCounterManager.cs
rename to NzbDrone.SignalR/NoOpPerformanceCounterManager.cs
index a3e3d7c1f..2eec1b004 100644
--- a/NzbDrone.Api/SignalR/NoOpPerformanceCounterManager.cs
+++ b/NzbDrone.SignalR/NoOpPerformanceCounterManager.cs
@@ -2,7 +2,7 @@
 using System.Threading;
 using Microsoft.AspNet.SignalR.Infrastructure;
 
-namespace NzbDrone.Api.SignalR
+namespace NzbDrone.SignalR
 {
     public class NoOpPerformanceCounterManager : IPerformanceCounterManager
     {
diff --git a/NzbDrone.SignalR/NzbDrone.SignalR.csproj b/NzbDrone.SignalR/NzbDrone.SignalR.csproj
new file mode 100644
index 000000000..859651305
--- /dev/null
+++ b/NzbDrone.SignalR/NzbDrone.SignalR.csproj
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>NzbDrone.SignalR</RootNamespace>
+    <AssemblyName>NzbDrone.SignalR</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+    <RestorePackages>true</RestorePackages>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x86\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+    <OutputPath>bin\x86\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNet.SignalR.Core">
+      <HintPath>..\packages\Microsoft.AspNet.SignalR.Core.1.1.3\lib\net40\Microsoft.AspNet.SignalR.Core.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json">
+      <HintPath>..\packages\Newtonsoft.Json.5.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="Microsoft.CSharp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs">
+      <Link>Properties\SharedAssemblyInfo.cs</Link>
+    </Compile>
+    <Compile Include="BroadcastSignalRMessage.cs" />
+    <Compile Include="NoOpPerformanceCounterManager.cs" />
+    <Compile Include="NzbDronePersistentConnection.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Serializer.cs" />
+    <Compile Include="SignalrDependencyResolver.cs" />
+    <Compile Include="SignalRMessage.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\NzbDrone.Common\NzbDrone.Common.csproj">
+      <Project>{f2be0fdf-6e47-4827-a420-dd4ef82407f8}</Project>
+      <Name>NzbDrone.Common</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\NzbDrone.Core\NzbDrone.Core.csproj">
+      <Project>{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}</Project>
+      <Name>NzbDrone.Core</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file
diff --git a/NzbDrone.SignalR/NzbDronePersistentConnection.cs b/NzbDrone.SignalR/NzbDronePersistentConnection.cs
new file mode 100644
index 000000000..b9a6f1eed
--- /dev/null
+++ b/NzbDrone.SignalR/NzbDronePersistentConnection.cs
@@ -0,0 +1,24 @@
+using Microsoft.AspNet.SignalR;
+using Microsoft.AspNet.SignalR.Infrastructure;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.SignalR
+{
+    public sealed class NzbDronePersistentConnection : PersistentConnection, IExecute<BroadcastSignalRMessage>
+    {
+        private IPersistentConnectionContext Context
+        {
+            get
+            {
+                return ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType());
+            }
+        }
+
+
+        public void Execute(BroadcastSignalRMessage message)
+        {
+            Context.Connection.Broadcast(message.Body);
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.SignalR/Properties/AssemblyInfo.cs b/NzbDrone.SignalR/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..ffaa67fdd
--- /dev/null
+++ b/NzbDrone.SignalR/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("NzbDrone.SignalR")]
+[assembly: Guid("98bd985a-4f23-4201-8ed3-f6f3d7f2a5fe")]
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/NzbDrone.Api/SignalR/Serializer.cs b/NzbDrone.SignalR/Serializer.cs
similarity index 95%
rename from NzbDrone.Api/SignalR/Serializer.cs
rename to NzbDrone.SignalR/Serializer.cs
index 4ab2bfc01..e631ef146 100644
--- a/NzbDrone.Api/SignalR/Serializer.cs
+++ b/NzbDrone.SignalR/Serializer.cs
@@ -3,7 +3,7 @@ using System.IO;
 using Microsoft.AspNet.SignalR.Json;
 using NzbDrone.Common.Serializer;
 
-namespace NzbDrone.Api.SignalR
+namespace NzbDrone.SignalR
 {
     public class Serializer : IJsonSerializer
     {
diff --git a/NzbDrone.SignalR/SignalRMessage.cs b/NzbDrone.SignalR/SignalRMessage.cs
new file mode 100644
index 000000000..e8993c286
--- /dev/null
+++ b/NzbDrone.SignalR/SignalRMessage.cs
@@ -0,0 +1,8 @@
+namespace NzbDrone.SignalR
+{
+    public class SignalRMessage
+    {
+        public object Body { get; set; }
+        public string Name { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Api/SignalR/SignalrDependencyResolver.cs b/NzbDrone.SignalR/SignalrDependencyResolver.cs
similarity index 96%
rename from NzbDrone.Api/SignalR/SignalrDependencyResolver.cs
rename to NzbDrone.SignalR/SignalrDependencyResolver.cs
index f95f9ae5a..a3633cbae 100644
--- a/NzbDrone.Api/SignalR/SignalrDependencyResolver.cs
+++ b/NzbDrone.SignalR/SignalrDependencyResolver.cs
@@ -3,7 +3,7 @@ using Microsoft.AspNet.SignalR;
 using Microsoft.AspNet.SignalR.Infrastructure;
 using NzbDrone.Common.Composition;
 
-namespace NzbDrone.Api.SignalR
+namespace NzbDrone.SignalR
 {
     public class SignalrDependencyResolver : DefaultDependencyResolver
     {
diff --git a/NzbDrone.SignalR/packages.config b/NzbDrone.SignalR/packages.config
new file mode 100644
index 000000000..f82b57a29
--- /dev/null
+++ b/NzbDrone.SignalR/packages.config
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.AspNet.SignalR.Core" version="1.1.3" targetFramework="net40" />
+  <package id="Newtonsoft.Json" version="5.0.6" targetFramework="net40" />
+</packages>
\ No newline at end of file
diff --git a/NzbDrone.Test.Common/Categories/DiskAccessTestAttribute.cs b/NzbDrone.Test.Common/Categories/DiskAccessTestAttribute.cs
new file mode 100644
index 000000000..faccff30d
--- /dev/null
+++ b/NzbDrone.Test.Common/Categories/DiskAccessTestAttribute.cs
@@ -0,0 +1,13 @@
+using NUnit.Framework;
+
+namespace NzbDrone.Test.Common.Categories
+{
+    public class DiskAccessTestAttribute : CategoryAttribute
+    {
+        public DiskAccessTestAttribute()
+            : base("DiskAccessTest")
+        {
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj b/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj
index 4e85ec162..dd6ffc0fe 100644
--- a/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj
+++ b/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj
@@ -76,6 +76,7 @@
     <Compile Include="AutoMoq\AutoMoqer.cs" />
     <Compile Include="AutoMoq\Unity\AutoMockingBuilderStrategy.cs" />
     <Compile Include="AutoMoq\Unity\AutoMockingContainerExtension.cs" />
+    <Compile Include="Categories\DiskAccessTestAttribute.cs" />
     <Compile Include="ConcurrencyCounter.cs" />
     <Compile Include="ExceptionVerification.cs" />
     <Compile Include="Categories\IntegrationTestAttribute.cs" />
diff --git a/NzbDrone.Test.Common/NzbDrone.Test.Common.ncrunchproject b/NzbDrone.Test.Common/NzbDrone.Test.Common.ncrunchproject
index 8641d3614..1a2228e7f 100644
--- a/NzbDrone.Test.Common/NzbDrone.Test.Common.ncrunchproject
+++ b/NzbDrone.Test.Common/NzbDrone.Test.Common.ncrunchproject
@@ -1,8 +1,8 @@
 <ProjectConfiguration>
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
-  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,8 +12,11 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration />
-  <ProxyProcessPath />
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.Test.Common/TestBase.cs b/NzbDrone.Test.Common/TestBase.cs
index 94521c8d5..82e73ab4d 100644
--- a/NzbDrone.Test.Common/TestBase.cs
+++ b/NzbDrone.Test.Common/TestBase.cs
@@ -1,5 +1,6 @@
 using System;
 using System.IO;
+using System.Threading;
 using FluentAssertions;
 using Moq;
 using NLog;
@@ -8,6 +9,8 @@ using NzbDrone.Common;
 using NzbDrone.Common.Cache;
 using NzbDrone.Common.EnvironmentInfo;
 using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Messaging;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Test.Common.AutoMoq;
 
 namespace NzbDrone.Test.Common
@@ -42,6 +45,9 @@ namespace NzbDrone.Test.Common
 
     public abstract class TestBase : LoggingTest
     {
+
+        private static readonly Random _random = new Random();
+
         private AutoMoqer _mocker;
         protected AutoMoqer Mocker
         {
@@ -58,6 +64,15 @@ namespace NzbDrone.Test.Common
 
         protected Mock<RestProvider> MockedRestProvider { get; private set; }
 
+        protected int RandomNumber
+        {
+            get
+            {
+                Thread.Sleep(1);
+                return _random.Next(0, int.MaxValue);
+            }
+        }
+
         private string VirtualPath
         {
             get
@@ -143,6 +158,11 @@ namespace NzbDrone.Test.Common
             return Path.Combine(SandboxFolder, fileName);
         }
 
+        protected string GetTestFilePath()
+        {
+            return GetTestFilePath(Path.GetRandomFileName());
+        }
+
         protected string SandboxFolder
         {
             get
@@ -160,12 +180,12 @@ namespace NzbDrone.Test.Common
 
         protected void VerifyEventPublished<TEvent>(Times times) where TEvent : class, IEvent
         {
-            Mocker.GetMock<IMessageAggregator>().Verify(c => c.PublishEvent(It.IsAny<TEvent>()), times);
+            Mocker.GetMock<IEventAggregator>().Verify(c => c.PublishEvent(It.IsAny<TEvent>()), times);
         }
 
         protected void VerifyEventNotPublished<TEvent>() where TEvent : class, IEvent
         {
-            Mocker.GetMock<IMessageAggregator>().Verify(c => c.PublishEvent(It.IsAny<TEvent>()), Times.Never());
+            Mocker.GetMock<IEventAggregator>().Verify(c => c.PublishEvent(It.IsAny<TEvent>()), Times.Never());
         }
     }
 }
diff --git a/NzbDrone.Test.Dummy/NzbDrone.Test.Dummy.ncrunchproject b/NzbDrone.Test.Dummy/NzbDrone.Test.Dummy.ncrunchproject
index 8641d3614..1a2228e7f 100644
--- a/NzbDrone.Test.Dummy/NzbDrone.Test.Dummy.ncrunchproject
+++ b/NzbDrone.Test.Dummy/NzbDrone.Test.Dummy.ncrunchproject
@@ -1,8 +1,8 @@
 <ProjectConfiguration>
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
-  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,8 +12,11 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration />
-  <ProxyProcessPath />
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.Update.Test/NzbDrone.Update.Test.ncrunchproject b/NzbDrone.Update.Test/NzbDrone.Update.Test.ncrunchproject
index 514d01a85..1a2228e7f 100644
--- a/NzbDrone.Update.Test/NzbDrone.Update.Test.ncrunchproject
+++ b/NzbDrone.Update.Test/NzbDrone.Update.Test.ncrunchproject
@@ -2,7 +2,7 @@
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
   <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,11 +12,11 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration></UseBuildConfiguration>
-  <UseBuildPlatform />
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
   <ProxyProcessPath></ProxyProcessPath>
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.Update/NLog.config b/NzbDrone.Update/NLog.config
deleted file mode 100644
index 8398980c9..000000000
--- a/NzbDrone.Update/NLog.config
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
-      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-  <extensions>
-    <add assembly="NzbDrone.Common"/>
-  </extensions>
-  <targets>
-    <target xsi:type="ColoredConsole" name="consoleLogger" layout="[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"/>
-    <target xsi:type="File" name="fileLogger" fileName="${updateLog}" autoFlush="true" keepFileOpen="false"
-           concurrentWrites="false" concurrentWriteAttemptDelay="50" concurrentWriteAttempts ="10"
-            layout="${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"/>
-  </targets>
-  <rules>
-    <logger name="*" minlevel="Debug" writeTo="consoleLogger"/>
-    <logger name="*" minlevel="Trace" writeTo="fileLogger"/>
-  </rules>
-</nlog>
\ No newline at end of file
diff --git a/NzbDrone.Update/NzbDrone.Update.csproj b/NzbDrone.Update/NzbDrone.Update.csproj
index c2d12c5c4..a1d42c2e0 100644
--- a/NzbDrone.Update/NzbDrone.Update.csproj
+++ b/NzbDrone.Update/NzbDrone.Update.csproj
@@ -63,10 +63,6 @@
   </ItemGroup>
   <ItemGroup>
     <None Include="app.config" />
-    <Content Include="NLog.config">
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
-      <SubType>Designer</SubType>
-    </Content>
     <None Include="NLog.xsd">
       <SubType>Designer</SubType>
     </None>
diff --git a/NzbDrone.Update/NzbDrone.Update.ncrunchproject b/NzbDrone.Update/NzbDrone.Update.ncrunchproject
index 8641d3614..1a2228e7f 100644
--- a/NzbDrone.Update/NzbDrone.Update.ncrunchproject
+++ b/NzbDrone.Update/NzbDrone.Update.ncrunchproject
@@ -1,8 +1,8 @@
 <ProjectConfiguration>
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
-  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,8 +12,11 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration />
-  <ProxyProcessPath />
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.Update/UpdateApp.cs b/NzbDrone.Update/UpdateApp.cs
index 167499373..3a882cd74 100644
--- a/NzbDrone.Update/UpdateApp.cs
+++ b/NzbDrone.Update/UpdateApp.cs
@@ -16,7 +16,7 @@ namespace NzbDrone.Update
         private readonly IProcessProvider _processProvider;
         private static IContainer _container;
 
-        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
+        private static readonly Logger logger =  NzbDroneLogger.GetLogger();
 
         public UpdateApp(IInstallUpdateService installUpdateService, IProcessProvider processProvider)
         {
@@ -28,14 +28,16 @@ namespace NzbDrone.Update
         {
             try
             {
+                var startupArgument = new StartupArguments(args);
+                LogTargets.Register(startupArgument, true, true);
+
                 Console.WriteLine("Starting NzbDrone Update Client");
 
                 IgnoreCertErrorPolicy.Register();
 
                 GlobalExceptionHandlers.Register();
 
-                new LogglyTarget().Register(LogLevel.Trace);
-                _container = UpdateContainerBuilder.Build(new StartupArguments(args));
+                _container = UpdateContainerBuilder.Build(startupArgument);
 
                 logger.Info("Updating NzbDrone to version {0}", BuildInfo.Version);
                 _container.Resolve<UpdateApp>().Start(args);
diff --git a/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs b/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs
index 78c3b8c73..91552105c 100644
--- a/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs
+++ b/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs
@@ -31,7 +31,6 @@ namespace NzbDrone.Update.UpdateEngine
 
         public void Restore(string target)
         {
-            //TODO:this should ignore single file failures.
             _logger.Info("Attempting to rollback upgrade");
             _diskProvider.CopyFolder(_appFolderInfo.GetUpdateBackUpFolder(), target);
         }
diff --git a/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
index ffafff691..0323360cc 100644
--- a/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
+++ b/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
@@ -64,6 +64,7 @@ namespace NzbDrone.Update.UpdateEngine
 
                 try
                 {
+                    _diskProvider.EmptyFolder(installationFolder);
                     _diskProvider.CopyFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder);
                 }
                 catch (Exception e)
diff --git a/NzbDrone.Wix/configuration.xml b/NzbDrone.Wix/configuration.xml
new file mode 100644
index 000000000..fdf4f44bf
--- /dev/null
+++ b/NzbDrone.Wix/configuration.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configurations lcid_type="UserExe" show_language_selector="False" language_selector_title="" language_selector_ok="OK" language_selector_cancel="Cancel" configuration_no_match_message="" ui_level="basic" fileversion="" productversion="" log_enabled="True" log_file="#TEMPPATH\dotNetInstallerLog.txt">
+  <schema version="2.2.824.0" generator="dotNetInstaller InstallerEditor" />
+  <configuration dialog_caption="SyntikX Installer" dialog_message="Following components need to be installed" dialog_message_uninstall="" dialog_bitmap="#APPPATH\banner.bmp" skip_caption="Skip" install_caption="Install" uninstall_caption="Uninstall" cancel_caption="Close" status_installed=" (Already Installed)" status_notinstalled="" failed_exec_command_continue="Failed to install %s." installation_completed="SyntikX installed successfully!" uninstallation_completed="SyntikX uninstalled successfully!" installation_none="SyntikX is not compatible with your machine. Please contact support for more information." uninstallation_none="SyntikX is not installed!" installing_component_wait="Installing %s. Wait, this operation could take some time ..." uninstalling_component_wait="Uninstalling %s. Wait, this operation could take some time ..." reboot_required="To continue the installation you must restart your computer. Restart now?" must_reboot_required="False" dialog_otherinfo_caption="" dialog_otherinfo_link="" complete_command="" complete_command_silent="" complete_command_basic="" wait_for_complete_command="True" prompt_for_optional_components="False" auto_close_if_installed="False" auto_close_on_error="False" reload_on_error="True" dialog_show_installed="True" dialog_show_uninstalled="False" dialog_show_required="True" cab_dialog_message="%s" cab_cancelled_message="" cab_dialog_caption="" cab_path="#TEMPPATH\#GUID" cab_path_autodelete="True" dialog_default_button="install" dialog_position="" dialog_components_list_position="" dialog_message_position="" dialog_bitmap_position="" dialog_otherinfo_link_position="" dialog_osinfo_position="" dialog_install_button_position="" dialog_cancel_button_position="" dialog_skip_button_position="" auto_start="True" auto_continue_on_reboot="True" reboot_cmd="" show_progress_dialog="False" show_cab_dialog="True" disable_wow64_fs_redirection="False" administrator_required="False" administrator_required_message="SyntikX installation requires administration rights." type="install" lcid_filter="" language_id="" language="" os_filter="" os_filter_min="" os_filter_max="" processor_architecture_filter="" supports_install="True" supports_uninstall="False">
+    <component executable="#TEMPPATH\syntik_bootstrap\WindowsXP-KB936929-SP3-x86-ENU.exe" executable_silent="" executable_basic="" install_directory="" responsefile_source="" responsefile_target="" responsefile_format="none" uninstall_executable="" uninstall_executable_silent="" uninstall_executable_basic="" uninstall_responsefile_source="" uninstall_responsefile_target="" returncodes_success="" returncodes_reboot="" exeparameters="" exeparameters_basic="" exeparameters_silent="" uninstall_exeparameters="" uninstall_exeparameters_basic="" uninstall_exeparameters_silent="" disable_wow64_fs_redirection="False" id="XPSP3" display_name="Windows XP Service Pack 3" uninstall_display_name="" os_filter="" os_filter_min="winXPsp1" os_filter_max="winXPsp2" os_filter_lcid="" type="exe" installcompletemessage="" uninstallcompletemessage="" mustreboot="False" reboot_required="" must_reboot_required="False" failed_exec_command_continue="" allow_continue_on_error="False" default_continue_on_error="False" required_install="True" required_uninstall="False" selected_install="True" selected_uninstall="True" note="Windows XP Service Pack 3" processor_architecture_filter="" status_installed="" status_notinstalled="" supports_install="True" supports_uninstall="False" show_progress_dialog="True" show_cab_dialog="True">
+      <downloaddialog dialog_caption="Download Windows XP Service Pack 3" dialog_message="SyntikX requires Windows XP Service Pack 3 or latter. Press start to automatically download and install this update." dialog_message_downloading="Downloading ..." dialog_message_copying="Copying ..." dialog_message_connecting="Connecting ..." dialog_message_sendingrequest="Sending request ..." autostartdownload="False" buttonstart_caption="Start" buttoncancel_caption="Cancel">
+        <download componentname="Windows XP Service Pack 3" sourceurl="http://download.microsoft.com/download/d/3/0/d30e32d8-418a-469d-b600-f32ce3edf42d/WindowsXP-KB936929-SP3-x86-ENU.exe" sourcepath="" destinationpath="#TEMPPATH\syntik_bootstrap\" destinationfilename="" alwaysdownload="False" clear_cache="False" />
+      </downloaddialog>
+    </component>
+    <component command="#TEMPPATH\syntik_bootstrap\dotNetFx40_Full_setup.exe /passive" command_silent="" command_basic="" uninstall_command="" uninstall_command_silent="" uninstall_command_basic="" returncodes_success="" returncodes_reboot="3010" disable_wow64_fs_redirection="False" id="Microsoft .NET Framework 4.0 - Full" display_name="Microsoft .NET Framework 4.0 " uninstall_display_name="" os_filter="" os_filter_min="winXPsp3" os_filter_max="" os_filter_lcid="" type="cmd" installcompletemessage="" uninstallcompletemessage="" mustreboot="False" reboot_required="" must_reboot_required="False" failed_exec_command_continue="" allow_continue_on_error="False" default_continue_on_error="False" required_install="True" required_uninstall="False" selected_install="True" selected_uninstall="False" note="English - WebSetup - .NET Framework 4.0 - Full for all operating system since Windows XP SP3 (Install check)" processor_architecture_filter="" status_installed="" status_notinstalled="" supports_install="True" supports_uninstall="False" show_progress_dialog="True" show_cab_dialog="True">
+      <installedcheck path="SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" fieldname="Install" fieldvalue="1" defaultvalue="False" fieldtype="REG_DWORD" comparison="match" rootkey="HKEY_LOCAL_MACHINE" wowoption="NONE" type="check_registry_value" description="Installed Check" />
+      <downloaddialog dialog_caption="Microsoft .NET Framework 4.0" dialog_message="Press 'Start' to download and install Microsoft .NET Framework 4.0 - Full" dialog_message_downloading="Download in progress. Please wait..." dialog_message_copying="Files are downloaded. Please wait ..." dialog_message_connecting="Connecting ..." dialog_message_sendingrequest="Sending request ..." autostartdownload="True" buttonstart_caption="Start" buttoncancel_caption="Cancel">
+        <download componentname="Microsoft .NET Framework 4.0" sourceurl="http://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe" sourcepath="" destinationpath="#TEMPPATH\syntik_bootstrap\" destinationfilename="" alwaysdownload="True" clear_cache="True" />
+      </downloaddialog>
+    </component>
+    <component package="#TEMPPATH\syntik_update\syntikx.msi" cmdparameters="" cmdparameters_silent="" cmdparameters_basic="" uninstall_package="" uninstall_cmdparameters="/qb-" uninstall_cmdparameters_silent="/qn" uninstall_cmdparameters_basic="/qb-" disable_wow64_fs_redirection="False" id="SyntikX" display_name="SyntikX" uninstall_display_name="" os_filter="" os_filter_min="winXPsp3" os_filter_max="" os_filter_lcid="" type="msi" installcompletemessage="" uninstallcompletemessage="" mustreboot="False" reboot_required="" must_reboot_required="False" failed_exec_command_continue="" allow_continue_on_error="False" default_continue_on_error="False" required_install="True" required_uninstall="True" selected_install="True" selected_uninstall="True" note="" processor_architecture_filter="" status_installed="" status_notinstalled="" supports_install="True" supports_uninstall="True" show_progress_dialog="True" show_cab_dialog="True">
+      <downloaddialog dialog_caption="Downloading latest version of SyntikX" dialog_message="Press 'Start' to install latest version of SyntikX" dialog_message_downloading="Downloading ..." dialog_message_copying="Copying ..." dialog_message_connecting="Connecting ..." dialog_message_sendingrequest="Sending request ..." autostartdownload="True" buttonstart_caption="Start" buttoncancel_caption="Cancel">
+        <download componentname="Download Setup" sourceurl="http://localhost:59330/v1/update" sourcepath="" destinationpath="#TEMPPATH\syntik_update\" destinationfilename="syntikx.msi" alwaysdownload="True" clear_cache="True" />
+      </downloaddialog>
+    </component>
+  </configuration>
+</configurations>
\ No newline at end of file
diff --git a/NzbDrone.Wix/dotNetInstaller.exe b/NzbDrone.Wix/dotNetInstaller.exe
new file mode 100644
index 000000000..a0dd60f8b
Binary files /dev/null and b/NzbDrone.Wix/dotNetInstaller.exe differ
diff --git a/NzbDrone.Wix/nzbdrone.wix.build.bat b/NzbDrone.Wix/nzbdrone.wix.build.bat
new file mode 100644
index 000000000..f031916c2
--- /dev/null
+++ b/NzbDrone.Wix/nzbdrone.wix.build.bat
@@ -0,0 +1,8 @@
+rd _raw  /s /q
+rd _setup /s /q
+xcopy  ..\SyntikX.Client\bin\release\*.*  _raw\ /S /V /I /F /R
+
+"C:\Program Files (x86)\WiX Toolset v3.6\bin\candle.exe" -nologo "syntik.wix.build.wxs" -out "_setup\SyntikX.Wix.wixobj"  -ext WixNetFxExtension -ext WixUIExtension
+"C:\Program Files (x86)\WiX Toolset v3.6\bin\light.exe" -nologo "_setup\SyntikX.Wix.wixobj" -out "_setup\SyntikX.msi"  -ext WixNetFxExtension -ext WixUIExtension
+
+pause
\ No newline at end of file
diff --git a/NzbDrone.Wix/nzbdrone.wix.build.debug.bat b/NzbDrone.Wix/nzbdrone.wix.build.debug.bat
new file mode 100644
index 000000000..abf84ccaf
--- /dev/null
+++ b/NzbDrone.Wix/nzbdrone.wix.build.debug.bat
@@ -0,0 +1,10 @@
+rd _raw  /s /q
+rd _setup /s /q
+xcopy  ..\SyntikX.Client\bin\debug\*.*  _raw\ /S /V /I /F /R
+
+SET BUILD_NUMBER=1.9.9.9
+
+"C:\Program Files (x86)\WiX Toolset v3.6\bin\candle.exe" -nologo "syntik.wix.build.wxs" -out "_setup\SyntikX.Wix.wixobj"  -ext WixNetFxExtension -ext WixUIExtension
+"C:\Program Files (x86)\WiX Toolset v3.6\bin\light.exe" -nologo "_setup\SyntikX.Wix.wixobj" -out "_setup\SyntikX.Wix.msi"  -ext WixNetFxExtension -ext WixUIExtension
+
+pause
\ No newline at end of file
diff --git a/NzbDrone.Wix/nzbdrone.wix.build.wxs b/NzbDrone.Wix/nzbdrone.wix.build.wxs
new file mode 100644
index 000000000..36284be23
--- /dev/null
+++ b/NzbDrone.Wix/nzbdrone.wix.build.wxs
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+  <Product Id="*" Name="NzbDrone" Language="1033" Version="$(env.BUILD_NUMBER)" Manufacturer="NzbDrone Team" UpgradeCode="56833D74-A480-4CA2-B562-5A018B3A0F99">
+    <Package Description="NzbDrone"
+             Comments="NzbDrone"
+             InstallerVersion="200"
+             Compressed="yes"
+             InstallPrivileges="limited"
+             InstallScope="perUser"
+             Platform="x86" 
+             Manufacturer="NzbDrone Team"
+             />
+    <Media Id="1" Cabinet="NzbDrone.cab" EmbedCab="yes" CompressionLevel="high"/>
+    <Property Id="ARPPRODUCTICON" Value="ND_ICON" />
+    <Directory Id="TARGETDIR" Name="SourceDir">
+      <Directory Id="LocalAppDataFolder" Name="AppDataFolder">
+        <Directory Name="NzbDrone" Id="SX_ROOT">
+          <Component Id="SX_APP_COMP" DiskId="1" Guid="9c3ac309-cde4-4338-be75-a914cbce2601">
+            <File Id="SX_EXE_FILE" Name="NzbDrone.Client.exe" Source="_raw\NzbDrone.Client.exe" />
+            <File Id="AUTOFAC.CONFIGURATION.DLL" Name="Autofac.Configuration.dll" Source="_raw\Autofac.Configuration.dll" />
+            <File Id="AUTOFAC.DLL" Name="Autofac.dll" Source="_raw\Autofac.dll" />
+            <File Id="BCRYPT.NET.DLL" Name="BCrypt.Net.dll" Source="_raw\BCrypt.Net.dll" />
+            <File Id="CALIBURN.MICRO.DLL" Name="Caliburn.Micro.dll" Source="_raw\Caliburn.Micro.dll" />
+            <File Id="ICSHARPCODE.SHARPZIPLIB.DLL" Name="ICSharpCode.SharpZipLib.dll" Source="_raw\ICSharpCode.SharpZipLib.dll" />
+            <File Id="MAHAPPS.METRO.DLL" Name="MahApps.Metro.dll" Source="_raw\MahApps.Metro.dll" />
+            <File Id="NEWTONSOFT.JSON.DLL" Name="Newtonsoft.Json.dll" Source="_raw\Newtonsoft.Json.dll" />
+            <File Id="NLOG.DLL" Name="NLog.dll" Source="_raw\NLog.dll" />
+            <File Id="RESTSHARP.DLL" Name="RestSharp.dll" Source="_raw\RestSharp.dll" />
+            <File Id="EXCEPTRON.CLIENT.DLL" Name="exceptron.client.dll" Source="_raw\exceptron.client.dll" />
+            <File Id="EXCEPTRON.NLOG.DLL" Name="exceptron.nlog.dll" Source="_raw\exceptron.nlog.dll" />
+            <File Id="NzbDrone.CLIENT.CORE.DLL" Name="NzbDrone.Client.Core.dll" Source="_raw\NzbDrone.Client.Core.dll" />
+            <File Id="NzbDrone.CLIENT.CORE.PDB" Name="NzbDrone.Client.Core.pdb" Source="_raw\NzbDrone.Client.Core.pdb" />
+            <File Id="NzbDrone.CLIENT.EXE.CONFIG" Name="NzbDrone.Client.exe.config" Source="_raw\NzbDrone.Client.exe.config" />
+            <File Id="NzbDrone.CLIENT.PDB" Name="NzbDrone.Client.pdb" Source="_raw\NzbDrone.Client.pdb" />
+            <File Id="NzbDrone.SHARED.DLL" Name="NzbDrone.Shared.dll" Source="_raw\NzbDrone.Shared.dll" />
+            <File Id="NzbDrone.SHARED.PDB" Name="NzbDrone.Shared.pdb" Source="_raw\NzbDrone.Shared.pdb" />
+            <File Id="SYSTEM.WINDOWS.INTERACTIVITY.DLL" Name="System.Windows.Interactivity.dll" Source="_raw\System.Windows.Interactivity.dll" />
+            <RegistryValue Root="HKCU" Key="Software\Microsoft\NzbDrone" Name="installed" Type="integer" Value="1" KeyPath="yes" />
+            <RemoveFolder Id="SX_ROOT" On="uninstall" />
+          </Component>
+        </Directory>
+      </Directory>
+      <Directory Id="DesktopFolder">
+        <Component Id="SX_DESKTOP_SHORTCUT_COMP" Guid="1f395635-7a9d-454d-aab4-95a5a4e70be4">
+          <Shortcut Id="SX_DESKTOP_SHORTCUT" Name="NzbDrone" Description="NzbDrone Backup Client" Target="[SX_ROOT]NzbDrone.Client.exe" WorkingDirectory="SX_ROOT" />
+          <RegistryValue Root="HKCU" Key="Software\Microsoft\NzbDrone" Name="installed" Type="integer" Value="1" KeyPath="yes" />
+        </Component>
+      </Directory>
+      <Directory Id="ProgramMenuFolder">
+        <Directory Id="SX_START_DIR" Name="NzbDrone">
+          <Component Id="SX_START_SHORTCUT_COMP" Guid="8b3d54c6-712b-4bc2-b1e9-7cf40bcc1344">
+            <Shortcut Id="SX_START_SHORTCUT" Name="NzbDrone" Description="NzbDrone Backup Client" Target="[SX_ROOT]NzbDrone.Client.exe" WorkingDirectory="SX_ROOT" />
+            <RemoveFolder Id="SX_START_DIR" On="uninstall" />
+            <RegistryValue Root="HKCU" Key="Software\Microsoft\NzbDrone" Name="installed" Type="integer" Value="1" KeyPath="yes" />
+          </Component>
+        </Directory>
+        <Directory Id="StartupFolder" Name="Startup">
+          <Component Id="SX_STARTUP_SHORTCUT_COMP" Guid="0fdfe510-621e-4925-a0d4-395617fb7cbc">
+            <Shortcut Id="SX_STARTUP_SHORTCUT" Name="NzbDrone" Description="NzbDrone Backup Client" Target="[SX_ROOT]NzbDrone.Client.exe" Arguments="/startup" WorkingDirectory="SX_ROOT" />
+            <RegistryValue Root="HKCU" Key="Software\Microsoft\NzbDrone" Name="installed" Type="integer" Value="1" KeyPath="yes" />
+          </Component>
+        </Directory>
+      </Directory>
+    </Directory>
+    <!--<UIRef Id="WixUI_Minimal" />-->
+    <UI />
+    <MajorUpgrade AllowDowngrades="no" AllowSameVersionUpgrades="yes" MigrateFeatures="yes" Schedule="afterInstallInitialize" DowngradeErrorMessage="Newer version of NzbDrone is already installed." />
+    <Feature Id="DefaultFeature" Title="Main Feature" Level="1">
+      <ComponentRef Id="SX_APP_COMP" />
+      <ComponentRef Id="SX_START_SHORTCUT_COMP" />
+      <ComponentRef Id="SX_STARTUP_SHORTCUT_COMP" />
+      <ComponentRef Id="SX_DESKTOP_SHORTCUT_COMP" />
+    </Feature>
+    <PropertyRef Id="NETFRAMEWORK40FULL" />
+    <Condition Message="This application requires .NET Framework 4.0 or later. Please install the .NET Framework then run this installer again.">NETFRAMEWORK40FULL</Condition>
+    <Icon Id="SX_ICON" SourceFile="_raw\NzbDrone.Client.exe" />
+    <InstallExecuteSequence>
+      <InstallInitialize/>
+      <Custom Action="SX_START_ACTION" After="InstallFiles" />
+      <InstallFinalize/>
+    </InstallExecuteSequence>
+    <CustomAction Id="SX_START_ACTION" FileKey="SX_EXE_FILE" ExeCommand="" Execute="deferred" Impersonate="no" Return="asyncNoWait" />
+  </Product>
+</Wix>
\ No newline at end of file
diff --git a/NzbDrone.ncrunchsolution b/NzbDrone.ncrunchsolution
index 6cb47a29a..098d74afe 100644
--- a/NzbDrone.ncrunchsolution
+++ b/NzbDrone.ncrunchsolution
@@ -1,12 +1,13 @@
 <SolutionConfiguration>
   <FileVersion>1</FileVersion>
-  <AutoEnableOnStartup>False</AutoEnableOnStartup>
+  <AutoEnableOnStartup>True</AutoEnableOnStartup>
   <AllowParallelTestExecution>true</AllowParallelTestExecution>
+  <AllowTestsToRunInParallelWithThemselves>true</AllowTestsToRunInParallelWithThemselves>
   <FrameworkUtilisationTypeForNUnit>UseDynamicAnalysis</FrameworkUtilisationTypeForNUnit>
   <FrameworkUtilisationTypeForGallio>Disabled</FrameworkUtilisationTypeForGallio>
   <FrameworkUtilisationTypeForMSpec>Disabled</FrameworkUtilisationTypeForMSpec>
   <FrameworkUtilisationTypeForMSTest>Disabled</FrameworkUtilisationTypeForMSTest>
-  <EngineModes>Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk</EngineModes>
+  <EngineModes>Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk;Fast:DlN0cnVjdHVyYWxOb2RlBAAAABNEb2VzTm90SGF2ZUNhdGVnb3J5D0ludGVncmF0aW9uVGVzdBNEb2VzTm90SGF2ZUNhdGVnb3J5BkRiVGVzdApJc0ltcGFjdGVkE0RvZXNOb3RIYXZlQ2F0ZWdvcnkORGlza0FjY2Vzc1Rlc3QAAAAAAAAAAAAAAAA=</EngineModes>
   <MetricsExclusionList>
 </MetricsExclusionList>
 </SolutionConfiguration>
\ No newline at end of file
diff --git a/NzbDrone.sln b/NzbDrone.sln
index 4fa697bac..f118155ff 100644
--- a/NzbDrone.sln
+++ b/NzbDrone.sln
@@ -56,92 +56,228 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Host", "NzbDrone.H
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Host", "Host", "{486ADF86-DD89-4E19-B805-9D94F19800D9}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.SignalR", "NzbDrone.SignalR\NzbDrone.SignalR.csproj", "{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|Mixed Platforms = Debug|Mixed Platforms
 		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|Mixed Platforms = Release|Mixed Platforms
 		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|x86.ActiveCfg = Debug|x86
 		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Debug|x86.Build.0 = Debug|x86
+		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|Any CPU.ActiveCfg = Release|x86
+		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|Mixed Platforms.Build.0 = Release|x86
 		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|x86.ActiveCfg = Release|x86
 		{D12F7F2F-8A3C-415F-88FA-6DD061A84869}.Release|x86.Build.0 = Release|x86
+		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|x86.ActiveCfg = Debug|x86
 		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Debug|x86.Build.0 = Debug|x86
+		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|Any CPU.ActiveCfg = Release|x86
+		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|Mixed Platforms.Build.0 = Release|x86
 		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|x86.ActiveCfg = Release|x86
 		{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}.Release|x86.Build.0 = Release|x86
+		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|x86.ActiveCfg = Debug|x86
 		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Debug|x86.Build.0 = Debug|x86
+		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|Any CPU.ActiveCfg = Release|x86
+		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|Mixed Platforms.Build.0 = Release|x86
 		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|x86.ActiveCfg = Release|x86
 		{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}.Release|x86.Build.0 = Release|x86
+		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|x86.ActiveCfg = Debug|x86
 		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Debug|x86.Build.0 = Debug|x86
+		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|Any CPU.ActiveCfg = Release|x86
+		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|Mixed Platforms.Build.0 = Release|x86
 		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|x86.ActiveCfg = Release|x86
 		{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5}.Release|x86.Build.0 = Release|x86
+		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|x86.ActiveCfg = Debug|x86
 		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Debug|x86.Build.0 = Debug|x86
+		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|Any CPU.ActiveCfg = Release|x86
+		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|Mixed Platforms.Build.0 = Release|x86
 		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|x86.ActiveCfg = Release|x86
 		{FAFB5948-A222-4CF6-AD14-026BE7564802}.Release|x86.Build.0 = Release|x86
+		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|x86.ActiveCfg = Debug|x86
 		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Debug|x86.Build.0 = Debug|x86
+		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|Any CPU.ActiveCfg = Release|x86
+		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|Mixed Platforms.Build.0 = Release|x86
 		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|x86.ActiveCfg = Release|x86
 		{4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7}.Release|x86.Build.0 = Release|x86
+		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|x86.ActiveCfg = Debug|x86
 		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Debug|x86.Build.0 = Debug|x86
+		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|Any CPU.ActiveCfg = Release|x86
+		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|Mixed Platforms.Build.0 = Release|x86
 		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|x86.ActiveCfg = Release|x86
 		{35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97}.Release|x86.Build.0 = Release|x86
+		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|x86.ActiveCfg = Debug|x86
 		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Debug|x86.Build.0 = Debug|x86
+		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|Any CPU.ActiveCfg = Release|x86
+		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|Mixed Platforms.Build.0 = Release|x86
 		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|x86.ActiveCfg = Release|x86
 		{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}.Release|x86.Build.0 = Release|x86
+		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|x86.ActiveCfg = Debug|x86
 		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Debug|x86.Build.0 = Debug|x86
+		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|Any CPU.ActiveCfg = Release|x86
+		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|Mixed Platforms.Build.0 = Release|x86
 		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|x86.ActiveCfg = Release|x86
 		{BEC74619-DDBB-4FBA-B517-D3E20AFC9997}.Release|x86.Build.0 = Release|x86
+		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|x86.ActiveCfg = Debug|x86
 		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Debug|x86.Build.0 = Debug|x86
+		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|Any CPU.ActiveCfg = Release|x86
+		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|Mixed Platforms.Build.0 = Release|x86
 		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|x86.ActiveCfg = Release|x86
 		{CADDFCE0-7509-4430-8364-2074E1EEFCA2}.Release|x86.Build.0 = Release|x86
+		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|x86.ActiveCfg = Debug|x86
 		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Debug|x86.Build.0 = Debug|x86
+		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|Any CPU.ActiveCfg = Release|x86
+		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|Mixed Platforms.Build.0 = Release|x86
 		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|x86.ActiveCfg = Release|x86
 		{6BCE712F-846D-4846-9D1B-A66B858DA755}.Release|x86.Build.0 = Release|x86
+		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|x86.ActiveCfg = Debug|x86
 		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Debug|x86.Build.0 = Debug|x86
+		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|Any CPU.ActiveCfg = Release|x86
+		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|Mixed Platforms.Build.0 = Release|x86
 		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|x86.ActiveCfg = Release|x86
 		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4}.Release|x86.Build.0 = Release|x86
+		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|x86.ActiveCfg = Debug|x86
 		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Debug|x86.Build.0 = Debug|x86
+		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|Any CPU.ActiveCfg = Release|x86
+		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|Mixed Platforms.Build.0 = Release|x86
 		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|x86.ActiveCfg = Release|x86
 		{FD286DF8-2D3A-4394-8AD5-443FADE55FB2}.Release|x86.Build.0 = Release|x86
+		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|x86.ActiveCfg = Debug|x86
 		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Debug|x86.Build.0 = Debug|x86
+		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|Any CPU.ActiveCfg = Release|x86
+		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|Mixed Platforms.Build.0 = Release|x86
 		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|x86.ActiveCfg = Release|x86
 		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976}.Release|x86.Build.0 = Release|x86
+		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|x86.ActiveCfg = Debug|x86
 		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Debug|x86.Build.0 = Debug|x86
+		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|Any CPU.ActiveCfg = Release|x86
+		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|Mixed Platforms.Build.0 = Release|x86
 		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|x86.ActiveCfg = Release|x86
 		{D18A5DEB-5102-4775-A1AF-B75DAAA8907B}.Release|x86.Build.0 = Release|x86
+		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|x86.ActiveCfg = Debug|x86
 		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Debug|x86.Build.0 = Debug|x86
+		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|Any CPU.ActiveCfg = Release|x86
+		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|Mixed Platforms.Build.0 = Release|x86
 		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|x86.ActiveCfg = Release|x86
 		{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}.Release|x86.Build.0 = Release|x86
+		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|x86.ActiveCfg = Debug|x86
 		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Debug|x86.Build.0 = Debug|x86
+		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|Any CPU.ActiveCfg = Release|x86
+		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|Mixed Platforms.Build.0 = Release|x86
 		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|x86.ActiveCfg = Release|x86
 		{CBF6B8B0-A015-413A-8C86-01238BB45770}.Release|x86.Build.0 = Release|x86
+		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|x86.ActiveCfg = Debug|x86
 		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Debug|x86.Build.0 = Debug|x86
+		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|Any CPU.ActiveCfg = Release|x86
+		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|Mixed Platforms.Build.0 = Release|x86
 		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|x86.ActiveCfg = Release|x86
 		{8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB}.Release|x86.Build.0 = Release|x86
+		{B1784698-592E-4132-BDFA-9817409E3A96}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{B1784698-592E-4132-BDFA-9817409E3A96}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{B1784698-592E-4132-BDFA-9817409E3A96}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{B1784698-592E-4132-BDFA-9817409E3A96}.Debug|x86.ActiveCfg = Debug|x86
 		{B1784698-592E-4132-BDFA-9817409E3A96}.Debug|x86.Build.0 = Debug|x86
+		{B1784698-592E-4132-BDFA-9817409E3A96}.Release|Any CPU.ActiveCfg = Release|x86
+		{B1784698-592E-4132-BDFA-9817409E3A96}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{B1784698-592E-4132-BDFA-9817409E3A96}.Release|Mixed Platforms.Build.0 = Release|x86
 		{B1784698-592E-4132-BDFA-9817409E3A96}.Release|x86.ActiveCfg = Release|x86
 		{B1784698-592E-4132-BDFA-9817409E3A96}.Release|x86.Build.0 = Release|x86
+		{95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|x86.ActiveCfg = Debug|x86
 		{95C11A9E-56ED-456A-8447-2C89C1139266}.Debug|x86.Build.0 = Debug|x86
+		{95C11A9E-56ED-456A-8447-2C89C1139266}.Release|Any CPU.ActiveCfg = Release|x86
+		{95C11A9E-56ED-456A-8447-2C89C1139266}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{95C11A9E-56ED-456A-8447-2C89C1139266}.Release|Mixed Platforms.Build.0 = Release|x86
 		{95C11A9E-56ED-456A-8447-2C89C1139266}.Release|x86.ActiveCfg = Release|x86
 		{95C11A9E-56ED-456A-8447-2C89C1139266}.Release|x86.Build.0 = Release|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|Mixed Platforms.Build.0 = Debug|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|x86.ActiveCfg = Debug|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Debug|x86.Build.0 = Debug|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|Any CPU.ActiveCfg = Release|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|Mixed Platforms.Build.0 = Release|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|x86.ActiveCfg = Release|x86
+		{7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36}.Release|x86.Build.0 = Release|x86
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -159,9 +295,9 @@ Global
 		{CADDFCE0-7509-4430-8364-2074E1EEFCA2} = {47697CDB-27B6-4B05-B4F8-0CBE6F6EDF97}
 		{6BCE712F-846D-4846-9D1B-A66B858DA755} = {F9E67978-5CD6-4A5F-827B-4249711C0B02}
 		{700D0B95-95CD-43F3-B6C9-FAA0FC1358D4} = {F9E67978-5CD6-4A5F-827B-4249711C0B02}
-		{D12F7F2F-8A3C-415F-88FA-6DD061A84869} = {486ADF86-DD89-4E19-B805-9D94F19800D9}
 		{3DCA7B58-B8B3-49AC-9D9E-56F4A0460976} = {486ADF86-DD89-4E19-B805-9D94F19800D9}
 		{95C11A9E-56ED-456A-8447-2C89C1139266} = {486ADF86-DD89-4E19-B805-9D94F19800D9}
+		{D12F7F2F-8A3C-415F-88FA-6DD061A84869} = {486ADF86-DD89-4E19-B805-9D94F19800D9}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.0\lib\NET35;packages\Unity.2.1.505.2\lib\NET35
diff --git a/NzbDrone.sln.DotSettings b/NzbDrone.sln.DotSettings
index 2aded5528..a7de7ce4f 100644
--- a/NzbDrone.sln.DotSettings
+++ b/NzbDrone.sln.DotSettings
@@ -11,6 +11,8 @@
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue">WARNING</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedParameter_002ELocal/@EntryIndexedValue">WARNING</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseObjectOrCollectionInitializer/@EntryIndexedValue">HINT</s:String>
+	<s:String x:Key="/Default/CodeInspection/TestFileAnalysis/TestClassSuffix/@EntryValue">Fixture</s:String>
+	<s:String x:Key="/Default/CodeInspection/TestFileAnalysis/TestProjectToCodeProjectNameSpaceRegEx/@EntryValue">^(.*)\.Test(\..*)$</s:String>
 	<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=NzbDrone/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="NzbDrone"&gt;&lt;CSArrangeThisQualifier&gt;True&lt;/CSArrangeThisQualifier&gt;&lt;RemoveCodeRedundancies&gt;True&lt;/RemoveCodeRedundancies&gt;&lt;CSUseAutoProperty&gt;True&lt;/CSUseAutoProperty&gt;&lt;CSMakeFieldReadonly&gt;True&lt;/CSMakeFieldReadonly&gt;&lt;CSUseVar&gt;&lt;BehavourStyle&gt;CAN_CHANGE_TO_IMPLICIT&lt;/BehavourStyle&gt;&lt;LocalVariableStyle&gt;IMPLICIT_EXCEPT_SIMPLE_TYPES&lt;/LocalVariableStyle&gt;&lt;ForeachVariableStyle&gt;ALWAYS_IMPLICIT&lt;/ForeachVariableStyle&gt;&lt;/CSUseVar&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSShortenReferences&gt;True&lt;/CSShortenReferences&gt;&lt;CSReorderTypeMembers&gt;True&lt;/CSReorderTypeMembers&gt;&lt;XAMLCollapseEmptyTags&gt;False&lt;/XAMLCollapseEmptyTags&gt;&lt;/Profile&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">NzbDrone</s:String>
 	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_CHOP_COMPOUND_WHILE_EXPRESSION/@EntryValue">True</s:Boolean>
@@ -47,6 +49,8 @@
 	<s:Boolean x:Key="/Default/Environment/UnitTesting/DisabledProviders/=Jasmine/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/UnitTesting/DisabledProviders/=MSTest/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/UnitTesting/DisabledProviders/=QUnit/@EntryIndexedValue">True</s:Boolean>
+	<s:Int64 x:Key="/Default/Environment/UnitTesting/ParallelProcessesCount/@EntryValue">4</s:Int64>
+	<s:Boolean x:Key="/Default/Environment/UnitTesting/SeparateAppDomainPerAssembly/@EntryValue">True</s:Boolean>
 	<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=26E712D4B91E2E49A0E92C0AFE6FF57E/Entry/=38860059D7978D4DAF1997C7CBC46A78/EntryName/@EntryValue">Backbone model</s:String>
 	<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=26E712D4B91E2E49A0E92C0AFE6FF57E/Entry/=38860059D7978D4DAF1997C7CBC46A78/Position/@EntryValue">5</s:Int64>
 	<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=09B531154963914B9AB9E2A05E1F2B44/@KeyIndexDefined">True</s:Boolean>
diff --git a/NzbDrone/Properties/Resources.Designer.cs b/NzbDrone/Properties/Resources.Designer.cs
index d47a0a7ea..65584111d 100644
--- a/NzbDrone/Properties/Resources.Designer.cs
+++ b/NzbDrone/Properties/Resources.Designer.cs
@@ -9,9 +9,6 @@
 //------------------------------------------------------------------------------
 
 namespace NzbDrone.Properties {
-    using System;
-    
-    
     /// <summary>
     ///   A strongly-typed resource class, for looking up localized strings, etc.
     /// </summary>
diff --git a/NzbDrone/WindowsApp.cs b/NzbDrone/WindowsApp.cs
index 4d4dc9d69..5b20de23b 100644
--- a/NzbDrone/WindowsApp.cs
+++ b/NzbDrone/WindowsApp.cs
@@ -2,6 +2,7 @@
 using System.Windows.Forms;
 using NLog;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Instrumentation;
 using NzbDrone.Host;
 using NzbDrone.SysTray;
 
@@ -9,12 +10,17 @@ namespace NzbDrone
 {
     public static class WindowsApp
     {
+        private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
 
         public static void Main(string[] args)
         {
             try
             {
-                var container = Bootstrap.Start(new StartupArguments(args), new MessageBoxUserAlert());
+                var startupArgs = new StartupArguments(args);
+
+                LogTargets.Register(startupArgs, false, true);
+
+                var container = Bootstrap.Start(startupArgs, new MessageBoxUserAlert());
                 container.Register<ISystemTrayApp, SystemTrayApp>();
                 container.Resolve<ISystemTrayApp>().Start();
             }
@@ -23,9 +29,10 @@ namespace NzbDrone
             }
             catch (Exception e)
             {
+                Logger.FatalException("EPIC FAIL: " + e.Message, e);
                 var message = string.Format("{0}: {1}", e.GetType().Name, e.Message);
                 MessageBox.Show(text: message, buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Error, caption: "Epic Fail!");
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/ServiceHelpers/ServiceInstall/ServiceInstall.ncrunchproject b/ServiceHelpers/ServiceInstall/ServiceInstall.ncrunchproject
index 73e1ebbb9..b2eed192e 100644
--- a/ServiceHelpers/ServiceInstall/ServiceInstall.ncrunchproject
+++ b/ServiceHelpers/ServiceInstall/ServiceInstall.ncrunchproject
@@ -1,8 +1,8 @@
 <ProjectConfiguration>
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
-  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,10 +12,12 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration></UseBuildConfiguration>
-  <UseBuildPlatform />
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
   <ProxyProcessPath></ProxyProcessPath>
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
   <HiddenWarnings>PostBuildEventDisabled</HiddenWarnings>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/ServiceHelpers/ServiceUninstall/ServiceUninstall.ncrunchproject b/ServiceHelpers/ServiceUninstall/ServiceUninstall.ncrunchproject
index 73e1ebbb9..b2eed192e 100644
--- a/ServiceHelpers/ServiceUninstall/ServiceUninstall.ncrunchproject
+++ b/ServiceHelpers/ServiceUninstall/ServiceUninstall.ncrunchproject
@@ -1,8 +1,8 @@
 <ProjectConfiguration>
   <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
-  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <ConsiderInconclusiveTestsAsPassing>true</ConsiderInconclusiveTestsAsPassing>
   <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
-  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowDynamicCodeContractChecking>false</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
@@ -12,10 +12,12 @@
   <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
   <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
-  <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration></UseBuildConfiguration>
-  <UseBuildPlatform />
+  <DefaultTestTimeout>5000</DefaultTestTimeout>
+  <UseBuildConfiguration>Debug</UseBuildConfiguration>
+  <UseBuildPlatform>x86</UseBuildPlatform>
   <ProxyProcessPath></ProxyProcessPath>
-  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <UseCPUArchitecture>x86</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
   <HiddenWarnings>PostBuildEventDisabled</HiddenWarnings>
 </ProjectConfiguration>
\ No newline at end of file
diff --git a/UI/AddSeries/AddSeriesTemplate.html b/UI/AddSeries/AddSeriesTemplate.html
index 87715f2b1..696eb92f7 100644
--- a/UI/AddSeries/AddSeriesTemplate.html
+++ b/UI/AddSeries/AddSeriesTemplate.html
@@ -1,9 +1,9 @@
 {{#if folder.path}}
-    <div class="row well unmapped-folder-path">
-        <div class="span11">
-            {{folder.path}}
-        </div>
+<div class="row well unmapped-folder-path">
+    <div class="span11">
+        {{folder.path}}
     </div>
+</div>
 {{/if}}
 <div class="row x-search-bar">
     <div class="input-prepend nz-input-large add-series-search span11">
diff --git a/UI/AddSeries/Collection.js b/UI/AddSeries/Collection.js
index 04101920b..0dce7bc1e 100644
--- a/UI/AddSeries/Collection.js
+++ b/UI/AddSeries/Collection.js
@@ -5,7 +5,7 @@ define(
         'Series/SeriesModel'
     ], function (Backbone, SeriesModel) {
         return Backbone.Collection.extend({
-            url  : window.ApiRoot + '/series/lookup',
+            url  : window.NzbDrone.ApiRoot + '/series/lookup',
             model: SeriesModel,
 
             initialize: function (options) {
diff --git a/UI/AddSeries/RootFolders/Collection.js b/UI/AddSeries/RootFolders/Collection.js
index 74b9b4555..157bf19ce 100644
--- a/UI/AddSeries/RootFolders/Collection.js
+++ b/UI/AddSeries/RootFolders/Collection.js
@@ -7,11 +7,11 @@ define(
     ], function (Backbone, RootFolderModel) {
 
         var RootFolderCollection = Backbone.Collection.extend({
-            url  : window.ApiRoot + '/rootfolder',
+            url  : window.NzbDrone.ApiRoot + '/rootfolder',
             model: RootFolderModel
         });
 
-        var collection = new RootFolderCollection().bindSignalR();
+        //var collection = new RootFolderCollection().bindSignalR();
 
-        return collection;
+        return new RootFolderCollection();
     });
diff --git a/UI/AddSeries/RootFolders/LayoutTemplate.html b/UI/AddSeries/RootFolders/LayoutTemplate.html
index a62ef128d..390b3c056 100644
--- a/UI/AddSeries/RootFolders/LayoutTemplate.html
+++ b/UI/AddSeries/RootFolders/LayoutTemplate.html
@@ -6,7 +6,7 @@
     <div class="validation-errors"></div>
     <div class="input-prepend input-append x-path control-group">
         <span class="add-on">&nbsp;<i class="icon-folder-open"></i></span>
-        <input class="span9" type="text" validation-name="path" placeholder="Start Typing Folder Path...">
+        <input class="span9" type="text" validation-name="path" placeholder="Enter path to folder that contains your shows">
         <button class="btn btn-success x-add">
             <i class="icon-ok"/>
         </button>
diff --git a/UI/AddSeries/RootFolders/Model.js b/UI/AddSeries/RootFolders/Model.js
index 2c54f6ae3..6cd0dc17b 100644
--- a/UI/AddSeries/RootFolders/Model.js
+++ b/UI/AddSeries/RootFolders/Model.js
@@ -4,7 +4,7 @@ define(
         'backbone'
     ], function (Backbone) {
         return Backbone.Model.extend({
-            urlRoot : window.ApiRoot + '/rootfolder',
+            urlRoot : window.NzbDrone.ApiRoot + '/rootfolder',
             defaults: {
                 freeSpace: 0
             }
diff --git a/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html b/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html
index 60f2a49d7..56d547da5 100644
--- a/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html
+++ b/UI/AddSeries/RootFolders/RootFolderSelectionPartial.html
@@ -1,4 +1,4 @@
-<select class="span6 x-root-folder">
+<select class="span4 x-root-folder">
     {{#if this}}
     {{#each this}}
     <option value="{{id}}">{{path}}</option>
diff --git a/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html b/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html
new file mode 100644
index 000000000..0827ba052
--- /dev/null
+++ b/UI/AddSeries/RootFolders/StartingSeasonSelectionPartial.html
@@ -0,0 +1,9 @@
+<select class="span2 x-starting-season">
+    {{#each this}}
+        {{#if_eq seasonNumber compare="0"}}
+            <option value="{{seasonNumber}}">Specials</option>
+        {{else}}
+            <option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
+        {{/if_eq}}
+    {{/each}}
+</select>
diff --git a/UI/AddSeries/SearchResultView.js b/UI/AddSeries/SearchResultView.js
index e7d16631a..28ba1a774 100644
--- a/UI/AddSeries/SearchResultView.js
+++ b/UI/AddSeries/SearchResultView.js
@@ -3,7 +3,6 @@ define(
     [
         'app',
         'marionette',
-
         'Quality/QualityProfileCollection',
         'AddSeries/RootFolders/Collection',
         'AddSeries/RootFolders/Layout',
@@ -15,13 +14,14 @@ define(
 
         return Marionette.ItemView.extend({
 
-            template: 'AddSeries/SearchResultTemplate',
+            template: 'AddSeries/SearchResultViewTemplate',
 
             ui: {
                 qualityProfile: '.x-quality-profile',
                 rootFolder    : '.x-root-folder',
                 addButton     : '.x-add',
-                overview      : '.x-overview'
+                overview      : '.x-overview',
+                startingSeason: '.x-starting-season'
             },
 
             events: {
@@ -57,6 +57,12 @@ define(
                     this.ui.rootFolder.val(defaultRoot);
                 }
 
+                var minSeasonNotZero = _.min(_.reject(this.model.get('seasons'), { seasonNumber: 0 }), 'seasonNumber');
+
+                if (minSeasonNotZero) {
+                    this.ui.startingSeason.val(minSeasonNotZero.seasonNumber);
+                }
+
                 //TODO: make this work via onRender, FM?
                 //works with onShow, but stops working after the first render
                 this.ui.overview.dotdotdot({
@@ -117,15 +123,16 @@ define(
 
                 var quality = this.ui.qualityProfile.val();
                 var rootFolderPath = this.ui.rootFolder.children(':selected').text();
+                var startingSeason = this.ui.startingSeason.val();
 
                 this.model.set('qualityProfileId', quality);
                 this.model.set('rootFolderPath', rootFolderPath);
+                this.model.setSeasonPass(startingSeason);
 
                 var self = this;
 
                 SeriesCollection.add(this.model);
 
-
                 this.model.save().done(function () {
                     self.close();
                     icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search');
diff --git a/UI/AddSeries/SearchResultTemplate.html b/UI/AddSeries/SearchResultViewTemplate.html
similarity index 65%
rename from UI/AddSeries/SearchResultTemplate.html
rename to UI/AddSeries/SearchResultViewTemplate.html
index e60f15039..574422b17 100644
--- a/UI/AddSeries/SearchResultTemplate.html
+++ b/UI/AddSeries/SearchResultViewTemplate.html
@@ -2,36 +2,34 @@
     <div class="row">
         <div class="span2">
             <a href="{{traktUrl}}" target="_blank">
-                <img class="new-series-poster" src="{{remotePoster}}"
-                {{defaultImg}} >
+                <img class="new-series-poster" src="{{remotePoster}}" {{defaultImg}} >
             </a>
         </div>
         <div class="span9">
 
             <div class="row">
-                <h2>{{title}}</h2>
+                <h2>{{titleWithYear}}</h2>
             </div>
             <div class="row new-series-overview x-overview">
                 {{overview}}
             </div>
             <div class="row">
                 {{#if existing}}
-                    <div class="btn pull-right add-series disabled">
+                    <div class="btn add-series disabled pull-right">
                         Already Exists
                     </div>
                 {{else}}
-                    <div class="btn btn-success x-add pull-right add-series">
-                        Add
-                        <icon class="icon-plus"></icon>
-                    </div>
                     {{#unless path}}
                         {{> RootFolderSelectionPartial rootFolders}}
                     {{/unless}}
-                    <div class='pull-right'>
-                        {{> QualityProfileSelectionPartial qualityProfiles}}
+
+                    {{> StartingSeasonSelectionPartial seasons}}
+                    {{> QualityProfileSelectionPartial qualityProfiles}}
+
+                    <div class="span1 btn btn-success x-add add-series pull-right">
+                        Add <i class="icon-plus"></i>
                     </div>
                 {{/if}}
-
             </div>
         </div>
     </div>
diff --git a/UI/Calendar/Collection.js b/UI/Calendar/Collection.js
index 41efd7979..87972acf7 100644
--- a/UI/Calendar/Collection.js
+++ b/UI/Calendar/Collection.js
@@ -5,7 +5,7 @@ define(
         'Series/EpisodeModel'
     ], function (Backbone, EpisodeModel) {
         return  Backbone.Collection.extend({
-            url  : window.ApiRoot + '/calendar',
+            url  : window.NzbDrone.ApiRoot + '/calendar',
             model: EpisodeModel,
 
             comparator: function (model) {
diff --git a/UI/Calendar/UpcomingCollection.js b/UI/Calendar/UpcomingCollection.js
index fdc7d58a0..2429e3744 100644
--- a/UI/Calendar/UpcomingCollection.js
+++ b/UI/Calendar/UpcomingCollection.js
@@ -6,7 +6,7 @@ define(
         'Series/EpisodeModel'
     ], function (Backbone, Moment, EpisodeModel) {
         return Backbone.Collection.extend({
-            url  : window.ApiRoot + '/calendar',
+            url  : window.NzbDrone.ApiRoot + '/calendar',
             model: EpisodeModel,
 
             comparator: function (model1, model2) {
diff --git a/UI/Cells/Edit/QualityCellEditor.js b/UI/Cells/Edit/QualityCellEditor.js
index 711bbed99..51ebceb55 100644
--- a/UI/Cells/Edit/QualityCellEditor.js
+++ b/UI/Cells/Edit/QualityCellEditor.js
@@ -28,7 +28,10 @@ define(
                     self.schema = qualityProfileSchemaCollection.first();
 
                     var selected = _.find(self.schema.get('available'), { 'id': self.model.get(self.column.get("name")).quality.id });
-                    selected.selected = true;
+
+                    if (selected) {
+                        selected.selected = true;
+                    }
 
                     self.templateFunction = Marionette.TemplateCache.get(templateName);
                     var data = self.schema.toJSON();
diff --git a/UI/Cells/EpisodeNumberCell.js b/UI/Cells/EpisodeNumberCell.js
index b72720de1..85be40255 100644
--- a/UI/Cells/EpisodeNumberCell.js
+++ b/UI/Cells/EpisodeNumberCell.js
@@ -17,13 +17,26 @@ define(
                 var seasonField = this.column.get('seasonNumber') || 'seasonNumber';
                 var episodeField = this.column.get('episodes') || 'episodeNumber';
 
-                if (this.cellValue) {
+                if (this.model) {
+                    var result = 'Unknown';
 
-                    var airDate = this.cellValue.get(airDateField);
-                    var seasonNumber = this.cellValue.get(seasonField);
-                    var episodes = this.cellValue.get(episodeField);
+                    var airDate = this.model.get(airDateField);
+                    var seasonNumber = this.model.get(seasonField);
+                    var episodes = this.model.get(episodeField);
 
-                    var result = 'Unknown';
+                    if (this.cellValue) {
+                        if (!seasonNumber) {
+                            seasonNumber = this.cellValue.get(seasonField);
+                        }
+
+                        if (!episodes) {
+                            episodes = this.cellValue.get(episodeField);
+                        }
+
+                        if (!airDate) {
+                            this.model.get(airDateField);
+                        }
+                    }
 
                     if (episodes) {
 
diff --git a/UI/Commands/CommandCollection.js b/UI/Commands/CommandCollection.js
new file mode 100644
index 000000000..52aaef365
--- /dev/null
+++ b/UI/Commands/CommandCollection.js
@@ -0,0 +1,26 @@
+'use strict';
+define(
+    [
+        'backbone',
+        'Commands/CommandModel',
+        'Mixins/backbone.signalr.mixin'
+    ], function (Backbone, CommandModel) {
+
+        var CommandCollection = Backbone.Collection.extend({
+            url  : window.NzbDrone.ApiRoot + '/command',
+            model: CommandModel,
+
+            findCommand: function (command) {
+                return this.find(function (model) {
+                    return model.isSameCommand(command);
+                });
+            }
+
+        });
+
+        var collection = new CommandCollection().bindSignalR();
+
+        collection.fetch();
+
+        return collection;
+    });
diff --git a/UI/Commands/CommandController.js b/UI/Commands/CommandController.js
index c10d66ae5..650d8f67b 100644
--- a/UI/Commands/CommandController.js
+++ b/UI/Commands/CommandController.js
@@ -1,16 +1,56 @@
 'use strict';
-define({
-        Execute: function (name, properties) {
-            var data = { command: name };
+define(
+    [
+        'Commands/CommandModel',
+        'Commands/CommandCollection',
+        'underscore',
+        'jQuery/jquery.spin'
+    ], function (CommandModel, CommandCollection, _) {
 
-            if (properties) {
-                $.extend(data, properties);
-            }
+        return{
+
+            Execute: function (name, properties) {
+
+                var attr = _.extend({name: name.toLocaleLowerCase()}, properties);
+
+                var commandModel = new CommandModel(attr);
+
+                return commandModel.save().success(function () {
+                    CommandCollection.add(commandModel);
+                });
+            },
+
+            bindToCommand: function (options) {
+
+                var self = this;
 
-            return $.ajax({
-                type: 'POST',
-                url : window.ApiRoot + '/command',
-                data: JSON.stringify(data)
-            });
+                var existingCommand = CommandCollection.findCommand(options.command);
+
+                if (existingCommand) {
+                    this._bindToCommandModel.call(this, existingCommand, options);
+                }
+
+                CommandCollection.bind('add sync', function (model) {
+                    if (model.isSameCommand(options.command)) {
+                        self._bindToCommandModel.call(self, model, options);
+                    }
+                });
+            },
+
+            _bindToCommandModel: function bindToCommand(model, options) {
+
+                if (!model.isActive()) {
+                    options.element.stopSpin();
+                    return;
+                }
+
+                model.bind('change:state', function (model) {
+                    if (!model.isActive()) {
+                        options.element.stopSpin();
+                    }
+                });
+
+                options.element.startSpin();
+            }
         }
     });
diff --git a/UI/Commands/CommandMessengerCollectionView.js b/UI/Commands/CommandMessengerCollectionView.js
new file mode 100644
index 000000000..238642ab6
--- /dev/null
+++ b/UI/Commands/CommandMessengerCollectionView.js
@@ -0,0 +1,15 @@
+'use strict';
+define(
+    [
+        'app',
+        'marionette',
+        'Commands/CommandCollection',
+        'Commands/CommandMessengerItemView'
+    ], function (App, Marionette, commandCollection, CommandMessengerItemView) {
+
+        var CollectionView = Marionette.CollectionView.extend({
+            itemView : CommandMessengerItemView
+        });
+
+        new CollectionView({collection: commandCollection});
+    });
diff --git a/UI/Commands/CommandMessengerItemView.js b/UI/Commands/CommandMessengerItemView.js
new file mode 100644
index 000000000..03199462b
--- /dev/null
+++ b/UI/Commands/CommandMessengerItemView.js
@@ -0,0 +1,48 @@
+'use strict';
+define(
+    [
+        'app',
+        'marionette',
+        'Shared/Messenger'
+    ], function (App, Marionette, Messenger) {
+
+        return Marionette.ItemView.extend({
+
+
+            initialize: function () {
+                this.listenTo(this.model, 'change', this.render);
+            },
+
+
+            render: function () {
+                if (!this.model.get('message') || !this.model.get('sendUpdatesToClient')) {
+                    return;
+                }
+
+                var message = {
+                    type     : 'info',
+                    message  : '[{0}] {1}'.format(this.model.get('name'), this.model.get('message')),
+                    id       : this.model.id,
+                    hideAfter: 0
+                };
+
+                switch (this.model.get('state')) {
+                    case 'completed':
+                        message.hideAfter = 4;
+                        break;
+                    case 'failed':
+                        message.hideAfter = 4;
+                        message.type = 'error';
+                        break;
+                    default :
+                        message.hideAfter = 0;
+                }
+
+                Messenger.show(message);
+
+                console.log(message.message);
+            }
+
+        });
+
+    });
diff --git a/UI/Commands/CommandModel.js b/UI/Commands/CommandModel.js
new file mode 100644
index 000000000..fcb08dd6d
--- /dev/null
+++ b/UI/Commands/CommandModel.js
@@ -0,0 +1,33 @@
+'use strict';
+define(
+    [
+        'backbone'
+    ], function (Backbone) {
+        return Backbone.Model.extend({
+            url: window.NzbDrone.ApiRoot + '/command',
+
+            parse: function (response) {
+                response.name = response.name.toLocaleLowerCase();
+                return response;
+            },
+
+            isActive: function () {
+                return this.get('state') !== 'completed' && this.get('state') !== 'failed';
+            },
+
+            isSameCommand: function (command) {
+
+                if (command.name.toLocaleLowerCase() != this.get('name').toLocaleLowerCase()) {
+                    return false;
+                }
+
+                for (var key in command) {
+                    if (key !== 'name' && command[key] !== this.get(key)) {
+                        return false;
+                    }
+                }
+
+                return true;
+            }
+        });
+    });
diff --git a/UI/Content/form.less b/UI/Content/form.less
index 00a64b748..3d9e5267c 100644
--- a/UI/Content/form.less
+++ b/UI/Content/form.less
@@ -3,8 +3,9 @@
 .control-group {
   .controls {
     i {
-      font-size : 16px;
-      color     : #595959;
+      font-size    : 16px;
+      color        : #595959;
+      margin-right : 5px;
     }
     .checkbox {
       width         : 100px;
@@ -15,7 +16,6 @@
     }
 
     .help-inline-checkbox {
-      padding-left   : 5px;
       display        : inline-block;
       margin-top     : -20px;
       margin-bottom  : 0;
@@ -35,9 +35,9 @@ textarea.release-restrictions {
 }
 
 .help-link {
-  text-decoration: none !important;
+  text-decoration : none !important;
 
   i {
     .clickable;
   }
-}
\ No newline at end of file
+}
diff --git a/UI/Content/icons.less b/UI/Content/icons.less
index 34a55b224..0a253e951 100644
--- a/UI/Content/icons.less
+++ b/UI/Content/icons.less
@@ -46,6 +46,11 @@
   transform         : scale(1, -1);
 }
 
+.icon-nd-warning:before {
+  .icon(@warning-sign);
+  color : #f89406;
+}
+
 .icon-nd-edit:before {
   .icon(@wrench);
 }
@@ -109,4 +114,16 @@
 
 .icon-nd-imported:before {
   .icon(@download-alt);
+}
+
+.icon-nd-status:before {
+  .icon(@circle);
+}
+
+.icon-nd-monitored:before {
+  .icon(@bookmark);
+}
+
+.icon-nd-unmonitored:before {
+  .icon(@bookmark-empty);
 }
\ No newline at end of file
diff --git a/UI/Content/theme.less b/UI/Content/theme.less
index 4c8c3a1e8..a11ceb566 100644
--- a/UI/Content/theme.less
+++ b/UI/Content/theme.less
@@ -12,7 +12,7 @@
 @import "../Shared/Styles/clickable";
 @import "../Shared/Styles/card";
 
-.progress {
+.progress.episode-progress {
   width         : 125px;
   position      : relative;
   margin-bottom : 2px;
@@ -21,6 +21,7 @@
     font-size   : 11.844px;
     font-weight : bold;
     text-align  : center;
+    cursor      : default;
   }
 
   .progressbar-back-text {
@@ -33,6 +34,7 @@
     display : block;
     width   : 125px;
   }
+
   .bar {
     position : absolute;
     overflow : hidden;
@@ -147,3 +149,15 @@ footer {
     padding-right : 5px;
   }
 }
+
+.status-primary {
+  color: @linkColor;
+}
+
+.status-success {
+  color: @successText;
+}
+
+.status-danger {
+  color: @errorText;
+}
\ No newline at end of file
diff --git a/UI/Controller.js b/UI/Controller.js
index b11840bbb..190708195 100644
--- a/UI/Controller.js
+++ b/UI/Controller.js
@@ -10,7 +10,6 @@ define(
         'Series/Details/SeriesDetailsLayout',
         'Series/SeriesCollection',
         'Missing/MissingLayout',
-        'Series/SeriesModel',
         'Calendar/CalendarLayout',
         'Logs/Layout',
         'Logs/Files/Layout',
@@ -19,7 +18,7 @@ define(
         'SeasonPass/Layout',
         'Shared/NotFoundView',
         'Shared/Modal/Region'
-    ], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, SeriesModel, CalendarLayout,
+    ], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, CalendarLayout,
         LogsLayout, LogFileLayout, ReleaseLayout, SystemLayout, SeasonPassLayout, NotFoundView) {
         return Marionette.Controller.extend({
 
diff --git a/UI/Episode/Search/Layout.js b/UI/Episode/Search/Layout.js
index 15b89f79a..2d812ce4c 100644
--- a/UI/Episode/Search/Layout.js
+++ b/UI/Episode/Search/Layout.js
@@ -7,11 +7,9 @@ define(
         'Episode/Search/ManualLayout',
         'Release/Collection',
         'Series/SeriesCollection',
-        'Shared/LoadingView',
-        'Shared/Messenger',
         'Commands/CommandController',
-        'Shared/FormatHelpers'
-    ], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection, LoadingView, Messenger, CommandController, FormatHelpers) {
+        'Shared/LoadingView'
+    ], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection,CommandController, LoadingView) {
 
         return Marionette.Layout.extend({
             template: 'Episode/Search/LayoutTemplate',
@@ -39,16 +37,8 @@ define(
                     e.preventDefault();
                 }
 
-                CommandController.Execute('episodeSearch', { episodeId: this.model.get('id') });
-
-                var series = SeriesCollection.get(this.model.get('seriesId'));
-                var seriesTitle = series.get('title');
-                var season = this.model.get('seasonNumber');
-                var episode = this.model.get('episodeNumber');
-                var message = seriesTitle + ' - ' + season + 'x' + FormatHelpers.pad(episode, 2);
-
-                Messenger.show({
-                    message: 'Search started for: ' + message
+                CommandController.Execute('episodeSearch', {
+                    episodeId: this.model.get('id')
                 });
 
                 App.vent.trigger(App.Commands.CloseModalCommand);
diff --git a/UI/Episode/Summary/Layout.js b/UI/Episode/Summary/Layout.js
deleted file mode 100644
index b263c5682..000000000
--- a/UI/Episode/Summary/Layout.js
+++ /dev/null
@@ -1,67 +0,0 @@
-'use strict';
-define(
-    [
-        'app',
-        'marionette',
-        'backgrid',
-        'Cells/FileSizeCell',
-        'Cells/QualityCell',
-        'Episode/Summary/NoFileView'
-    ], function (App, Marionette, Backgrid, FileSizeCell, QualityCell, NoFileView) {
-
-        return Marionette.Layout.extend({
-            template: 'Episode/Summary/LayoutTemplate',
-
-            regions: {
-                overview: '.episode-overview',
-                activity: '.episode-file-info'
-            },
-
-            columns:
-                [
-                    {
-                        name    : 'path',
-                        label   : 'Path',
-                        cell    : 'string',
-                        sortable: false
-                    },
-                    {
-                        name    : 'size',
-                        label   : 'Size',
-                        cell    : FileSizeCell,
-                        sortable: false
-                    },
-                    {
-                        name    : 'quality',
-                        label   : 'Quality',
-                        cell    : QualityCell,
-                        sortable: false,
-                        editable: true
-                    }
-                ],
-
-            templateHelpers: {},
-
-            initialize: function (options) {
-                if (!this.model.series) {
-                    this.templateHelpers.series = options.series.toJSON();
-                }
-            },
-
-            onShow: function () {
-                if (this.model.get('hasFile')) {
-                    var episodeFile = App.request(App.Reqres.GetEpisodeFileById, this.model.get('episodeFileId'));
-
-                    this.activity.show(new Backgrid.Grid({
-                        collection: new Backbone.Collection(episodeFile),
-                        columns   : this.columns,
-                        className : 'table table-bordered'spinn
-                    }));
-                }
-
-                else {
-                    this.activity.show(new NoFileView());
-                }
-            }
-        });
-    });
diff --git a/UI/Handlebars/Helpers/Html.js b/UI/Handlebars/Helpers/Html.js
index 21534b9ac..9c5556c0f 100644
--- a/UI/Handlebars/Helpers/Html.js
+++ b/UI/Handlebars/Helpers/Html.js
@@ -4,7 +4,17 @@ define(
     [
         'handlebars'
     ], function (Handlebars) {
+
+        var placeHolder = '/Content/Images/poster-dark.jpg';
+
+        window.NzbDrone.imageError = function (img) {
+            if (!img.src.contains(placeHolder)) {
+                img.src = placeHolder;
+            }
+            img.onerror = null;
+        };
+
         Handlebars.registerHelper('defaultImg', function () {
-            return new Handlebars.SafeString('onerror=this.src=\'/Content/Images/poster-dark.jpg\';');
+            return new Handlebars.SafeString('onerror=window.NzbDrone.imageError(this)');
         });
     });
diff --git a/UI/Handlebars/Helpers/Series.js b/UI/Handlebars/Helpers/Series.js
index ac2a21747..f9d849325 100644
--- a/UI/Handlebars/Helpers/Series.js
+++ b/UI/Handlebars/Helpers/Series.js
@@ -62,4 +62,12 @@ define(
 
             return new Handlebars.SafeString('<span class="label label-info">{0} Seasons</span>'.format(seasonCount))
         });
+
+        Handlebars.registerHelper('titleWithYear', function () {
+            if (this.title.endsWith(' ({0})'.format(this.year))) {
+                return this.title;
+            }
+
+            return '{0} ({1})'.format(this.title, this.year);
+        });
     });
diff --git a/UI/History/Collection.js b/UI/History/Collection.js
index 525bf1e48..e3f19ca04 100644
--- a/UI/History/Collection.js
+++ b/UI/History/Collection.js
@@ -5,7 +5,7 @@ define(
         'backbone.pageable'
     ], function (HistoryModel, PageableCollection) {
         return PageableCollection.extend({
-            url  : window.ApiRoot + '/history',
+            url  : window.NzbDrone.ApiRoot + '/history',
             model: HistoryModel,
 
             state: {
diff --git a/UI/History/Details/HistoryDetailsViewTemplate.html b/UI/History/Details/HistoryDetailsViewTemplate.html
index 5d15ca7b4..2dd83ed5e 100644
--- a/UI/History/Details/HistoryDetailsViewTemplate.html
+++ b/UI/History/Details/HistoryDetailsViewTemplate.html
@@ -27,7 +27,7 @@
 
                     {{#if nzbInfoUrl}}
                         <dt>Info</dt>
-                        <dd><a href="{{nzbInfoUrl}}">{{nzbInfoUrl}}o</a></dd>
+                        <dd><a href="{{infoUrl}}">{{infoUrl}}o</a></dd>
                     {{/if}}
                 {{/with}}
             </dl>
diff --git a/UI/Instrumentation/ErrorHandler.js b/UI/Instrumentation/ErrorHandler.js
index 589723771..7909dc5fb 100644
--- a/UI/Instrumentation/ErrorHandler.js
+++ b/UI/Instrumentation/ErrorHandler.js
@@ -73,8 +73,14 @@
             return false;
         }
 
+        else if (xmlHttpRequest.status === 503) {
+            message.message = xmlHttpRequest.responseJSON.message;
+        }
 
-        message.message = '[{0}] {1} : {2}'.format(ajaxOptions.type, xmlHttpRequest.statusText, ajaxOptions.url);
+        else
+        {
+            message.message = '[{0}] {1} : {2}'.format(ajaxOptions.type, xmlHttpRequest.statusText, ajaxOptions.url);
+        }
 
         window.Messenger().post(message);
         return false;
diff --git a/UI/Logs/Collection.js b/UI/Logs/Collection.js
index 3b6070b59..9b755203a 100644
--- a/UI/Logs/Collection.js
+++ b/UI/Logs/Collection.js
@@ -1,7 +1,7 @@
 'use strict';
 define(['backbone.pageable', 'Logs/Model', ], function (PagableCollection, LogsModel) {
     return PagableCollection.extend({
-        url  : window.ApiRoot + '/log',
+        url  : window.NzbDrone.ApiRoot + '/log',
         model: LogsModel,
 
         state: {
diff --git a/UI/Logs/Files/Collection.js b/UI/Logs/Files/Collection.js
index 969070232..0b2a5c8bb 100644
--- a/UI/Logs/Files/Collection.js
+++ b/UI/Logs/Files/Collection.js
@@ -2,7 +2,7 @@
 define(['Logs/Files/Model' ],
 function (LogFileModel) {
     return Backbone.Collection.extend({
-        url  : window.ApiRoot + '/log/files',
+        url  : window.NzbDrone.ApiRoot + '/log/files',
         model: LogFileModel,
 
         state: {
diff --git a/UI/Missing/Collection.js b/UI/Missing/Collection.js
index 7ef9f627f..4461dad06 100644
--- a/UI/Missing/Collection.js
+++ b/UI/Missing/Collection.js
@@ -5,7 +5,7 @@ define(
         'backbone.pageable'
     ], function (EpisodeModel, PagableCollection) {
         return PagableCollection.extend({
-            url  : window.ApiRoot + '/missing',
+            url  : window.NzbDrone.ApiRoot + '/missing',
             model: EpisodeModel,
 
             state: {
diff --git a/UI/Mixins/AutoComplete.js b/UI/Mixins/AutoComplete.js
index 6da4c82b1..90856cb20 100644
--- a/UI/Mixins/AutoComplete.js
+++ b/UI/Mixins/AutoComplete.js
@@ -6,7 +6,7 @@ define(function () {
         $(this).typeahead({
             source   : function (filter, callback) {
                 $.ajax({
-                    url     : window.ApiRoot + resource,
+                    url     : window.NzbDrone.ApiRoot + resource,
                     dataType: 'json',
                     type    : 'GET',
                     data    : { query: filter },
diff --git a/UI/Mixins/backbone.ajax.js b/UI/Mixins/backbone.ajax.js
index 5bfbe6d13..0544b2e28 100644
--- a/UI/Mixins/backbone.ajax.js
+++ b/UI/Mixins/backbone.ajax.js
@@ -11,7 +11,7 @@ define(function () {
 
             //check if ajax call was made with data option
             if (xhr && xhr.data && xhr.type === 'DELETE') {
-                if (xhr.url.indexOf('?') === -1) {
+                if (!xhr.url.contains('?')) {
                     xhr.url = xhr.url + '?' + $.param(xhr.data);
                 }
                 else {
diff --git a/UI/Mixins/backbone.signalr.mixin.js b/UI/Mixins/backbone.signalr.mixin.js
index 39b309f2a..8f5c695ae 100644
--- a/UI/Mixins/backbone.signalr.mixin.js
+++ b/UI/Mixins/backbone.signalr.mixin.js
@@ -5,59 +5,29 @@ define(
     ], function () {
 
         _.extend(Backbone.Collection.prototype, {
-            bindSignalR: function (options) {
+            bindSignalR: function () {
 
-                if (!options) {
-                    options = {};
-                }
+                var collection = this;
 
-                if (!options.url) {
-                    console.assert(this.url, 'url must be provided or collection must have url');
-                    options.url = this.url.replace('api', 'signalr');
-                }
+                var processMessage = function (options) {
 
-                var self = this;
-
-                var _getStatus = function (status) {
-                    switch (status) {
-                        case    0:
-                            return 'connecting';
-                        case 1:
-                            return 'connected';
-                        case 2:
-                            return 'reconnecting';
-                        case 4:
-                            return 'disconnected';
-                        default:
-                            throw 'invalid status ' + status;
-                    }
+                    var model = new collection.model(options.resource, {parse: true});
+                    collection.add(model, {merge: true});
+                    console.log(options.action + ": %O", options.resource);
                 };
 
-                this.signalRconnection = $.connection(options.url);
-
-                this.signalRconnection.stateChanged(function (change) {
-                    console.debug('{0} [{1}]'.format(options.url, _getStatus(change.newState)));
-                });
-
-                this.signalRconnection.received(function (message) {
-                    console.debug(message);
-                    self.fetch();
-                });
-
-                this.signalRconnection.start({ transport:
+                require(
                     [
-                        'longPolling'
-                    ] });
+                        'app'
+                    ], function (app) {
+                        collection.listenTo(app.vent, 'server:' + collection.url.replace('/api/', ''), processMessage)
+                    });
 
                 return this;
             },
 
             unbindSignalR: function () {
 
-                if(this.signalRconnection){
-                    this.signalRconnection.stop();
-                    delete this.signalRconnection;
-                }
 
             }});
     });
diff --git a/UI/ProgressMessaging/ProgressMessageCollection.js b/UI/ProgressMessaging/ProgressMessageCollection.js
new file mode 100644
index 000000000..ca74fb638
--- /dev/null
+++ b/UI/ProgressMessaging/ProgressMessageCollection.js
@@ -0,0 +1,47 @@
+'use strict';
+define(
+    [
+        'app',
+        'backbone',
+        'Shared/Messenger',
+        'Mixins/backbone.signalr.mixin'
+    ], function (App, Backbone, Messenger) {
+
+        var ProgressMessageCollection = Backbone.Collection.extend({
+            url  : window.NzbDrone.ApiRoot + '/progressmessage',
+            model: Backbone.Model,
+
+            initialize: function(){
+
+            }
+
+        });
+
+        var collection = new ProgressMessageCollection();//.bindSignalR();
+
+        /*        collection.signalRconnection.received(function (message) {
+
+         var type = getMessengerType(message.status);
+         var hideAfter = type === 'info' ? 60 :5;
+
+         Messenger.show({
+         id       : message.commandId,
+         message  : message.message,
+         type     : type,
+         hideAfter: hideAfter
+         });
+         });*/
+
+        var getMessengerType = function (status) {
+            switch (status) {
+                case 'completed':
+                    return 'success';
+                case 'failed':
+                    return 'error';
+                default:
+                    return 'info';
+            }
+        };
+
+        return collection;
+    });
diff --git a/UI/Quality/QualityProfileCollection.js b/UI/Quality/QualityProfileCollection.js
index db24cd24b..dbed578d5 100644
--- a/UI/Quality/QualityProfileCollection.js
+++ b/UI/Quality/QualityProfileCollection.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict';
 define(
     [
         'backbone',
@@ -7,7 +7,7 @@ define(
 
         var QualityProfileCollection = Backbone.Collection.extend({
             model: QualityProfileModel,
-            url  : window.ApiRoot + '/qualityprofiles'
+            url  : window.NzbDrone.ApiRoot + '/qualityprofiles'
         });
 
         var profiles = new QualityProfileCollection();
diff --git a/UI/Quality/QualitySizeCollection.js b/UI/Quality/QualitySizeCollection.js
index 4919f5ac7..b29fc62f7 100644
--- a/UI/Quality/QualitySizeCollection.js
+++ b/UI/Quality/QualitySizeCollection.js
@@ -5,6 +5,6 @@ define(
     ], function (QualitySizeModel) {
         return Backbone.Collection.extend({
             model: QualitySizeModel,
-            url  : window.ApiRoot + '/qualitysize'
+            url  : window.NzbDrone.ApiRoot + '/qualitysize'
         });
     });
diff --git a/UI/Release/Collection.js b/UI/Release/Collection.js
index e584f35d0..93ec60c15 100644
--- a/UI/Release/Collection.js
+++ b/UI/Release/Collection.js
@@ -5,7 +5,7 @@ define(
         'backbone.pageable'
     ], function (ReleaseModel, PagableCollection) {
         return PagableCollection.extend({
-            url  : window.ApiRoot + '/release',
+            url  : window.NzbDrone.ApiRoot + '/release',
             model: ReleaseModel,
 
             mode: 'client',
diff --git a/UI/Router.js b/UI/Router.js
index 76513c471..de2111644 100644
--- a/UI/Router.js
+++ b/UI/Router.js
@@ -5,10 +5,12 @@ require(
         'marionette',
         'Controller',
         'Series/SeriesCollection',
+        'ProgressMessaging/ProgressMessageCollection',
+        'Commands/CommandMessengerCollectionView',
         'Navbar/NavbarView',
         'jQuery/RouteBinder',
         'jquery'
-    ], function (App, Marionette, Controller, SeriesCollection, NavbarView, RouterBinder, $) {
+    ], function (App, Marionette, Controller, SeriesCollection, ProgressMessageCollection, CommandMessengerCollectionView, NavbarView, RouterBinder, $) {
 
         var Router = Marionette.AppRouter.extend({
 
@@ -38,11 +40,11 @@ require(
             App.Router = new Router();
 
             SeriesCollection.fetch().done(function () {
-                    Backbone.history.start({ pushState: true });
-                    RouterBinder.bind(App.Router);
-                    App.navbarRegion.show(new NavbarView());
-                    $('body').addClass('started');
-                })
+                Backbone.history.start({ pushState: true });
+                RouterBinder.bind(App.Router);
+                App.navbarRegion.show(new NavbarView());
+                $('body').addClass('started');
+            });
         });
 
         return App.Router;
diff --git a/UI/SeasonPass/Layout.js b/UI/SeasonPass/Layout.js
index cb4500daf..ebf2609cf 100644
--- a/UI/SeasonPass/Layout.js
+++ b/UI/SeasonPass/Layout.js
@@ -24,16 +24,10 @@ define(
                 this.series.show(new LoadingView());
 
                 this.seriesCollection = SeriesCollection;
-                this.seasonCollection = new SeasonCollection();
 
-                var promise = this.seasonCollection.fetch();
-
-                promise.done(function () {
-                    self.series.show(new SeriesCollectionView({
-                        collection: self.seriesCollection,
-                        seasonCollection: self.seasonCollection
-                    }));
-                });
+                self.series.show(new SeriesCollectionView({
+                    collection: self.seriesCollection
+                }));
             }
         });
     });
diff --git a/UI/SeasonPass/SeriesCollectionView.js b/UI/SeasonPass/SeriesCollectionView.js
index 48cf5c39e..d5de2530e 100644
--- a/UI/SeasonPass/SeriesCollectionView.js
+++ b/UI/SeasonPass/SeriesCollectionView.js
@@ -5,22 +5,6 @@ define(
         'SeasonPass/SeriesLayout'
     ], function (Marionette, SeriesLayout) {
         return Marionette.CollectionView.extend({
-
-            itemView: SeriesLayout,
-
-            initialize: function (options) {
-
-                if (!options.seasonCollection) {
-                    throw 'seasonCollection is needed';
-                }
-
-                this.seasonCollection = options.seasonCollection;
-            },
-
-            itemViewOptions: function () {
-                return {
-                    seasonCollection: this.seasonCollection
-                };
-            }
+            itemView: SeriesLayout
         });
     });
diff --git a/UI/SeasonPass/SeriesLayout.js b/UI/SeasonPass/SeriesLayout.js
index 5de36f404..1eae0ce0e 100644
--- a/UI/SeasonPass/SeriesLayout.js
+++ b/UI/SeasonPass/SeriesLayout.js
@@ -3,10 +3,8 @@ define(
     [
         'marionette',
         'backgrid',
-        'Series/SeasonCollection',
-        'Cells/ToggleCell',
-        'Shared/Actioneer'
-    ], function (Marionette, Backgrid, SeasonCollection, ToggleCell, Actioneer) {
+        'Series/SeasonCollection'
+    ], function (Marionette, Backgrid, SeasonCollection) {
         return Marionette.Layout.extend({
             template: 'SeasonPass/SeriesLayoutTemplate',
 
@@ -19,48 +17,22 @@ define(
             events: {
                 'change .x-season-select': '_seasonSelected',
                 'click .x-expander'      : '_expand',
-                'click .x-latest'        : '_latest'
+                'click .x-latest'        : '_latest',
+                'click .x-monitored'     : '_toggleSeasonMonitored'
             },
 
             regions: {
                 seasonGrid: '.x-season-grid'
             },
 
-            columns:
-                [
-                    {
-                        name      : 'monitored',
-                        label     : '',
-                        cell      : ToggleCell,
-                        trueClass : 'icon-bookmark',
-                        falseClass: 'icon-bookmark-empty',
-                        tooltip   : 'Toggle monitored status',
-                        sortable  : false
-                    },
-                    {
-                        name : 'seasonNumber',
-                        label: 'Season',
-                        cell : Backgrid.IntegerCell.extend({
-                            className: 'season-number-cell'
-                        })
-                    }
-                ],
-
-            initialize: function (options) {
-                this.seasonCollection = options.seasonCollection.bySeries(this.model.get('id'));
-                this.model.set('seasons', this.seasonCollection);
+            initialize: function () {
+                this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
                 this.expanded = false;
             },
 
             onRender: function () {
-                this.seasonGrid.show(new Backgrid.Grid({
-                    columns   : this.columns,
-                    collection: this.seasonCollection,
-                    className : 'table table-condensed season-grid span5'
-                }));
-
                 if (!this.expanded) {
-                    this.seasonGrid.$el.hide();
+                    this.ui.seasonGrid.hide();
                 }
 
                 this._setExpanderIcon();
@@ -103,31 +75,49 @@ define(
             },
 
             _latest: function () {
-                var season = _.max(this.seasonCollection.models, function (model) {
-                    return model.get('seasonNumber');
+                var season = _.max(this.model.get('seasons'), function (s) {
+                    return s.seasonNumber;
                 });
 
-                //var seasonNumber = season.get('seasonNumber');
-
-                this._setMonitored(season.get('seasonNumber'))
+                this._setMonitored(season.seasonNumber);
             },
 
             _setMonitored: function (seasonNumber) {
                 var self = this;
 
-                var promise = $.ajax({
-                    url: this.seasonCollection.url + '/pass',
-                    type: 'POST',
-                    data: {
-                        seriesId: this.model.get('id'),
-                        seasonNumber: seasonNumber
-                    }
-                });
+                this.model.setSeasonPass(seasonNumber);
+
+                var promise = this.model.save();
 
                 promise.done(function (data) {
                     self.seasonCollection = new SeasonCollection(data);
                     self.render();
                 });
+            },
+
+            _toggleSeasonMonitored: function (e) {
+                var seasonNumber = 0;
+                var element;
+
+                if (e.target.localName === 'i') {
+                    seasonNumber = parseInt($(e.target).parent('td').attr('data-season-number'));
+                    element = $(e.target);
+                }
+
+                else {
+                    seasonNumber = parseInt($(e.target).attr('data-season-number'));
+                    element = $(e.target).children('i');
+                }
+
+                this.model.setSeasonMonitored(seasonNumber);
+
+                var savePromise =this.model.save()
+                    .always(this.render.bind(this));
+                element.spinForPromise(savePromise);
+            },
+
+            _afterToggleSeasonMonitored: function () {
+                this.render();
             }
         });
     });
diff --git a/UI/SeasonPass/SeriesLayoutTemplate.html b/UI/SeasonPass/SeriesLayoutTemplate.html
index 026718493..9c12a4483 100644
--- a/UI/SeasonPass/SeriesLayoutTemplate.html
+++ b/UI/SeasonPass/SeriesLayoutTemplate.html
@@ -12,12 +12,12 @@
             <span class="span3">
                 <select class="x-season-select season-select">
                     <option value="-1">Select season...</option>
-                    {{#each seasons.models}}
-                    {{#if_eq attributes.seasonNumber compare="0"}}
-                    <option value="{{attributes.seasonNumber}}">Specials</option>
-                    {{else}}
-                    <option value="{{attributes.seasonNumber}}">Season {{attributes.seasonNumber}}</option>
-                    {{/if_eq}}
+                    {{#each seasons}}
+                        {{#if_eq seasonNumber compare="0"}}
+                            <option value="{{seasonNumber}}">Specials</option>
+                        {{else}}
+                            <option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
+                        {{/if_eq}}
                     {{/each}}
                 </select>
 
@@ -36,7 +36,36 @@
 
     <div class="row">
         <div class="span11">
-            <div class="x-season-grid season-grid"></div>
+            <div class="x-season-grid season-grid">
+                <table class="table table-striped">
+                    <thead>
+                        <tr>
+                            <th></th>
+                            <th class="sortable">Season</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        {{#each seasons}}
+                            <tr>
+                                <td class="toggle-cell x-monitored" data-season-number="{{seasonNumber}}">
+                                    {{#if monitored}}
+                                        <i class="icon-nd-monitored"></i>
+                                    {{else}}
+                                        <i class="icon-nd-unmonitored"></i>
+                                    {{/if}}
+                                </td>
+                                <td>
+                                    {{#if_eq seasonNumber compare="0"}}
+                                        Specials
+                                    {{else}}
+                                        Season {{seasonNumber}}
+                                    {{/if_eq}}
+                                </td>
+                            </tr>
+                        {{/each}}
+                    </tbody>
+                </table>
+            </div>
         </div>
     </div>
 </div>
diff --git a/UI/Series/Details/InfoView.js b/UI/Series/Details/InfoView.js
index c3b5a6a31..60389b17d 100644
--- a/UI/Series/Details/InfoView.js
+++ b/UI/Series/Details/InfoView.js
@@ -5,6 +5,10 @@ define(
     ], function (Marionette) {
 
         return  Marionette.ItemView.extend({
-            template: 'Series/Details/InfoViewTemplate'
+            template: 'Series/Details/InfoViewTemplate',
+
+            initialize: function () {
+                this.listenTo(this.model, 'change', this.render);
+            }
         });
     });
diff --git a/UI/Series/Details/SeasonLayout.js b/UI/Series/Details/SeasonLayout.js
index 1244050e8..91b4d8d1c 100644
--- a/UI/Series/Details/SeasonLayout.js
+++ b/UI/Series/Details/SeasonLayout.js
@@ -1,6 +1,7 @@
 'use strict';
 define(
     [
+        'app',
         'marionette',
         'backgrid',
         'Cells/ToggleCell',
@@ -8,8 +9,8 @@ define(
         'Cells/RelativeDateCell',
         'Cells/EpisodeStatusCell',
         'Commands/CommandController',
-        'Shared/Actioneer'
-    ], function ( Marionette, Backgrid, ToggleCell, EpisodeTitleCell, RelativeDateCell, EpisodeStatusCell, CommandController, Actioneer) {
+        'moment'
+    ], function (App, Marionette, Backgrid, ToggleCell, EpisodeTitleCell, RelativeDateCell, EpisodeStatusCell, CommandController, Moment) {
         return Marionette.Layout.extend({
             template: 'Series/Details/SeasonLayoutTemplate',
 
@@ -20,13 +21,15 @@ define(
             },
 
             events: {
-                'click .x-season-search'   : '_seasonSearch',
-                'click .x-season-monitored': '_seasonMonitored',
-                'click .x-season-rename'   : '_seasonRename'
+                'click .x-season-monitored'  : '_seasonMonitored',
+                'click .x-season-search'     : '_seasonSearch',
+                'click .x-season-rename'     : '_seasonRename',
+                'click .x-show-hide-episodes': '_showHideEpisodes',
+                'dblclick .series-season h2' : '_showHideEpisodes'
             },
 
             regions: {
-                episodeGrid: '#x-episode-grid'
+                episodeGrid: '.x-episode-grid'
             },
 
             columns:
@@ -48,11 +51,11 @@ define(
                         })
                     },
                     {
-                        name    : 'this',
-                        label   : 'Title',
-                        hideSeriesLink : true,
-                        cell    : EpisodeTitleCell,
-                        sortable: false
+                        name          : 'this',
+                        label         : 'Title',
+                        hideSeriesLink: true,
+                        cell          : EpisodeTitleCell,
+                        sortable      : false
                     },
                     {
                         name : 'airDateUtc',
@@ -74,48 +77,68 @@ define(
                 }
 
                 this.episodeCollection = options.episodeCollection.bySeason(this.model.get('seasonNumber'));
+                this.series = options.series;
 
-                this.listenTo(this.model, 'sync', function () {
-                    this._afterSeasonMonitored();
-                }, this);
+                this.showingEpisodes = this._shouldShowEpisodes();
 
-                this.listenTo(this.episodeCollection, 'sync', function () {
-                    this.render();
-                }, this);
+                this.listenTo(this.model, 'sync', this._afterSeasonMonitored);
+                this.listenTo(this.episodeCollection, 'sync', this.render);
             },
 
             onRender: function () {
-                this.episodeGrid.show(new Backgrid.Grid({
-                    columns   : this.columns,
-                    collection: this.episodeCollection,
-                    className : 'table table-hover season-grid'
-                }));
+
+
+                if (this.showingEpisodes) {
+                    this._showEpisodes();
+                }
 
                 this._setSeasonMonitoredState();
+
+                CommandController.bindToCommand({
+                    element: this.ui.seasonSearch,
+                    command: {
+                        name        : 'seasonSearch',
+                        seriesId    : this.series.id,
+                        seasonNumber: this.model.get('seasonNumber')
+                    }
+                });
+
+                CommandController.bindToCommand({
+                    element: this.ui.seasonRename,
+                    command: {
+                        name        : 'renameSeason',
+                        seriesId    : this.series.id,
+                        seasonNumber: this.model.get('seasonNumber')
+                    }
+                });
             },
 
             _seasonSearch: function () {
-                Actioneer.ExecuteCommand({
-                    command     : 'seasonSearch',
-                    properties  : {
-                        seriesId    : this.model.get('seriesId'),
-                        seasonNumber: this.model.get('seasonNumber')
-                    },
-                    element     : this.ui.seasonSearch,
-                    failMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')),
-                    startMessage: 'Search for season {0} started'.format(this.model.get('seasonNumber'))
+
+                CommandController.Execute('seasonSearch', {
+                    name        : 'seasonSearch',
+                    seriesId    : this.series.id,
+                    seasonNumber: this.model.get('seasonNumber')
+                });
+            },
+
+            _seasonRename: function () {
+
+                CommandController.Execute('renameSeason', {
+                    name        : 'renameSeason',
+                    seriesId    : this.series.id,
+                    seasonNumber: this.model.get('seasonNumber')
                 });
             },
 
             _seasonMonitored: function () {
                 var name = 'monitored';
                 this.model.set(name, !this.model.get(name));
+                this.series.setSeasonMonitored(this.model.get('seasonNumber'));
 
-                Actioneer.SaveModel({
-                    context: this,
-                    element: this.ui.seasonMonitored,
-                    always : this._afterSeasonMonitored
-                });
+                var savePromise = this.series.save().always(this._afterSeasonMonitored.bind(this));
+
+                this.ui.seasonMonitored.spinForPromise(savePromise);
             },
 
             _afterSeasonMonitored: function () {
@@ -141,16 +164,72 @@ define(
                 }
             },
 
-            _seasonRename: function () {
-                Actioneer.ExecuteCommand({
-                    command    : 'renameSeason',
-                    properties : {
-                        seriesId    : this.model.get('seriesId'),
-                        seasonNumber: this.model.get('seasonNumber')
-                    },
-                    element    : this.ui.seasonRename,
-                    failMessage: 'Season rename failed'
+
+            _afterRename: function () {
+                App.vent.trigger(App.Events.SeasonRenamed, { series: this.series, seasonNumber: this.model.get('seasonNumber') });
+            },
+
+            _showEpisodes: function () {
+                this.episodeGrid.show(new Backgrid.Grid({
+                    columns   : this.columns,
+                    collection: this.episodeCollection,
+                    className : 'table table-hover season-grid'
+                }));
+            },
+
+            _shouldShowEpisodes: function () {
+                var startDate = Moment().add('month', -1);
+                var endDate = Moment().add('year', 1);
+
+                return this.episodeCollection.some(function (episode) {
+
+                    var airDate = episode.get('airDateUtc');
+
+                    if (airDate) {
+                        var airDateMoment = Moment(airDate);
+
+                        if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) {
+                            return true;
+                        }
+                    }
+
+                    return false;
                 });
+            },
+
+            templateHelpers: function () {
+
+                var episodeCount = this.episodeCollection.filter(function (episode) {
+                    return episode.get('hasFile') || (episode.get('monitored') && Moment(episode.get('airDateUtc')).isBefore(Moment()));
+                }).length;
+
+                var episodeFileCount = this.episodeCollection.where({ hasFile: true }).length;
+                var percentOfEpisodes = 100;
+
+                if (episodeCount > 0) {
+                    percentOfEpisodes = episodeFileCount / episodeCount * 100;
+                }
+
+                return {
+                    showingEpisodes  : this.showingEpisodes,
+                    episodeCount     : episodeCount,
+                    episodeFileCount : episodeFileCount,
+                    percentOfEpisodes: percentOfEpisodes
+                };
+            },
+
+            _showHideEpisodes: function () {
+                if (this.showingEpisodes) {
+                    this.showingEpisodes = false;
+                    this.episodeGrid.close();
+                }
+                else {
+                    this.showingEpisodes = true;
+                    this._showEpisodes();
+                }
+
+                this.templateHelpers.showingEpisodes = this.showingEpisodes;
+                this.render();
             }
         });
     });
diff --git a/UI/Series/Details/SeasonLayoutTemplate.html b/UI/Series/Details/SeasonLayoutTemplate.html
index eb02bf938..4b401c2e6 100644
--- a/UI/Series/Details/SeasonLayoutTemplate.html
+++ b/UI/Series/Details/SeasonLayoutTemplate.html
@@ -1,15 +1,42 @@
 <div class="series-season" id="season-{{seasonNumber}}">
     <h2>
         <i class="x-season-monitored clickable" title="Toggle season monitored status"/>
+
         {{#if seasonNumber}}
-            Season {{seasonNumber}}
+        Season {{seasonNumber}}
         {{else}}
-            Specials
+        Specials
         {{/if}}
+
+        {{#if_eq episodeCount compare=0}}
+        <i class="icon-nd-status season-status status-primary" title="No aired episodes"/>
+        {{else}}
+        {{#if_eq percentOfEpisodes compare=100}}
+        <i class="icon-nd-status season-status status-success" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
+        {{else}}
+        <i class="icon-nd-status season-status status-danger" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
+        {{/if_eq}}
+        {{/if_eq}}
+
         <span class="season-actions pull-right">
-            <i class="icon-nd-rename x-season-rename" title="Rename  all episodes in season {{seasonNumber}}"/>
-            <i class="icon-search x-season-search" title="Search for all episodes in season {{seasonNumber}}"/>
+            <div class="x-season-rename">
+                <i class="icon-nd-rename" title="Rename  all episodes in season {{seasonNumber}}"/>
+            </div>
+            <div class="x-season-search">
+                <i class="icon-search" title="Search for all episodes in season {{seasonNumber}}"/>
+            </div>
         </span>
     </h2>
-    <div id="x-episode-grid"/>
+    <div class="x-episode-grid"></div>
+    <div class="show-hide-episodes x-show-hide-episodes">
+        <h4>
+            {{#if showingEpisodes}}
+            <i class="icon-chevron-sign-up"/>
+            Hide Episodes
+            {{else}}
+            <i class="icon-chevron-sign-down"/>
+            Show Episodes
+            {{/if}}
+        </h4>
+    </div>
 </div>
diff --git a/UI/Series/Details/SeasonMenu/CollectionView.js b/UI/Series/Details/SeasonMenu/CollectionView.js
deleted file mode 100644
index 32cfafd60..000000000
--- a/UI/Series/Details/SeasonMenu/CollectionView.js
+++ /dev/null
@@ -1,31 +0,0 @@
-'use strict';
-define(
-    [
-        'marionette',
-        'Series/Details/SeasonMenu/ItemView'
-    ], function (Marionette, ItemView) {
-        return Marionette.CollectionView.extend({
-
-            itemView: ItemView,
-
-            initialize: function (options) {
-
-                if (!options.episodeCollection) {
-                    throw 'episodeCollection is needed';
-                }
-
-                this.episodeCollection = options.episodeCollection;
-            },
-
-            itemViewOptions: function () {
-                return {
-                    episodeCollection: this.episodeCollection
-                };
-            },
-
-            appendHtml: function(collectionView, itemView){
-                var childrenContainer = $(collectionView.childrenContainer || collectionView.el);
-                childrenContainer.prepend(itemView.el);
-            }
-        });
-    });
diff --git a/UI/Series/Details/SeasonMenu/ItemView.js b/UI/Series/Details/SeasonMenu/ItemView.js
deleted file mode 100644
index 2ffe52418..000000000
--- a/UI/Series/Details/SeasonMenu/ItemView.js
+++ /dev/null
@@ -1,88 +0,0 @@
-'use strict';
-define(
-    [
-        'marionette',
-        'Shared/Actioneer'
-    ], function (Marionette, Actioneer) {
-        return Marionette.ItemView.extend({
-            template: 'Series/Details/SeasonMenu/ItemViewTemplate',
-            tagName : 'span',
-
-            ui: {
-                seasonMonitored: '.x-season-monitored'
-            },
-
-            events: {
-                'click .x-season-monitored': '_seasonMonitored',
-                'click .x-text': '_gotoSeason'
-            },
-
-            initialize: function (options) {
-
-                if (!options.episodeCollection) {
-                    throw 'episodeCollection is needed';
-                }
-
-                this.episodeCollection = options.episodeCollection.bySeason(this.model.get('seasonNumber'));
-
-                var allDownloaded = _.all(this.episodeCollection.models, function (model) {
-                    var hasFile = model.get('hasFile');
-                    return hasFile;
-                });
-
-                this.model.set({
-                   allFilesDownloaded: allDownloaded
-                });
-
-                this.listenTo(this.model, 'sync', function () {
-                    this.render();
-                }, this);
-            },
-
-            onRender: function () {
-                this._setSeasonMonitoredState();
-            },
-
-            _seasonSearch: function () {
-                Actioneer.ExecuteCommand({
-                    command     : 'seasonSearch',
-                    properties  : {
-                        seriesId    : this.model.get('seriesId'),
-                        seasonNumber: this.model.get('seasonNumber')
-                    },
-                    element     : this.ui.seasonSearch,
-                    failMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')),
-                    startMessage: 'Search for season {0} started'.format(this.model.get('seasonNumber'))
-                });
-            },
-
-            _seasonMonitored: function (e) {
-                e.preventDefault();
-
-                var name = 'monitored';
-                this.model.set(name, !this.model.get(name));
-
-                Actioneer.SaveModel({
-                    context       : this,
-                    element       : this.ui.seasonMonitored
-                });
-            },
-
-            _setSeasonMonitoredState: function () {
-                this.ui.seasonMonitored.removeClass('icon-spinner icon-spin');
-
-                if (this.model.get('monitored')) {
-                    this.ui.seasonMonitored.addClass('icon-bookmark');
-                    this.ui.seasonMonitored.removeClass('icon-bookmark-empty');
-                }
-                else {
-                    this.ui.seasonMonitored.addClass('icon-bookmark-empty');
-                    this.ui.seasonMonitored.removeClass('icon-bookmark');
-                }
-            },
-
-            _gotoSeason: function () {
-                window.location.hash = '#season-' + this.model.get('seasonNumber');
-            }
-        });
-    });
diff --git a/UI/Series/Details/SeasonMenu/ItemViewTemplate.html b/UI/Series/Details/SeasonMenu/ItemViewTemplate.html
deleted file mode 100644
index 8b0283ff6..000000000
--- a/UI/Series/Details/SeasonMenu/ItemViewTemplate.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{{#if allFilesDownloaded}}
-<span class="label label-info season-menu-item">
-{{else}}
-<span class="label label-white season-menu-item">
-{{/if}}
-    <i class="x-season-monitored clickable" title="Toggle season monitored status"/>
-    <span class="x-text text" title="Go to season">
-        {{#if_eq seasonNumber compare=0}}
-            Specials
-        {{else}}
-            S{{Pad2 seasonNumber}}
-        {{/if_eq}}
-    </span>
-</span>
\ No newline at end of file
diff --git a/UI/Series/Details/SeriesDetailsLayout.js b/UI/Series/Details/SeriesDetailsLayout.js
index 8f073cc96..ec6fc38c9 100644
--- a/UI/Series/Details/SeriesDetailsLayout.js
+++ b/UI/Series/Details/SeriesDetailsLayout.js
@@ -7,31 +7,20 @@ define(
         'Series/EpisodeFileCollection',
         'Series/SeasonCollection',
         'Series/Details/SeasonCollectionView',
-        'Series/Details/SeasonMenu/CollectionView',
         'Series/Details/InfoView',
+        'Commands/CommandController',
         'Shared/LoadingView',
-        'Shared/Actioneer',
         'backstrech',
         'Mixins/backbone.signalr.mixin'
-    ], function (App,
-                 Marionette,
-                 EpisodeCollection,
-                 EpisodeFileCollection,
-                 SeasonCollection,
-                 SeasonCollectionView,
-                 SeasonMenuCollectionView,
-                 InfoView,
-                 LoadingView,
-                 Actioneer) {
+    ], function (App, Marionette, EpisodeCollection, EpisodeFileCollection, SeasonCollection, SeasonCollectionView, InfoView, CommandController, LoadingView) {
         return Marionette.Layout.extend({
 
             itemViewContainer: '.x-series-seasons',
             template         : 'Series/Details/SeriesDetailsTemplate',
 
             regions: {
-                seasonMenu: '#season-menu',
-                seasons   : '#seasons',
-                info      : '#info'
+                seasons: '#seasons',
+                info   : '#info'
             },
 
             ui: {
@@ -54,12 +43,9 @@ define(
             initialize: function () {
                 $('body').addClass('backdrop');
 
-                this.listenTo(this.model, 'sync', function () {
-                    this._setMonitoredState();
-                    this._showInfo();
-                }, this);
-
+                this.listenTo(this.model, 'change:monitored', this._setMonitoredState);
                 this.listenTo(App.vent, App.Events.SeriesDeleted, this._onSeriesDeleted);
+                this.listenTo(App.vent, App.Events.SeasonRenamed, this._onSeasonRenamed);
             },
 
             onShow: function () {
@@ -75,6 +61,31 @@ define(
                 this._showSeasons();
                 this._setMonitoredState();
                 this._showInfo();
+
+
+            },
+
+            onRender: function () {
+                CommandController.bindToCommand({
+                    element: this.ui.refresh,
+                    command: {
+                        name: 'refreshSeries'
+                    }
+                });
+
+                CommandController.bindToCommand({
+                    element: this.ui.search,
+                    command: {
+                        name: 'seriesSearch'
+                    }
+                });
+
+                CommandController.bindToCommand({
+                    element: this.ui.rename,
+                    command: {
+                        name: 'renameSeries'
+                    }
+                });
             },
 
             _getFanArt: function () {
@@ -99,28 +110,25 @@ define(
             },
 
             _toggleMonitored: function () {
-                var name = 'monitored';
-                this.model.set(name, !this.model.get(name), { silent: true });
-
-                Actioneer.SaveModel({
-                    context: this,
-                    element: this.ui.monitored,
-                    always : this._setMonitoredState()
+                var savePromise = this.model.save('monitored', !this.model.get('monitored'), {
+                    wait: true
                 });
+
+                this.ui.monitored.spinForPromise(savePromise);
             },
 
             _setMonitoredState: function () {
                 var monitored = this.model.get('monitored');
 
-                this.ui.monitored.removeClass('icon-spin icon-spinner');
+                this.ui.monitored.removeAttr('data-idle-icon');
 
-                if (this.model.get('monitored')) {
-                    this.ui.monitored.addClass('icon-bookmark');
-                    this.ui.monitored.removeClass('icon-bookmark-empty');
+                if (monitored) {
+                    this.ui.monitored.addClass('icon-nd-monitored');
+                    this.ui.monitored.removeClass('icon-nd-unmonitored');
                 }
                 else {
-                    this.ui.monitored.addClass('icon-bookmark-empty');
-                    this.ui.monitored.removeClass('icon-bookmark');
+                    this.ui.monitored.addClass('icon-nd-unmonitored');
+                    this.ui.monitored.removeClass('icon-nd-monitored');
                 }
             },
 
@@ -129,15 +137,9 @@ define(
             },
 
             _refreshSeries: function () {
-                Actioneer.ExecuteCommand({
-                    command   : 'refreshSeries',
-                    properties: {
-                        seriesId: this.model.get('id')
-                    },
-                    element   : this.ui.refresh,
-                    leaveIcon : true,
-                    context   : this,
-                    onSuccess : this._showSeasons
+                CommandController.Execute('refreshSeries', {
+                    name    : 'refreshSeries',
+                    seriesId: this.model.id
                 });
             },
 
@@ -149,27 +151,18 @@ define(
             },
 
             _renameSeries: function () {
-                Actioneer.ExecuteCommand({
-                    command    : 'renameSeries',
-                    properties : {
-                        seriesId: this.model.get('id')
-                    },
-                    element    : this.ui.rename,
-                    context   : this,
-                    onSuccess : this._refetchEpisodeFiles,
-                    failMessage: 'Series search failed'
+
+                CommandController.Execute('renameSeries', {
+                    name    : 'renameSeries',
+                    seriesId: this.model.id
                 });
+
             },
 
             _seriesSearch: function () {
-                Actioneer.ExecuteCommand({
-                    command     : 'seriesSearch',
-                    properties  : {
-                        seriesId: this.model.get('id')
-                    },
-                    element     : this.ui.search,
-                    failMessage : 'Series search failed',
-                    startMessage: 'Search for {0} started'.format(this.model.get('title'))
+                CommandController.Execute('seriesSearch', {
+                    name    : 'seriesSearch',
+                    seriesId: this.model.id
                 });
             },
 
@@ -178,32 +171,22 @@ define(
 
                 this.seasons.show(new LoadingView());
 
-                this.seasonCollection = new SeasonCollection();
+                this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
                 this.episodeCollection = new EpisodeCollection({ seriesId: this.model.id });
                 this.episodeFileCollection = new EpisodeFileCollection({ seriesId: this.model.id });
 
-                $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch(), this.seasonCollection.fetch({data: { seriesId: this.model.id }})).done(function () {
+                $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function () {
                     var seasonCollectionView = new SeasonCollectionView({
                         collection       : self.seasonCollection,
                         episodeCollection: self.episodeCollection,
                         series           : self.model
                     });
 
-                    App.reqres.setHandler(App.Reqres.GetEpisodeFileById, function(episodeFileId){
+                    App.reqres.setHandler(App.Reqres.GetEpisodeFileById, function (episodeFileId) {
                         return self.episodeFileCollection.get(episodeFileId);
                     });
 
- /*                   self.episodeCollection.bindSignalR({
-                        onReceived: seasonCollectionView.onEpisodeGrabbed,
-                        context   : seasonCollectionView
-                    });*/
-
                     self.seasons.show(seasonCollectionView);
-
-                    self.seasonMenu.show(new SeasonMenuCollectionView({
-                        collection       : self.seasonCollection,
-                        episodeCollection: self.episodeCollection
-                    }));
                 });
             },
 
@@ -211,8 +194,10 @@ define(
                 this.info.show(new InfoView({ model: this.model }));
             },
 
-            _refetchEpisodeFiles: function () {
-                this.episodeFileCollection.fetch();
+            _onSeasonRenamed: function (event) {
+                if (this.model.get('id') === event.series.get('id')) {
+                    this.episodeFileCollection.fetch();
+                }
             }
         });
     });
diff --git a/UI/Series/Details/SeriesDetailsTemplate.html b/UI/Series/Details/SeriesDetailsTemplate.html
index cef95f4e3..1aa4ed9fe 100644
--- a/UI/Series/Details/SeriesDetailsTemplate.html
+++ b/UI/Series/Details/SeriesDetailsTemplate.html
@@ -2,14 +2,23 @@
     <div class="span11">
         <div class="row">
             <h1>
-                <i class="icon-bookmark x-monitored clickable" title="Toggle monitored state for entire series"/>
+                <i class="x-monitored clickable series-monitor-toggle" title="Toggle monitored state for entire series"/>
                 {{title}}
-                <span class="series-actions pull-right">
-                    <i class="icon-refresh x-refresh" title="Update series info and scan disk"/>
-                    <i class="icon-nd-rename x-rename" title="Rename all episodes in this series"/>
-                    <i class="icon-search x-search" title="Search for all episodes in this series"/>
-                    <i class="icon-nd-edit x-edit" title="Edit series"/>
-                </span>
+                <div class="series-actions pull-right">
+                    <div class="x-refresh">
+                        <i class="icon-refresh icon-can-spin" title="Update series info and scan disk"/>
+                    </div>
+                    <div class="x-rename">
+                        <i class="icon-nd-rename" title="Rename all episodes in this series"/>
+                    </div>
+                    <div class="x-search">
+                        <i class="icon-search" title="Search for all episodes in this series"/>
+                    </div>
+                    <div class="x-edit">
+                        <i class="icon-nd-edit" title="Edit series"/>
+                    </div>
+
+                </div>
             </h1>
         </div>
         <div class="row series-detail-overview">
@@ -18,9 +27,6 @@
         <div id="info" class="row">
 
         </div>
-        <div class="row">
-            <div id="season-menu" class="season-menu"></div>
-        </div>
     </div>
 </div>
 <div id="seasons"></div>
diff --git a/UI/Series/EpisodeCollection.js b/UI/Series/EpisodeCollection.js
index 52aa024e9..3bd6d9979 100644
--- a/UI/Series/EpisodeCollection.js
+++ b/UI/Series/EpisodeCollection.js
@@ -5,7 +5,7 @@ define(
         'Series/EpisodeModel'
     ], function (Backbone, EpisodeModel) {
         return Backbone.Collection.extend({
-            url  : window.ApiRoot + '/episodes',
+            url  : window.NzbDrone.ApiRoot + '/episodes',
             model: EpisodeModel,
 
             state: {
diff --git a/UI/Series/EpisodeFileCollection.js b/UI/Series/EpisodeFileCollection.js
index 2cac898ea..5cd65aaad 100644
--- a/UI/Series/EpisodeFileCollection.js
+++ b/UI/Series/EpisodeFileCollection.js
@@ -5,7 +5,7 @@ define(
         'Series/EpisodeFileModel'
     ], function (Backbone, EpisodeFileModel) {
         return Backbone.Collection.extend({
-            url  : window.ApiRoot + '/episodefile',
+            url  : window.NzbDrone.ApiRoot + '/episodefile',
             model: EpisodeFileModel,
 
             originalFetch: Backbone.Collection.prototype.fetch,
diff --git a/UI/Series/Index/EpisodeProgressPartial.html b/UI/Series/Index/EpisodeProgressPartial.html
index b4f5931e8..51ff6588d 100644
--- a/UI/Series/Index/EpisodeProgressPartial.html
+++ b/UI/Series/Index/EpisodeProgressPartial.html
@@ -1,12 +1,12 @@
 {{#if_eq episodeFileCount compare=episodeCount}}
     {{#if_eq status compare="continuing"}}
-        <div class="progress">
+        <div class="progress episode-progress">
     {{else}}
-        <div class="progress progress-success">
+        <div class="progress progress-success episode-progress">
     {{/if_eq}}
 
 {{else}}
-    <div class="progress progress-danger">
+    <div class="progress progress-danger episode-progress">
 {{/if_eq}}
 
     <span class="progressbar-back-text">{{episodeFileCount}} / {{episodeCount}}</span>
diff --git a/UI/Series/Index/SeriesIndexLayout.js b/UI/Series/Index/SeriesIndexLayout.js
index b35234589..8b7a48c12 100644
--- a/UI/Series/Index/SeriesIndexLayout.js
+++ b/UI/Series/Index/SeriesIndexLayout.js
@@ -101,11 +101,15 @@ define(
                             icon : 'icon-plus',
                             route: 'addseries'
                         },
+                        {
+                            title  : 'Season Pass',
+                            icon   : 'icon-bookmark',
+                            route  : 'seasonpass'
+                        },
                         {
                             title         : 'RSS Sync',
                             icon          : 'icon-rss',
                             command       : 'rsssync',
-                            successMessage: 'RSS Sync Completed',
                             errorMessage  : 'RSS Sync Failed!'
                         },
                         {
@@ -140,7 +144,6 @@ define(
                 this._fetchCollection();
             },
 
-
             initialize: function () {
                 this.seriesCollection = SeriesCollection;
 
@@ -148,7 +151,6 @@ define(
                 this.listenTo(SeriesCollection, 'remove', this._renderView);
             },
 
-
             _renderView: function () {
 
                 if (SeriesCollection.length === 0) {
@@ -164,7 +166,6 @@ define(
                 }
             },
 
-
             onShow: function () {
                 this._showToolbar();
                 this._renderView();
diff --git a/UI/Series/SeasonCollection.js b/UI/Series/SeasonCollection.js
index 68e312ff1..35984204a 100644
--- a/UI/Series/SeasonCollection.js
+++ b/UI/Series/SeasonCollection.js
@@ -5,21 +5,10 @@ define(
         'Series/SeasonModel'
     ], function (Backbone, SeasonModel) {
         return Backbone.Collection.extend({
-            url  : window.ApiRoot + '/season',
             model: SeasonModel,
 
             comparator: function (season) {
                 return -season.get('seasonNumber');
-            },
-
-            bySeries: function (series) {
-                var filtered = this.filter(function (season) {
-                    return season.get('seriesId') === series;
-                });
-
-                var SeasonCollection = require('Series/SeasonCollection');
-
-                return new SeasonCollection(filtered);
             }
         });
     });
diff --git a/UI/Series/SeriesCollection.js b/UI/Series/SeriesCollection.js
index 7c61ac73a..188382775 100644
--- a/UI/Series/SeriesCollection.js
+++ b/UI/Series/SeriesCollection.js
@@ -5,7 +5,7 @@ define(
         'Series/SeriesModel'
     ], function (Backbone, SeriesModel) {
         var Collection = Backbone.Collection.extend({
-            url  : window.ApiRoot + '/series',
+            url  : window.NzbDrone.ApiRoot + '/series',
             model: SeriesModel,
 
             comparator: function (model) {
diff --git a/UI/Series/SeriesModel.js b/UI/Series/SeriesModel.js
index 075e0fc80..e73d9cc33 100644
--- a/UI/Series/SeriesModel.js
+++ b/UI/Series/SeriesModel.js
@@ -7,13 +7,32 @@ define(
     ], function (Backbone, _) {
         return Backbone.Model.extend({
 
-            urlRoot: ApiRoot + '/series',
+            urlRoot: window.NzbDrone.ApiRoot + '/series',
 
             defaults: {
                 episodeFileCount: 0,
                 episodeCount    : 0,
                 isExisting      : false,
                 status          : 0
+            },
+
+            setSeasonMonitored: function (seasonNumber) {
+                _.each(this.get('seasons'), function (season) {
+                    if (season.seasonNumber === seasonNumber) {
+                        season.monitored = !season.monitored;
+                    }
+                });
+            },
+
+            setSeasonPass: function (seasonNumber) {
+                _.each(this.get('seasons'), function (season) {
+                    if (season.seasonNumber >= seasonNumber) {
+                        season.monitored = true;
+                    }
+                    else {
+                        season.monitored = false;
+                    }
+                });
             }
         });
     });
diff --git a/UI/Series/series.less b/UI/Series/series.less
index f405ea210..e80f978b1 100644
--- a/UI/Series/series.less
+++ b/UI/Series/series.less
@@ -6,18 +6,18 @@
   overflow : visible;
 
   .series-poster {
-    padding-left: 20px;
-    width : 168px;
+    padding-left : 20px;
+    width        : 168px;
   }
 
   .form-horizontal {
-    margin-top: 10px;
+    margin-top : 10px;
   }
 }
 
 .delete-series-modal {
   .path {
-    margin-left: 30px;
+    margin-left : 30px;
   }
 }
 
@@ -48,26 +48,16 @@
 
 .series-season {
   .card;
-  margin  : 80px 10px;
-  padding : 20px 40px;
+  margin  : 30px 10px;
+  padding : 10px 25px;
   .opacity(0.9);
-}
-
-.season-menu {
-  margin-top: 5px;
-
-  .season-menu-item {
-
-    .text {
-      .clickable;
-    }
 
-    a:hover {
-      text-decoration: none;
-    }
+  .show-hide-episodes {
+    .clickable();
+    text-align : center;
 
-    i:before {
-      width: 10px;
+    i {
+      .clickable();
     }
   }
 }
@@ -94,24 +84,24 @@
       text-align   : center;
 
       .progress {
-        text-align: left;
+        text-align : left;
         margin-top : 5px;
-        left: 0px;
-        width: 170px;
+        left       : 0px;
+        width      : 170px;
 
         .progressbar-front-text {
-          width: 170px;
+          width : 170px;
         }
       }
     }
 
     .title-container {
-      position: relative;
+      position : relative;
 
       .title {
         position : absolute;
-        top        : -100px;
-        opacity: 0.0;
+        top      : -100px;
+        opacity  : 0.0;
       }
     }
 
@@ -198,7 +188,7 @@
 .episode-detail-modal {
 
   .episode-info {
-    margin-bottom: 10px;
+    margin-bottom : 10px;
   }
 
   .episode-overview {
@@ -231,44 +221,56 @@
 }
 
 .season-actions, .series-actions {
-  font-size      : 24px;
+
+  div {
+    display : inline-block
+  }
+
   text-transform : none;
 
   i {
     .clickable;
+    font-size    : 24px;
+    padding-left : 5px;
   }
 }
 
 .series-stats {
-  font-size: 11px;
+  font-size : 11px;
 }
 
 .series-legend {
-  padding-top: 5px;
+  padding-top : 5px;
 }
 
 .seasonpass-series {
   .card;
-  margin  : 20px 0px;
+  margin : 20px 0px;
 
   .title {
-    font-weight: 300;
-    font-size: 24px;
-    line-height: 30px;
-    margin-left: 5px;
+    font-weight : 300;
+    font-size   : 24px;
+    line-height : 30px;
+    margin-left : 5px;
   }
 
   .season-select {
-    margin-bottom: 0px;
+    margin-bottom : 0px;
   }
 
   .expander {
     .clickable;
     line-height: 30px;
     margin-left: 8px;
+    width: 16px;
   }
 
   .season-grid {
-    margin-top: 10px;
+    margin-top : 10px;
   }
-}
\ No newline at end of file
+}
+
+.season-status {
+  font-size      : 16px;
+  vertical-align : middle !important;
+}
diff --git a/UI/ServerStatus.js b/UI/ServerStatus.js
index ff01fb00e..1a5da687b 100644
--- a/UI/ServerStatus.js
+++ b/UI/ServerStatus.js
@@ -1,18 +1,19 @@
-window.ApiRoot = '/api';
+window.NzbDrone = {};
+window.NzbDrone.ApiRoot = '/api';
 
 var statusText = $.ajax({
     type : 'GET',
-    url  : window.ApiRoot + '/system/status',
+    url  : window.NzbDrone.ApiRoot + '/system/status',
     async: false
 }).responseText;
 
-window.ServerStatus = JSON.parse(statusText);
+window.NzbDrone.ServerStatus = JSON.parse(statusText);
 
-var footerText = window.ServerStatus.version;
+var footerText = window.NzbDrone.ServerStatus.version;
 
 $(document).ready(function () {
-    if (window.ServerStatus.branch != 'master') {
-        footerText += '</br>' + window.ServerStatus.branch;
+    if (window.NzbDrone.ServerStatus.branch != 'master') {
+        footerText += '</br>' + window.NzbDrone.ServerStatus.branch;
     }
     $('#footer-region .version').html(footerText);
 });
diff --git a/UI/Settings/General/GeneralSettingsModel.js b/UI/Settings/General/GeneralSettingsModel.js
index f8854135e..c458cec87 100644
--- a/UI/Settings/General/GeneralSettingsModel.js
+++ b/UI/Settings/General/GeneralSettingsModel.js
@@ -5,7 +5,7 @@ define(
     ], function (SettingsModelBase) {
         return SettingsModelBase.extend({
 
-            url           : window.ApiRoot + '/settings/host',
+            url           : window.NzbDrone.ApiRoot + '/settings/host',
             successMessage: 'General settings saved',
             errorMessage  : 'Failed to save general settings'
 
diff --git a/UI/Settings/General/GeneralTemplate.html b/UI/Settings/General/GeneralTemplate.html
index 5c9cf3ebc..bb9de7626 100644
--- a/UI/Settings/General/GeneralTemplate.html
+++ b/UI/Settings/General/GeneralTemplate.html
@@ -90,4 +90,21 @@
             </div>
         </div>
     </fieldset>
+
+    {{#unless_eq branch compare="master"}}
+    <fieldset>
+        <legend>Development</legend>
+        <div class="alert">
+            <i class="icon-nd-warning"></i>
+            Don't change anything here unless you know what you are doing.
+        </div>
+        <div class="control-group">
+            <label class="control-label">Branch</label>
+
+            <div class="controls">
+                <input type="text" placeholder="master" name="branch"/>
+            </div>
+        </div>
+    </fieldset>
+    {{/unless_eq}}
 </div>
diff --git a/UI/Settings/Indexers/Collection.js b/UI/Settings/Indexers/Collection.js
index 76a8fbe0e..234c97da7 100644
--- a/UI/Settings/Indexers/Collection.js
+++ b/UI/Settings/Indexers/Collection.js
@@ -5,7 +5,7 @@ define(
         'Form/FormBuilder'
     ], function (IndexerModel) {
         return Backbone.Collection.extend({
-            url  : window.ApiRoot + '/indexer',
+            url  : window.NzbDrone.ApiRoot + '/indexer',
             model: IndexerModel
         });
     });
diff --git a/UI/Settings/Indexers/ItemTemplate.html b/UI/Settings/Indexers/ItemTemplate.html
index 617fb94aa..87acb9e0b 100644
--- a/UI/Settings/Indexers/ItemTemplate.html
+++ b/UI/Settings/Indexers/ItemTemplate.html
@@ -3,7 +3,9 @@
         <h3>{{name}}</h3>
         {{#if_eq implementation compare="Newznab"}}
             <span class="btn-group pull-right">
-                <button class="btn btn-mini btn-icon-only x-delete"><i class="icon-nd-delete"/></button>
+                <button class="btn btn-mini btn-icon-only x-delete">
+                    <i class="icon-nd-delete"/>
+                </button>
             </span>
         {{/if_eq}}
     </div>
@@ -27,8 +29,9 @@
     {{formBuilder}}
 
     {{#if_eq name compare="WomblesIndex"}}
-        <div class="alert">
-            <strong>Warning!</strong> Does not support searching
-        </div>
+    <div class="alert">
+        <i class="icon-nd-warning"></i>
+        Does not support searching
+    </div>
     {{/if_eq}}
 </div>
diff --git a/UI/Settings/MediaManagement/Naming/Model.js b/UI/Settings/MediaManagement/Naming/Model.js
index cb8028556..dbb1e0bf7 100644
--- a/UI/Settings/MediaManagement/Naming/Model.js
+++ b/UI/Settings/MediaManagement/Naming/Model.js
@@ -4,7 +4,7 @@ define(
         'Settings/SettingsModelBase'
     ], function (ModelBase) {
         return  ModelBase.extend({
-            url           : window.ApiRoot + '/config/naming',
+            url           : window.NzbDrone.ApiRoot + '/config/naming',
             successMessage: 'MediaManagement settings saved',
             errorMessage  : 'Couldn\'t save naming settings'
         });
diff --git a/UI/Settings/MediaManagement/Naming/View.js b/UI/Settings/MediaManagement/Naming/View.js
index d33b0bbc0..8cd0027d2 100644
--- a/UI/Settings/MediaManagement/Naming/View.js
+++ b/UI/Settings/MediaManagement/Naming/View.js
@@ -45,7 +45,7 @@ define(
 
                 var promise = $.ajax({
                     type: 'GET',
-                    url : window.ApiRoot + '/config/naming/samples',
+                    url : window.NzbDrone.ApiRoot + '/config/naming/samples',
                     data: this.model.toJSON()
                 });
 
diff --git a/UI/Settings/Notifications/Collection.js b/UI/Settings/Notifications/Collection.js
index a8dd48449..00367ffdf 100644
--- a/UI/Settings/Notifications/Collection.js
+++ b/UI/Settings/Notifications/Collection.js
@@ -4,7 +4,7 @@ define(
         'Settings/Notifications/Model'
     ], function (NotificationModel) {
         return Backbone.Collection.extend({
-            url  : window.ApiRoot + '/notification',
+            url  : window.NzbDrone.ApiRoot + '/notification',
             model: NotificationModel
         });
     });
diff --git a/UI/Settings/Notifications/EditTemplate.html b/UI/Settings/Notifications/EditTemplate.html
index dba9e30f9..91e9ecdb7 100644
--- a/UI/Settings/Notifications/EditTemplate.html
+++ b/UI/Settings/Notifications/EditTemplate.html
@@ -66,7 +66,7 @@
         <button class="btn pull-left x-back">back</button>
     {{/if}}
 
-    <button class="btn x-test">test <i class="x-test-icon"/></button>
+    <button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button>
     <button class="btn" data-dismiss="modal">cancel</button>
 
     <div class="btn-group">
diff --git a/UI/Settings/Notifications/EditView.js b/UI/Settings/Notifications/EditView.js
index 312e48b88..244ea8e18 100644
--- a/UI/Settings/Notifications/EditView.js
+++ b/UI/Settings/Notifications/EditView.js
@@ -10,7 +10,7 @@ define([
     'Mixins/AsModelBoundView',
     'Form/FormBuilder'
 
-], function (App, Marionette, NotificationModel, DeleteView, Messenger, CommandController, AsModelBoundView) {
+], function (App, Marionette, NotificationModel, DeleteView, Messenger, CommandController,  AsModelBoundView) {
 
     var model = Marionette.ItemView.extend({
         template: 'Settings/Notifications/EditTemplate',
@@ -70,41 +70,20 @@ define([
             var testCommand = this.model.get('testCommand');
             if (testCommand) {
                 this.idle = false;
-                this.ui.testButton.addClass('disabled');
-                this.ui.testIcon.addClass('icon-spinner icon-spin');
-
                 var properties = {};
 
                 _.each(this.model.get('fields'), function (field) {
                     properties[field.name] = field.value;
                 });
 
-                var self = this;
-                var commandPromise = CommandController.Execute(testCommand, properties);
-                commandPromise.done(function () {
-                    Messenger.show({
-                        message: 'Notification settings tested successfully'
-                    });
-                });
-
-                commandPromise.fail(function (options) {
-                    if (options.readyState === 0 || options.status === 0) {
-                        return;
-                    }
 
-                    Messenger.show({
-                        message: 'Failed to test notification settings',
-                        type   : 'error'
-                    });
-                });
+                CommandController.Execute(testCommand, properties);
+            }
+        },
 
-                commandPromise.always(function () {
-                    if (!self.isClosed) {
-                        self.ui.testButton.removeClass('disabled');
-                        self.ui.testIcon.removeClass('icon-spinner icon-spin');
-                        self.idle = true;
-                    }
-                });
+        _testOnAlways: function () {
+            if (!this.isClosed) {
+                this.idle = true;
             }
         }
     });
diff --git a/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js b/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js
index 2a5b80a39..47c3e232d 100644
--- a/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js
+++ b/UI/Settings/Quality/Profile/QualityProfileSchemaCollection.js
@@ -8,6 +8,6 @@ define(
 
         return Backbone.Collection.extend({
             model: QualityProfileModel,
-            url  : window.ApiRoot + '/qualityprofiles/schema'
+            url  : window.NzbDrone.ApiRoot + '/qualityprofiles/schema'
         });
     });
diff --git a/UI/Settings/SettingsModel.js b/UI/Settings/SettingsModel.js
index 4e0c142ec..1fbccdb74 100644
--- a/UI/Settings/SettingsModel.js
+++ b/UI/Settings/SettingsModel.js
@@ -2,7 +2,7 @@
 define(['app',
     'Settings/SettingsModelBase'], function (App, SettingsModelBase) {
     return SettingsModelBase.extend({
-        url           : window.ApiRoot + '/settings',
+        url           : window.NzbDrone.ApiRoot + '/settings',
         successMessage: 'Settings saved',
         errorMessage  : 'Failed to save settings'
     });
diff --git a/UI/Shared/Actioneer.js b/UI/Shared/Actioneer.js
deleted file mode 100644
index 929147ff1..000000000
--- a/UI/Shared/Actioneer.js
+++ /dev/null
@@ -1,95 +0,0 @@
-'use strict';
-define(['Commands/CommandController', 'Shared/Messenger'],
-    function(CommandController, Messenger) {
-        return {
-            ExecuteCommand: function (options) {
-                options.iconClass = this._getIconClass(options.element);
-
-                this._showStartMessage(options);
-                this._setSpinnerOnElement(options);
-
-                var promise = CommandController.Execute(options.command, options.properties);
-                this._handlePromise(promise, options);
-            },
-
-            SaveModel: function (options) {
-                options.iconClass = this._getIconClass(options.element);
-
-                this._showStartMessage(options);
-                this._setSpinnerOnElement(options);
-
-                var promise = options.context.model.save();
-                this._handlePromise(promise, options);
-            },
-
-            _handlePromise: function (promise, options) {
-                promise.done(function () {
-                    if (options.successMessage) {
-                        Messenger.show({
-                            message: options.successMessage
-                        });
-                    }
-
-                    if (options.onSuccess) {
-                        options.onSuccess.call(options.context);
-                    }
-                });
-
-                promise.fail(function (ajaxOptions) {
-                    if (ajaxOptions.readyState === 0 || ajaxOptions.status === 0) {
-                        return;
-                    }
-
-                    if (options.failMessage) {
-                        Messenger.show({
-                            message: options.failMessage,
-                            type   : 'error'
-                        });
-                    }
-
-                    if (options.onError) {
-                        options.onError.call(options.context);
-                    }
-                });
-
-                promise.always(function () {
-
-                    if (options.leaveIcon) {
-                        options.element.removeClass('icon-spin');
-                    }
-
-                    else {
-                        options.element.addClass(options.iconClass);
-                        options.element.removeClass('icon-nd-spinner');
-                    }
-
-                    if (options.always) {
-                        options.always.call(options.context);
-                    }
-                });
-            },
-
-            _getIconClass: function(element) {
-                return element.attr('class').match(/(?:^|\s)icon\-.+?(?:$|\s)/)[0];
-            },
-
-            _setSpinnerOnElement: function (options) {
-                if (options.leaveIcon) {
-                    options.element.addClass('icon-spin');
-                }
-
-                else {
-                    options.element.removeClass(options.iconClass);
-                    options.element.addClass('icon-nd-spinner');
-                }
-            },
-
-            _showStartMessage: function (options) {
-                if (options.startMessage) {
-                    Messenger.show({
-                        message: options.startMessage
-                    });
-                }
-            }
-        }
-});
diff --git a/UI/Shared/LoadingView.js b/UI/Shared/LoadingView.js
index a10261a42..e551583f2 100644
--- a/UI/Shared/LoadingView.js
+++ b/UI/Shared/LoadingView.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict';
 define(
     [
         'marionette'
diff --git a/UI/Shared/Messenger.js b/UI/Shared/Messenger.js
index 8f04009d9..ab53a619a 100644
--- a/UI/Shared/Messenger.js
+++ b/UI/Shared/Messenger.js
@@ -1,20 +1,25 @@
 'use strict';
 define(function () {
     return {
+
         show: function (options) {
 
             if (!options.type) {
                 options.type = 'info';
             }
 
-            if (!options.hideAfter) {
+            if (options.hideAfter === undefined) {
                 switch (options.type) {
                     case 'info':
                         options.hideAfter = 5;
                         break;
 
+                    case 'success':
+                        options.hideAfter = 5;
+                        break;
+
                     default :
-                        options.hideAfter = 0;
+                        options.hideAfter = 5;
                 }
             }
 
@@ -22,7 +27,8 @@ define(function () {
                 message        : options.message,
                 type           : options.type,
                 showCloseButton: true,
-                hideAfter      : options.hideAfter
+                hideAfter      : options.hideAfter,
+                id             : options.id
             });
         },
 
diff --git a/UI/Shared/SignalRBroadcaster.js b/UI/Shared/SignalRBroadcaster.js
new file mode 100644
index 000000000..5a58dcb1d
--- /dev/null
+++ b/UI/Shared/SignalRBroadcaster.js
@@ -0,0 +1,51 @@
+'use strict';
+define(
+    [
+        'app',
+        'signalR'
+    ], function () {
+        return {
+
+            appInitializer: function () {
+                console.log('starting signalR');
+
+                var getStatus = function (status) {
+                    switch (status) {
+                        case    0:
+                            return 'connecting';
+                        case 1:
+                            return 'connected';
+                        case 2:
+                            return 'reconnecting';
+                        case 4:
+                            return 'disconnected';
+                        default:
+                            throw 'invalid status ' + status;
+                    }
+                };
+
+                this.signalRconnection = $.connection("/signalr");
+
+                this.signalRconnection.stateChanged(function (change) {
+                    console.debug('SignalR: [{0}]'.format(getStatus(change.newState)));
+                });
+
+                this.signalRconnection.received(function (message) {
+                    require(
+                        [
+                            'app'
+                        ], function (app) {
+                            app.vent.trigger('server:' + message.name, message.body);
+                        })
+                });
+
+                this.signalRconnection.start({ transport:
+                    [
+                        'longPolling'
+                    ] });
+
+                return this;
+            }
+        };
+    });
+
diff --git a/UI/Shared/StringHelpers.js b/UI/Shared/StringHelpers.js
deleted file mode 100644
index e5fafb55a..000000000
--- a/UI/Shared/StringHelpers.js
+++ /dev/null
@@ -1,33 +0,0 @@
-'use strict';
-
-define(
-    [
-    ], function () {
-
-        return {
-
-            startsWith: function (str, starts) {
-                if (starts === '') {
-                    return true;
-                }
-                if (str == null || starts == null) {
-                    return false;
-                }
-                str = String(str);
-                starts = String(starts);
-                return str.length >= starts.length && str.slice(0, starts.length) === starts;
-            },
-
-            endsWith: function (str, ends) {
-                if (ends === '') {
-                    return true;
-                }
-                if (str == null || ends == null) {
-                    return false;
-                }
-                str = String(str);
-                ends = String(ends);
-                return str.length >= ends.length && str.slice(str.length - ends.length) === ends;
-            }
-        }
-    });
diff --git a/UI/Shared/Toolbar/Button/ButtonView.js b/UI/Shared/Toolbar/Button/ButtonView.js
index 32e8b3fbc..1bcd36679 100644
--- a/UI/Shared/Toolbar/Button/ButtonView.js
+++ b/UI/Shared/Toolbar/Button/ButtonView.js
@@ -3,9 +3,8 @@ define(
     [
         'app',
         'marionette',
-        'Commands/CommandController',
-        'Shared/Messenger'
-    ], function (App, Marionette, CommandController, Messenger) {
+        'Commands/CommandController'
+    ], function (App, Marionette, CommandController) {
 
         return Marionette.ItemView.extend({
             template : 'Shared/Toolbar/ButtonTemplate',
@@ -15,14 +14,8 @@ define(
                 'click': 'onClick'
             },
 
-            ui: {
-                icon: '.x-icon'
-            },
-
-
             initialize: function () {
                 this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key');
-                this.idle = true;
             },
 
             onRender: function () {
@@ -31,82 +24,34 @@ define(
                     this.invokeCallback();
                 }
 
-                if(!this.model.get('title')){
+                if (!this.model.get('title')) {
                     this.$el.addClass('btn-icon-only');
                 }
+
+                var command = this.model.get('command');
+                if (command) {
+                    CommandController.bindToCommand({
+                        command: {name: command},
+                        element: this.$el
+                    });
+                }
             },
 
             onClick: function () {
-                if (this.idle) {
-                    this.invokeCallback();
-                    this.invokeRoute();
-                    this.invokeCommand();
+
+                if (this.$el.hasClass('disabled')) {
+                    return;
                 }
+
+                this.invokeCallback();
+                this.invokeRoute();
+                this.invokeCommand();
             },
 
             invokeCommand: function () {
-                //TODO: Use Actioneer to handle icon swapping
-
                 var command = this.model.get('command');
                 if (command) {
-                    this.idle = false;
-                    this.$el.addClass('disabled');
-                    this.ui.icon.addClass('icon-spinner icon-spin');
-
-                    var self = this;
-                    var commandPromise = CommandController.Execute(command);
-                    commandPromise.done(function () {
-                        if (self.model.get('successMessage')) {
-                            Messenger.show({
-                                message: self.model.get('successMessage')
-                            });
-                        }
-
-                        if (self.model.get('onSuccess')) {
-                            if (!self.model.ownerContext) {
-                                throw 'ownerContext must be set.';
-                            }
-
-                            self.model.get('onSuccess').call(self.model.ownerContext);
-                        }
-                    });
-
-                    commandPromise.fail(function (options) {
-                        if (options.readyState === 0 || options.status === 0) {
-                            return;
-                        }
-
-                        if (self.model.get('errorMessage')) {
-                            Messenger.show({
-                                message: self.model.get('errorMessage'),
-                                type   : 'error'
-                            });
-                        }
-
-                        if (self.model.get('onError')) {
-                            if (!self.model.ownerContext) {
-                                throw 'ownerContext must be set.';
-                            }
-
-                            self.model.get('onError').call(self.model.ownerContext);
-                        }
-                    });
-
-                    commandPromise.always(function () {
-                        if (!self.isClosed) {
-                            self.$el.removeClass('disabled');
-                            self.ui.icon.removeClass('icon-spinner icon-spin');
-                            self.idle = true;
-                        }
-                    });
-
-                    if (self.model.get('always')) {
-                        if (!self.model.ownerContext) {
-                            throw 'ownerContext must be set.';
-                        }
-
-                        self.model.get('always').call(self.model.ownerContext);
-                    }
+                    CommandController.Execute(command);
                 }
             },
 
@@ -134,7 +79,6 @@ define(
                     callback.call(this.model.ownerContext);
                 }
             }
-
         });
     });
 
diff --git a/UI/Shared/Toolbar/ToolbarLayout.js b/UI/Shared/Toolbar/ToolbarLayout.js
index 701fdf07d..8945337a2 100644
--- a/UI/Shared/Toolbar/ToolbarLayout.js
+++ b/UI/Shared/Toolbar/ToolbarLayout.js
@@ -30,10 +30,8 @@ define(
                 this.left = options.left;
                 this.right = options.right;
                 this.toolbarContext = options.context;
-
             },
 
-
             onShow: function () {
                 if (this.left) {
                     _.each(this.left, this._showToolbarLeft, this);
@@ -51,7 +49,6 @@ define(
                 this._showToolbar(element, index, 'right');
             },
 
-
             _showToolbar: function (buttonGroup, index, position) {
 
                 var groupCollection = new ButtonCollection();
diff --git a/UI/System/Layout.js b/UI/System/Layout.js
index f1d499dc1..390ce48d8 100644
--- a/UI/System/Layout.js
+++ b/UI/System/Layout.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict';
 define(
     [
         'app',
@@ -6,15 +6,13 @@ define(
         'System/StatusModel',
         'System/About/View',
         'Logs/Layout',
-        'Shared/Toolbar/ToolbarLayout',
-        'Shared/LoadingView'
+        'Shared/Toolbar/ToolbarLayout'
     ], function (App,
                  Marionette,
                  StatusModel,
                  AboutView,
                  LogsLayout,
-                 ToolbarLayout,
-                 LoadingView) {
+                 ToolbarLayout) {
         return Marionette.Layout.extend({
             template: 'System/LayoutTemplate',
 
@@ -38,21 +36,7 @@ define(
                             title  : 'Check for Update',
                             icon   : 'icon-nd-update',
                             command: 'applicationUpdate'
-                        },
-//                        {
-//                            title         : 'Restart',
-//                            icon          : 'icon-repeat',
-//                            command       : 'restart',
-//                            successMessage: 'NzbDrone restart has been triggered',
-//                            errorMessage  : 'Failed to restart NzbDrone'
-//                        },
-//                        {
-//                            title         : 'Shutdown',
-//                            icon          : 'icon-power-off',
-//                            command       : 'shutdown',
-//                            successMessage: 'NzbDrone shutdown has started',
-//                            errorMessage  : 'Failed to shutdown NzbDrone'
-//                        }
+                        }
                     ]
             },
 
diff --git a/UI/System/StatusModel.js b/UI/System/StatusModel.js
index 8fabd1e55..20805cec7 100644
--- a/UI/System/StatusModel.js
+++ b/UI/System/StatusModel.js
@@ -5,7 +5,7 @@ define(
     ], function (Backbone) {
 
         var model = Backbone.Model.extend({
-            url: window.ApiRoot + '/system/status'
+            url: window.NzbDrone.ApiRoot + '/system/status'
         });
 
 
diff --git a/UI/app.js b/UI/app.js
index c3ba25d6f..280a16d39 100644
--- a/UI/app.js
+++ b/UI/app.js
@@ -1,7 +1,7 @@
 'use strict';
 require.config({
 
-    urlArgs: 'v=' + window.ServerStatus.version,
+    urlArgs: 'v=' + window.NzbDrone.ServerStatus.version,
 
     paths: {
         'backbone'            : 'JsLibraries/backbone',
@@ -181,14 +181,16 @@ require.config({
 define(
     [
         'marionette',
+        'Shared/SignalRBroadcaster',
         'Instrumentation/StringFormat'
-    ], function (Marionette) {
+    ], function (Marionette, SignalRBroadcaster) {
 
         var app = new Marionette.Application();
 
         app.Events = {
             SeriesAdded  : 'series:added',
-            SeriesDeleted: 'series:deleted'
+            SeriesDeleted: 'series:deleted',
+            SeasonRenamed: 'season:renamed'
         };
 
         app.Commands = {
@@ -209,6 +211,8 @@ define(
             console.log('starting application');
         });
 
+        app.addInitializer(SignalRBroadcaster.appInitializer, {app: app});
+
         app.addRegions({
             navbarRegion: '#nav-region',
             mainRegion  : '#main-region',
diff --git a/UI/index.html b/UI/index.html
index 5a4659fa5..5ffff693e 100644
--- a/UI/index.html
+++ b/UI/index.html
@@ -60,6 +60,7 @@
     </div>
 </footer>
 </body>
+<script src="/polyfills.js"></script>
 <script src="/JsLibraries/jquery.js"></script>
 <script src="/JsLibraries/messenger.js"></script>
 <script src="/ServerStatus.js"></script>
diff --git a/UI/jQuery/RouteBinder.js b/UI/jQuery/RouteBinder.js
index 7b599cc88..70ffc8742 100644
--- a/UI/jQuery/RouteBinder.js
+++ b/UI/jQuery/RouteBinder.js
@@ -1,5 +1,5 @@
 'use strict';
-define(['Shared/StringHelpers'],function (StringHelpers) {
+define(function () {
     //This module will automatically route all relative links through backbone router rather than
     //causing links to reload pages.
 
@@ -40,7 +40,7 @@ define(['Shared/StringHelpers'],function (StringHelpers) {
             }
 
 
-            if (!StringHelpers.startsWith(href, 'http')) {
+            if (!href.startsWith('http')) {
                 router.navigate(href, { trigger: true });
             }
 
diff --git a/UI/jQuery/jquery.spin.js b/UI/jQuery/jquery.spin.js
new file mode 100644
index 000000000..17fe52edf
--- /dev/null
+++ b/UI/jQuery/jquery.spin.js
@@ -0,0 +1,58 @@
+define(
+    [
+        'jquery'
+    ], function ($) {
+        'use strict';
+
+        $.fn.spinForPromise = function (promise) {
+            var self = this;
+
+            if (!promise || promise.state() !== 'pending') {
+                return this;
+            }
+            promise.always(function () {
+                self.stopSpin();
+            });
+
+            return this.startSpin();
+        };
+
+        $.fn.startSpin = function () {
+
+            var icon = this.find('i').andSelf('i');
+
+            var iconClasses = icon.attr('class').match(/(?:^|\s)icon\-.+?(?:$|\s)/);
+
+            if (iconClasses.length === 0) {
+                return this;
+            }
+
+            var iconClass = $.trim(iconClasses[0]);
+
+            this.addClass('disabled');
+
+            if (icon.hasClass('icon-can-spin')) {
+                icon.addClass('icon-spin');
+            }
+            else {
+                icon.attr('data-idle-icon', iconClass);
+                icon.removeClass(iconClass);
+                icon.addClass('icon-nd-spinner');
+            }
+
+            return this;
+        };
+
+        $.fn.stopSpin = function () {
+            var icon = this.find('i').andSelf('i');
+
+            this.removeClass('disabled');
+            icon.removeClass('icon-spin icon-nd-spinner');
+            var idleIcon = icon.attr('data-idle-icon');
+
+            if (idleIcon) {
+                icon.addClass(idleIcon);
+            }
+            return this;
+        };
+    });
diff --git a/UI/polyfills.js b/UI/polyfills.js
new file mode 100644
index 000000000..3e6797bf8
--- /dev/null
+++ b/UI/polyfills.js
@@ -0,0 +1,32 @@
+if (!String.prototype.startsWith) {
+    Object.defineProperty(String.prototype, 'startsWith', {
+        enumerable: false,
+        configurable: false,
+        writable: false,
+        value: function (searchString, position) {
+            position = position || 0;
+            return this.indexOf(searchString, position) === position;
+        }
+    });
+}
+
+if (!String.prototype.endsWith) {
+    Object.defineProperty(String.prototype, 'endsWith', {
+        enumerable: false,
+        configurable: false,
+        writable: false,
+        value: function (searchString, position) {
+            position = position || this.length;
+            position = position - searchString.length;
+            var lastIndex = this.lastIndexOf(searchString);
+            return lastIndex !== -1 && lastIndex === position;
+        }
+    });
+}
+
+if(!('contains' in String.prototype))
+{
+    String.prototype.contains = function(str, startIndex) {
+        return -1 !== String.prototype.indexOf.call(this, str, startIndex);
+    };
+}
\ No newline at end of file