diff --git a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/Connection.cs b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/Connection.cs
index f29063f7f..d2109e3bf 100644
--- a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/Connection.cs
+++ b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/Connection.cs
@@ -238,6 +238,12 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
 
                                                       if (command == null)
                                                       {
+                                                          var platform = (int)Environment.OSVersion.Platform;
+                                                          if (platform == 4 || platform == 6 || platform == 128)
+                                                          {
+                                                              return;
+                                                          }
+
                                                           throw new SerializationException("Couldn't parse message " + message.Value);
                                                       }
 
diff --git a/src/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs b/src/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs
index e03e02c35..1cff8601f 100644
--- a/src/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs
+++ b/src/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs
@@ -41,7 +41,8 @@ namespace NzbDrone.Api.Test.MappingTests
         [TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))]
         [TestCase(typeof(DownloadDecision), typeof(ReleaseResource))]
         [TestCase(typeof(Core.History.History), typeof(HistoryResource))]
-        [TestCase(typeof(Quality), typeof(QualityResource))]
+        [TestCase(typeof(QualityProfile), typeof(QualityProfileResource))]
+        [TestCase(typeof(QualityProfileItem), typeof(QualityProfileItemResource))]
         [TestCase(typeof(Log), typeof(LogResource))]
         [TestCase(typeof(Command), typeof(CommandResource))]
         public void matching_fields(Type modelType, Type resourceType)
@@ -107,10 +108,10 @@ namespace NzbDrone.Api.Test.MappingTests
         [Test]
         public void should_map_qualityprofile()
         {
-
             var profileResource = new QualityProfileResource
                 {
-                    Allowed = Builder<QualityResource>.CreateListOfSize(1).Build().ToList(),
+                    Cutoff = Quality.WEBDL1080p,
+                    Items = new List<QualityProfileItemResource> { new QualityProfileItemResource { Quality = Quality.WEBDL1080p, Allowed = true } }
                 };
 
 
diff --git a/src/NzbDrone.Api/Blacklist/BlacklistResource.cs b/src/NzbDrone.Api/Blacklist/BlacklistResource.cs
index f2c27f2c4..b9638cf9c 100644
--- a/src/NzbDrone.Api/Blacklist/BlacklistResource.cs
+++ b/src/NzbDrone.Api/Blacklist/BlacklistResource.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 using NzbDrone.Api.REST;
-using NzbDrone.Core.Tv;
+using NzbDrone.Core.Qualities;
 
 namespace NzbDrone.Api.Blacklist
 {
diff --git a/src/NzbDrone.Api/Calendar/CalendarModule.cs b/src/NzbDrone.Api/Calendar/CalendarModule.cs
index 8a7df0dab..141961ad5 100644
--- a/src/NzbDrone.Api/Calendar/CalendarModule.cs
+++ b/src/NzbDrone.Api/Calendar/CalendarModule.cs
@@ -51,7 +51,7 @@ namespace NzbDrone.Api.Calendar
             var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end))
                 .LoadSubtype(e => e.SeriesId, _seriesRepository);
 
-            return resources.OrderBy(e => e.AirDate).ToList();
+            return resources.OrderBy(e => e.AirDateUtc).ToList();
         }
 
         public void Handle(EpisodeGrabbedEvent message)
diff --git a/src/NzbDrone.Api/EpisodeFiles/EpisodeFileResource.cs b/src/NzbDrone.Api/EpisodeFiles/EpisodeFileResource.cs
index f3bd37368..311253f3f 100644
--- a/src/NzbDrone.Api/EpisodeFiles/EpisodeFileResource.cs
+++ b/src/NzbDrone.Api/EpisodeFiles/EpisodeFileResource.cs
@@ -1,5 +1,6 @@
 using System;
 using NzbDrone.Api.REST;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Api.EpisodeFiles
diff --git a/src/NzbDrone.Api/Episodes/EpisodeModule.cs b/src/NzbDrone.Api/Episodes/EpisodeModule.cs
index 46f6b7f94..91589b850 100644
--- a/src/NzbDrone.Api/Episodes/EpisodeModule.cs
+++ b/src/NzbDrone.Api/Episodes/EpisodeModule.cs
@@ -18,7 +18,7 @@ namespace NzbDrone.Api.Episodes
         private readonly IEpisodeService _episodeService;
 
         public EpisodeModule(ICommandExecutor commandExecutor, IEpisodeService episodeService)
-            : base(commandExecutor, "episodes")
+            : base(commandExecutor)
         {
             _episodeService = episodeService;
 
diff --git a/src/NzbDrone.Api/History/HistoryResource.cs b/src/NzbDrone.Api/History/HistoryResource.cs
index fe572a25c..e95330c52 100644
--- a/src/NzbDrone.Api/History/HistoryResource.cs
+++ b/src/NzbDrone.Api/History/HistoryResource.cs
@@ -4,6 +4,7 @@ using NzbDrone.Api.Episodes;
 using NzbDrone.Api.REST;
 using NzbDrone.Api.Series;
 using NzbDrone.Core.History;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Api.History
diff --git a/src/NzbDrone.Api/Indexers/ReleaseResource.cs b/src/NzbDrone.Api/Indexers/ReleaseResource.cs
index fda9ef67a..03955b5f8 100644
--- a/src/NzbDrone.Api/Indexers/ReleaseResource.cs
+++ b/src/NzbDrone.Api/Indexers/ReleaseResource.cs
@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using NzbDrone.Api.REST;
 using NzbDrone.Core.Parser;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Api.Indexers
diff --git a/src/NzbDrone.Api/NancyBootstrapper.cs b/src/NzbDrone.Api/NancyBootstrapper.cs
index d9496e038..0f5e89721 100644
--- a/src/NzbDrone.Api/NancyBootstrapper.cs
+++ b/src/NzbDrone.Api/NancyBootstrapper.cs
@@ -3,6 +3,7 @@ using Nancy.Bootstrapper;
 using Nancy.Diagnostics;
 using NzbDrone.Api.ErrorManagement;
 using NzbDrone.Api.Extensions.Pipelines;
+using NzbDrone.Common.EnvironmentInfo;
 using NzbDrone.Common.Instrumentation;
 using NzbDrone.Core.Instrumentation;
 using NzbDrone.Core.Lifecycle;
@@ -26,6 +27,11 @@ namespace NzbDrone.Api
         {
             _logger.Info("Starting NzbDrone API");
 
+            if (RuntimeInfo.IsProduction)
+            {
+                DiagnosticsHook.Disable(pipelines);
+            }
+
             RegisterPipelines(pipelines);
 
             container.Resolve<DatabaseTarget>().Register();
diff --git a/src/NzbDrone.Api/Notifications/NotificationResource.cs b/src/NzbDrone.Api/Notifications/NotificationResource.cs
index 54ffe720a..51c7fb7df 100644
--- a/src/NzbDrone.Api/Notifications/NotificationResource.cs
+++ b/src/NzbDrone.Api/Notifications/NotificationResource.cs
@@ -7,6 +7,7 @@ namespace NzbDrone.Api.Notifications
         public String Link { get; set; }
         public Boolean OnGrab { get; set; }
         public Boolean OnDownload { get; set; }
+        public Boolean OnUpgrade { get; set; }
         public String TestCommand { get; set; }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index 0371bd9cb..7a98eedf5 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -139,6 +139,7 @@
     <Compile Include="Missing\MissingModule.cs" />
     <Compile Include="Config\NamingSampleResource.cs" />
     <Compile Include="NzbDroneRestModuleWithSignalR.cs" />
+    <Compile Include="Qualities\QualityProfileValidation.cs" />
     <Compile Include="Queue\QueueModule.cs" />
     <Compile Include="Queue\QueueResource.cs" />
     <Compile Include="ResourceChangeMessage.cs" />
@@ -168,8 +169,8 @@
     <Compile Include="NzbDroneApiModule.cs" />
     <Compile Include="Qualities\QualityProfileResource.cs" />
     <Compile Include="Qualities\QualityProfileModule.cs" />
-    <Compile Include="Qualities\QualitySizeResource.cs" />
-    <Compile Include="Qualities\QualitySizeModule.cs" />
+    <Compile Include="Qualities\QualityDefinitionResource.cs" />
+    <Compile Include="Qualities\QualityDefinitionModule.cs" />
     <Compile Include="Extensions\ReqResExtensions.cs" />
     <Compile Include="Config\SettingsModule.cs" />
     <Compile Include="System\SystemModule.cs" />
diff --git a/src/NzbDrone.Api/Qualities/QualityDefinitionModule.cs b/src/NzbDrone.Api/Qualities/QualityDefinitionModule.cs
new file mode 100644
index 000000000..d675d54bc
--- /dev/null
+++ b/src/NzbDrone.Api/Qualities/QualityDefinitionModule.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Api.Mapping;
+
+namespace NzbDrone.Api.Qualities
+{
+    public class QualityDefinitionModule : NzbDroneRestModule<QualityDefinitionResource>
+    {
+        private readonly IQualityDefinitionService _qualityDefinitionService;
+
+        public QualityDefinitionModule(IQualityDefinitionService qualityDefinitionService)
+        {
+            _qualityDefinitionService = qualityDefinitionService;
+
+            GetResourceAll = GetAll;
+
+            GetResourceById = GetById;
+
+            UpdateResource = Update;
+        }
+
+        private void Update(QualityDefinitionResource resource)
+        {
+            var model = resource.InjectTo<QualityDefinition>();
+            _qualityDefinitionService.Update(model);
+        }
+
+        private QualityDefinitionResource GetById(int id)
+        {
+            return _qualityDefinitionService.Get((Quality)id).InjectTo<QualityDefinitionResource>();
+        }
+
+        private List<QualityDefinitionResource> GetAll()
+        {
+            return ToListResource(_qualityDefinitionService.All);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Qualities/QualityDefinitionResource.cs b/src/NzbDrone.Api/Qualities/QualityDefinitionResource.cs
new file mode 100644
index 000000000..8750a6c33
--- /dev/null
+++ b/src/NzbDrone.Api/Qualities/QualityDefinitionResource.cs
@@ -0,0 +1,18 @@
+using System;
+using NzbDrone.Api.REST;
+using NzbDrone.Core.Qualities;
+
+namespace NzbDrone.Api.Qualities
+{
+    public class QualityDefinitionResource : RestResource
+    {
+        public Quality Quality { get; set; }
+
+        public String Title { get; set; }
+
+        public Int32 Weight { get; set; }
+
+        public Int32 MinSize { get; set; }
+        public Int32 MaxSize { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Qualities/QualityProfileModule.cs b/src/NzbDrone.Api/Qualities/QualityProfileModule.cs
index a08fdb12c..ee7ce95cb 100644
--- a/src/NzbDrone.Api/Qualities/QualityProfileModule.cs
+++ b/src/NzbDrone.Api/Qualities/QualityProfileModule.cs
@@ -1,7 +1,6 @@
 using System.Collections.Generic;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Api.Mapping;
-using System.Linq;
 using FluentValidation;
 
 namespace NzbDrone.Api.Qualities
@@ -14,19 +13,14 @@ namespace NzbDrone.Api.Qualities
             : base("/qualityprofiles")
         {
             _qualityProfileService = qualityProfileService;
-
             SharedValidator.RuleFor(c => c.Name).NotEmpty();
             SharedValidator.RuleFor(c => c.Cutoff).NotNull();
-            SharedValidator.RuleFor(c => c.Allowed).NotEmpty();
+            SharedValidator.RuleFor(c => c.Items).MustHaveAllowedQuality();//.SetValidator(new AllowedValidator<QualityProfileItemResource>());
 
             GetResourceAll = GetAll;
-
             GetResourceById = GetById;
-
             UpdateResource = Update;
-
             CreateResource = Create;
-
             DeleteResource = DeleteProfile;
         }
 
@@ -44,38 +38,24 @@ namespace NzbDrone.Api.Qualities
 
         private void Update(QualityProfileResource resource)
         {
-            var model = resource.InjectTo<QualityProfile>();
+            var model = _qualityProfileService.Get(resource.Id);
+            
+            model.Name = resource.Name;
+            model.Cutoff = (Quality)resource.Cutoff.Id;
+            model.Items = resource.Items.InjectTo<List<QualityProfileItem>>();
             _qualityProfileService.Update(model);
         }
 
         private QualityProfileResource GetById(int id)
         {
-            return QualityToResource(_qualityProfileService.Get(id));
+            return _qualityProfileService.Get(id).InjectTo<QualityProfileResource>();
         }
 
         private List<QualityProfileResource> GetAll()
         {
-            var allProfiles = _qualityProfileService.All();
-
-
-            var profiles = allProfiles.Select(QualityToResource).ToList();
+            var profiles = _qualityProfileService.All().InjectTo<List<QualityProfileResource>>();
 
             return profiles;
         }
-
-        private static QualityProfileResource QualityToResource(QualityProfile profile)
-        {
-            return new QualityProfileResource
-                {
-                    Cutoff = profile.Cutoff.InjectTo<QualityResource>(),
-                    Available = Quality.All()
-                        .Where(c => !profile.Allowed.Any(q => c.Id == q.Id))
-                        .InjectTo<List<QualityResource>>(),
-
-                    Allowed = profile.Allowed.InjectTo<List<QualityResource>>(),
-                    Name = profile.Name,
-                    Id = profile.Id
-                };
-        }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Qualities/QualityProfileResource.cs b/src/NzbDrone.Api/Qualities/QualityProfileResource.cs
index 25e6a9108..dbeff0fa0 100644
--- a/src/NzbDrone.Api/Qualities/QualityProfileResource.cs
+++ b/src/NzbDrone.Api/Qualities/QualityProfileResource.cs
@@ -1,20 +1,20 @@
 using System;
 using System.Collections.Generic;
 using NzbDrone.Api.REST;
+using NzbDrone.Core.Qualities;
 
 namespace NzbDrone.Api.Qualities
 {
     public class QualityProfileResource : RestResource
     {
         public String Name { get; set; }
-        public QualityResource Cutoff { get; set; }
-        public List<QualityResource> Available { get; set; }
-        public List<QualityResource> Allowed { get; set; }
+        public Quality Cutoff { get; set; }
+        public List<QualityProfileItemResource> Items { get; set; }
     }
 
-    public class QualityResource : RestResource
+    public class QualityProfileItemResource : RestResource
     {
-        public Int32 Weight { get; set; }
-        public String Name { get; set; }
+        public Quality Quality { get; set; }
+        public bool Allowed { get; set; }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Qualities/QualityProfileSchemaModule.cs b/src/NzbDrone.Api/Qualities/QualityProfileSchemaModule.cs
index 06d941a2a..64caeefab 100644
--- a/src/NzbDrone.Api/Qualities/QualityProfileSchemaModule.cs
+++ b/src/NzbDrone.Api/Qualities/QualityProfileSchemaModule.cs
@@ -7,33 +7,28 @@ namespace NzbDrone.Api.Qualities
 {
     public class QualityProfileSchemaModule : NzbDroneRestModule<QualityProfileResource>
     {
-        public QualityProfileSchemaModule()
+        private readonly IQualityDefinitionService _qualityDefinitionService;
+
+        public QualityProfileSchemaModule(IQualityDefinitionService qualityDefinitionService)
             : base("/qualityprofiles/schema")
         {
+            _qualityDefinitionService = qualityDefinitionService;
+
             GetResourceAll = GetAll;
         }
 
         private List<QualityProfileResource> GetAll()
         {
+            var items = _qualityDefinitionService.All()
+                .OrderBy(v => v.Weight)
+                .Select(v => new QualityProfileItem { Quality = v.Quality, Allowed = false })
+                .ToList();
+
             var profile = new QualityProfile();
             profile.Cutoff = Quality.Unknown;
-            profile.Allowed = new List<Quality>();
+            profile.Items = items;
 
-            return new List<QualityProfileResource>{ QualityToResource(profile)};
-        }
-
-        private static QualityProfileResource QualityToResource(QualityProfile profile)
-        {
-            return new QualityProfileResource
-                {
-                    Available = Quality.All()
-                        .Where(c => !profile.Allowed.Any(q => c.Id == q.Id))
-                        .InjectTo<List<QualityResource>>(),
-
-                    Allowed = profile.Allowed.InjectTo<List<QualityResource>>(),
-                    Name = profile.Name,
-                    Id = profile.Id
-                };
+            return new List<QualityProfileResource> { profile.InjectTo<QualityProfileResource>() };
         }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Qualities/QualityProfileValidation.cs b/src/NzbDrone.Api/Qualities/QualityProfileValidation.cs
new file mode 100644
index 000000000..c90ebda61
--- /dev/null
+++ b/src/NzbDrone.Api/Qualities/QualityProfileValidation.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Linq;
+using FluentValidation;
+using FluentValidation.Validators;
+
+namespace NzbDrone.Api.Qualities
+{
+    public static class QualityProfileValidation
+    {
+        public static IRuleBuilderOptions<T, IList<QualityProfileItemResource>> MustHaveAllowedQuality<T>(this IRuleBuilder<T, IList<QualityProfileItemResource>> ruleBuilder)
+        {
+            ruleBuilder.SetValidator(new NotEmptyValidator(null));
+
+            return ruleBuilder.SetValidator(new AllowedValidator<T>());
+        }
+    }
+
+    public class AllowedValidator<T> : PropertyValidator
+    {
+        public AllowedValidator()
+            : base("Must contain at least one allowed quality")
+        {
+
+        }
+
+        protected override bool IsValid(PropertyValidatorContext context)
+        {
+            var list = context.PropertyValue as IList<QualityProfileItemResource>;
+
+            if (list == null)
+            {
+                return false;
+            }
+
+            if (!list.Any(c => c.Allowed))
+            {
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/src/NzbDrone.Api/Qualities/QualitySizeModule.cs b/src/NzbDrone.Api/Qualities/QualitySizeModule.cs
deleted file mode 100644
index 206bc9c51..000000000
--- a/src/NzbDrone.Api/Qualities/QualitySizeModule.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Collections.Generic;
-using NzbDrone.Core.Qualities;
-using NzbDrone.Api.Mapping;
-
-namespace NzbDrone.Api.Qualities
-{
-    public class QualitySizeModule : NzbDroneRestModule<QualitySizeResource>
-    {
-        private readonly IQualitySizeService _qualityTypeProvider;
-
-        public QualitySizeModule(IQualitySizeService qualityTypeProvider)
-        {
-            _qualityTypeProvider = qualityTypeProvider;
-
-            GetResourceAll = GetAll;
-
-            GetResourceById = GetById;
-
-            UpdateResource = Update;
-        }
-
-        private void Update(QualitySizeResource resource)
-        {
-            var model = resource.InjectTo<QualitySize>();
-            _qualityTypeProvider.Update(model);
-        }
-
-        private QualitySizeResource GetById(int id)
-        {
-            return _qualityTypeProvider.Get(id).InjectTo<QualitySizeResource>();
-        }
-
-        private List<QualitySizeResource> GetAll()
-        {
-            return ToListResource(_qualityTypeProvider.All);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Qualities/QualitySizeResource.cs b/src/NzbDrone.Api/Qualities/QualitySizeResource.cs
deleted file mode 100644
index 2d428f575..000000000
--- a/src/NzbDrone.Api/Qualities/QualitySizeResource.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using NzbDrone.Api.REST;
-
-namespace NzbDrone.Api.Qualities
-{
-    public class QualitySizeResource : RestResource
-    {
-        public Int32 QualityId { get; set; }
-        public String Name { get; set; }
-        public Int32 MinSize { get; set; }
-        public Int32 MaxSize { get; set; }
-    }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Queue/QueueResource.cs b/src/NzbDrone.Api/Queue/QueueResource.cs
index 94a7f07fc..0adfe1e79 100644
--- a/src/NzbDrone.Api/Queue/QueueResource.cs
+++ b/src/NzbDrone.Api/Queue/QueueResource.cs
@@ -1,5 +1,6 @@
 using System;
 using NzbDrone.Api.REST;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Api.Queue
diff --git a/src/NzbDrone.Api/RootFolders/RootFolderModule.cs b/src/NzbDrone.Api/RootFolders/RootFolderModule.cs
index 6691032d8..5d0298698 100644
--- a/src/NzbDrone.Api/RootFolders/RootFolderModule.cs
+++ b/src/NzbDrone.Api/RootFolders/RootFolderModule.cs
@@ -1,4 +1,7 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using FluentValidation;
+using FluentValidation.Results;
 using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.RootFolders;
 using NzbDrone.Api.Mapping;
@@ -30,7 +33,15 @@ namespace NzbDrone.Api.RootFolders
 
         private int CreateRootFolder(RootFolderResource rootFolderResource)
         {
-            return GetNewId<RootFolder>(_rootFolderService.Add, rootFolderResource);
+            try
+            {
+                return GetNewId<RootFolder>(_rootFolderService.Add, rootFolderResource);
+            }
+            catch (Exception ex)
+            {
+                throw new ValidationException(new [] { new ValidationFailure("Path", ex.Message) });
+            }
+            
         }
 
         private List<RootFolderResource> GetRootFolders()
diff --git a/src/NzbDrone.Api/Series/SeriesModule.cs b/src/NzbDrone.Api/Series/SeriesModule.cs
index ea01b1a06..d87a0f4ec 100644
--- a/src/NzbDrone.Api/Series/SeriesModule.cs
+++ b/src/NzbDrone.Api/Series/SeriesModule.cs
@@ -2,23 +2,39 @@
 using System.Collections.Generic;
 using System.Linq;
 using FluentValidation;
+using NzbDrone.Core.Datastore.Events;
 using NzbDrone.Core.MediaCover;
+using NzbDrone.Core.MediaFiles.Events;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Core.SeriesStats;
 using NzbDrone.Core.Tv;
 using NzbDrone.Api.Validation;
 using NzbDrone.Api.Mapping;
+using NzbDrone.Core.Tv.Events;
 
 namespace NzbDrone.Api.Series
 {
-    public class SeriesModule : NzbDroneRestModule<SeriesResource>
+    public class SeriesModule : NzbDroneRestModuleWithSignalR<SeriesResource, Core.Tv.Series>, 
+                                IHandle<EpisodeImportedEvent>, 
+                                IHandle<EpisodeFileDeletedEvent>,
+                                IHandle<SeriesUpdatedEvent>,       
+                                IHandle<SeriesEditedEvent>,  
+                                IHandle<SeriesDeletedEvent>
+                                
     {
+        private readonly ICommandExecutor _commandExecutor;
         private readonly ISeriesService _seriesService;
         private readonly ISeriesStatisticsService _seriesStatisticsService;
         private readonly IMapCoversToLocal _coverMapper;
 
-        public SeriesModule(ISeriesService seriesService, ISeriesStatisticsService seriesStatisticsService, IMapCoversToLocal coverMapper)
-            : base("/Series")
+        public SeriesModule(ICommandExecutor commandExecutor,
+                            ISeriesService seriesService,
+                            ISeriesStatisticsService seriesStatisticsService,
+                            IMapCoversToLocal coverMapper)
+            : base(commandExecutor)
         {
+            _commandExecutor = commandExecutor;
             _seriesService = seriesService;
             _seriesStatisticsService = seriesStatisticsService;
             _coverMapper = coverMapper;
@@ -74,6 +90,8 @@ namespace NzbDrone.Api.Series
         private void UpdateSeries(SeriesResource seriesResource)
         {
             GetNewId<Core.Tv.Series>(_seriesService.UpdateSeries, seriesResource);
+
+            BroadcastResourceChange(ModelAction.Updated, seriesResource);
         }
 
         private void DeleteSeries(int id)
@@ -119,5 +137,32 @@ namespace NzbDrone.Api.Series
             resource.EpisodeFileCount = seriesStatistics.EpisodeFileCount;
             resource.NextAiring = seriesStatistics.NextAiring;
         }
+
+        public void Handle(EpisodeImportedEvent message)
+        {
+            BroadcastResourceChange(ModelAction.Updated, message.ImportedEpisode.SeriesId);
+        }
+
+        public void Handle(EpisodeFileDeletedEvent message)
+        {
+            if (message.ForUpgrade) return;
+
+            BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.SeriesId);
+        }
+
+        public void Handle(SeriesUpdatedEvent message)
+        {
+            BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
+        }
+
+        public void Handle(SeriesEditedEvent message)
+        {
+            BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
+        }
+
+        public void Handle(SeriesDeletedEvent message)
+        {
+            BroadcastResourceChange(ModelAction.Deleted, message.Series.InjectTo<SeriesResource>());
+        }
     }
 }
diff --git a/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs b/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs
index 0c792f0c3..faef69f54 100644
--- a/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs
+++ b/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs
@@ -23,11 +23,7 @@ namespace NzbDrone.App.Test
         [Test]
         public void should_continue_if_only_instance()
         {
-            Mocker.GetMock<IProcessProvider>()
-                .Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME))
-                .Returns(new List<ProcessInfo>());
-
-            Mocker.GetMock<IProcessProvider>().Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME))
+            Mocker.GetMock<INzbDroneProcessProvider>().Setup(c => c.FindNzbDroneProcesses())
                 .Returns(new List<ProcessInfo>
                 {
                     new ProcessInfo{Id = CURRENT_PROCESS_ID}
@@ -36,29 +32,20 @@ namespace NzbDrone.App.Test
 
             Subject.PreventStartIfAlreadyRunning();
 
-
             Mocker.GetMock<IBrowserService>().Verify(c => c.LaunchWebUI(), Times.Never());
-
         }
 
         [Test]
         public void should_enforce_if_another_console_is_running()
         {
-            Mocker.GetMock<IProcessProvider>()
-                .Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME))
+            Mocker.GetMock<INzbDroneProcessProvider>()
+                .Setup(c => c.FindNzbDroneProcesses())
                     .Returns(new List<ProcessInfo>
                 {
-                    new ProcessInfo{Id = 10}
-                });
-
-            Mocker.GetMock<IProcessProvider>().Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME))
-                .Returns(new List<ProcessInfo>
-                {
+                    new ProcessInfo{Id = 10},
                     new ProcessInfo{Id = CURRENT_PROCESS_ID}
                 });
 
-
-
             Assert.Throws<TerminateApplicationException>(() => Subject.PreventStartIfAlreadyRunning());
             Mocker.GetMock<IBrowserService>().Verify(c => c.LaunchWebUI(), Times.Once());
             ExceptionVerification.ExpectedWarns(1);
@@ -67,22 +54,15 @@ namespace NzbDrone.App.Test
         [Test]
         public void should_return_false_if_another_gui_is_running()
         {
-            Mocker.GetMock<IProcessProvider>()
-                .Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME))
+            Mocker.GetMock<INzbDroneProcessProvider>()
+                .Setup(c => c.FindNzbDroneProcesses())
                 .Returns(new List<ProcessInfo>
                 {
-                      new ProcessInfo{Id = CURRENT_PROCESS_ID}
+                      new ProcessInfo{Id = CURRENT_PROCESS_ID},
+                      new ProcessInfo{Id = 10}
                 
                 });
 
-            Mocker.GetMock<IProcessProvider>().Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME))
-                .Returns(new List<ProcessInfo>
-                {
-                      new ProcessInfo{Id = 10}
-                });
-
-
-
             Assert.Throws<TerminateApplicationException>(() => Subject.PreventStartIfAlreadyRunning());
             Mocker.GetMock<IBrowserService>().Verify(c => c.LaunchWebUI(), Times.Once());
             ExceptionVerification.ExpectedWarns(1);
diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
index e59fb8bf9..80c3a159e 100644
--- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
+++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
@@ -73,7 +73,6 @@
     <Compile Include="EnvironmentProviderTest.cs" />
     <Compile Include="ProcessProviderTests.cs" />
     <Compile Include="ReflectionTests\ReflectionExtensionFixture.cs" />
-    <Compile Include="ServiceFactoryFixture.cs" />
     <Compile Include="ServiceProviderTests.cs" />
     <Compile Include="WebClientTests.cs" />
   </ItemGroup>
diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
index b9c506a2e..b1af0ccaa 100644
--- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs
+++ b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
@@ -204,6 +204,20 @@ namespace NzbDrone.Common.Disk
             File.Delete(path);
         }
 
+        public void CopyFile(string source, string destination, bool overwrite = false)
+        {
+            Ensure.That(source, () => source).IsValidPath();
+            Ensure.That(destination, () => destination).IsValidPath();
+
+            if (source.PathEquals(destination))
+            {
+                Logger.Warn("Source and destination can't be the same {0}", source);
+                return;
+            }
+
+            File.Copy(source, destination, overwrite);
+        }
+
         public void MoveFile(string source, string destination)
         {
             Ensure.That(source, () => source).IsValidPath();
diff --git a/src/NzbDrone.Common/Disk/IDiskProvider.cs b/src/NzbDrone.Common/Disk/IDiskProvider.cs
index 3e8b8ceff..0bb415452 100644
--- a/src/NzbDrone.Common/Disk/IDiskProvider.cs
+++ b/src/NzbDrone.Common/Disk/IDiskProvider.cs
@@ -26,6 +26,7 @@ namespace NzbDrone.Common.Disk
         void CopyFolder(string source, string destination);
         void MoveFolder(string source, string destination);
         void DeleteFile(string path);
+        void CopyFile(string source, string destination, bool overwrite = false);
         void MoveFile(string source, string destination);
         void DeleteFolder(string path, bool recursive);
         string ReadAllText(string filePath);
diff --git a/src/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs
index 3bf7876f3..3a5bcd019 100644
--- a/src/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs
+++ b/src/NzbDrone.Common/EnvironmentInfo/AppFolderInfo.cs
@@ -1,11 +1,6 @@
 using System;
 using System.IO;
 using System.Reflection;
-using System.Security.AccessControl;
-using System.Security.Principal;
-using NLog;
-using NzbDrone.Common.Disk;
-using NzbDrone.Common.Instrumentation;
 
 namespace NzbDrone.Common.EnvironmentInfo
 {
diff --git a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
index 7f81d23fd..82369c35b 100644
--- a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
+++ b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
@@ -1,37 +1,45 @@
 using System;
 using System.Diagnostics;
 using System.IO;
+using System.Reflection;
 using System.Security.Principal;
 using System.ServiceProcess;
 using NLog;
+using NzbDrone.Common.Processes;
 
 namespace NzbDrone.Common.EnvironmentInfo
 {
-
     public interface IRuntimeInfo
     {
         bool IsUserInteractive { get; }
         bool IsAdmin { get; }
         bool IsWindowsService { get; }
+        bool IsConsole { get; }
+        bool IsRunning { get; set; }
+        string ExecutingApplication { get; }
     }
 
     public class RuntimeInfo : IRuntimeInfo
     {
         private readonly Logger _logger;
+        private static readonly string ProcessName = Process.GetCurrentProcess().ProcessName.ToLower();
 
         public RuntimeInfo(Logger logger, IServiceProvider serviceProvider)
         {
             _logger = logger;
 
             IsWindowsService = !IsUserInteractive &&
-             OsInfo.IsWindows &&
-             serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) &&
-             serviceProvider.GetStatus(ServiceProvider.NZBDRONE_SERVICE_NAME) == ServiceControllerStatus.StartPending;
-        }
+                               OsInfo.IsWindows &&
+                               serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) &&
+                               serviceProvider.GetStatus(ServiceProvider.NZBDRONE_SERVICE_NAME) == ServiceControllerStatus.StartPending;
 
-        public bool IsUserInteractive
-        {
-            get { return Environment.UserInteractive; }
+            //Guarded to avoid issues when running in a non-managed process 
+            var entry = Assembly.GetEntryAssembly();
+
+            if (entry != null)
+            {
+                ExecutingApplication = entry.Location;
+            }            
         }
 
         static RuntimeInfo()
@@ -39,6 +47,11 @@ namespace NzbDrone.Common.EnvironmentInfo
             IsProduction = InternalIsProduction();
         }
 
+        public bool IsUserInteractive
+        {
+            get { return Environment.UserInteractive; }
+        }
+
         public bool IsAdmin
         {
             get
@@ -58,7 +71,19 @@ namespace NzbDrone.Common.EnvironmentInfo
 
         public bool IsWindowsService { get; private set; }
 
-        private static readonly string ProcessName = Process.GetCurrentProcess().ProcessName.ToLower();
+        public bool IsConsole
+        { 
+            get
+            {
+                return (OsInfo.IsWindows &&
+                        IsUserInteractive &&
+                        ProcessName.Equals(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME, StringComparison.InvariantCultureIgnoreCase)) ||
+                        OsInfo.IsLinux;
+            } 
+        }
+
+        public bool IsRunning { get; set; }
+        public string ExecutingApplication { get; private set; }
 
         public static bool IsProduction { get; private set; }
 
diff --git a/src/NzbDrone.Common/EnvironmentInfo/StartupContext.cs b/src/NzbDrone.Common/EnvironmentInfo/StartupContext.cs
index 0702fc861..3331b39e4 100644
--- a/src/NzbDrone.Common/EnvironmentInfo/StartupContext.cs
+++ b/src/NzbDrone.Common/EnvironmentInfo/StartupContext.cs
@@ -17,6 +17,7 @@ namespace NzbDrone.Common.EnvironmentInfo
         internal const string INSTALL_SERVICE = "i";
         internal const string UNINSTALL_SERVICE = "u";
         public const string HELP = "?";
+        public const string TERMINATE = "terminateexisting";
 
         public StartupContext(params string[] args)
         {
@@ -58,6 +59,5 @@ namespace NzbDrone.Common.EnvironmentInfo
                 return Flags.Contains(UNINSTALL_SERVICE);
             }
         }
-
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Common/Instrumentation/LogTargets.cs b/src/NzbDrone.Common/Instrumentation/LogTargets.cs
index 514caf506..f44ce456c 100644
--- a/src/NzbDrone.Common/Instrumentation/LogTargets.cs
+++ b/src/NzbDrone.Common/Instrumentation/LogTargets.cs
@@ -5,6 +5,7 @@ using NLog;
 using NLog.Config;
 using NLog.Targets;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Processes;
 
 namespace NzbDrone.Common.Instrumentation
 {
@@ -30,7 +31,7 @@ namespace NzbDrone.Common.Instrumentation
             }
             else
             {
-                if (inConsole && (OsInfo.IsLinux || new RuntimeInfo(null, new ServiceProvider()).IsUserInteractive))
+                if (inConsole && (OsInfo.IsLinux || new RuntimeInfo(null, new ServiceProvider(new ProcessProvider())).IsUserInteractive))
                 {
                     RegisterConsole();
                 }
diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj
index 6ce3ba94c..9378ab2f6 100644
--- a/src/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/src/NzbDrone.Common/NzbDrone.Common.csproj
@@ -101,6 +101,7 @@
     <Compile Include="Messaging\IEvent.cs" />
     <Compile Include="Messaging\IMessage.cs" />
     <Compile Include="PathEqualityComparer.cs" />
+    <Compile Include="Processes\INzbDroneProcessProvider.cs" />
     <Compile Include="Processes\ProcessOutput.cs" />
     <Compile Include="Serializer\IntConverter.cs" />
     <Compile Include="Services.cs" />
diff --git a/src/NzbDrone.Common/PathExtensions.cs b/src/NzbDrone.Common/PathExtensions.cs
index 5bf143796..309fee9cb 100644
--- a/src/NzbDrone.Common/PathExtensions.cs
+++ b/src/NzbDrone.Common/PathExtensions.cs
@@ -18,6 +18,7 @@ namespace NzbDrone.Common
         private static readonly string UPDATE_SANDBOX_FOLDER_NAME = "nzbdrone_update" + Path.DirectorySeparatorChar;
         private static readonly string UPDATE_PACKAGE_FOLDER_NAME = "nzbdrone" + Path.DirectorySeparatorChar;
         private static readonly string UPDATE_BACKUP_FOLDER_NAME = "nzbdrone_backup" + Path.DirectorySeparatorChar;
+        private static readonly string UPDATE_BACKUP_APPDATA_FOLDER_NAME = "nzbdrone_appdata_backup" + Path.DirectorySeparatorChar;
         private static readonly string UPDATE_CLIENT_FOLDER_NAME = "NzbDrone.Update" + Path.DirectorySeparatorChar;
         private static readonly string UPDATE_LOG_FOLDER_NAME = "UpdateLogs" + Path.DirectorySeparatorChar;
 
@@ -155,6 +156,21 @@ namespace NzbDrone.Common
             return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_BACKUP_FOLDER_NAME);
         }
 
+        public static string GetUpdateBackUpAppDataFolder(this IAppFolderInfo appFolderInfo)
+        {
+            return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_BACKUP_APPDATA_FOLDER_NAME);
+        }
+
+        public static string GetUpdateBackupConfigFile(this IAppFolderInfo appFolderInfo)
+        {
+            return Path.Combine(GetUpdateBackUpAppDataFolder(appFolderInfo), APP_CONFIG_FILE);
+        }
+
+        public static string GetUpdateBackupDatabase(this IAppFolderInfo appFolderInfo)
+        {
+            return Path.Combine(GetUpdateBackUpAppDataFolder(appFolderInfo), NZBDRONE_DB);
+        }
+
         public static string GetUpdatePackageFolder(this IAppFolderInfo appFolderInfo)
         {
             return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_PACKAGE_FOLDER_NAME);
diff --git a/src/NzbDrone.Common/Processes/INzbDroneProcessProvider.cs b/src/NzbDrone.Common/Processes/INzbDroneProcessProvider.cs
new file mode 100644
index 000000000..71abd6d0e
--- /dev/null
+++ b/src/NzbDrone.Common/Processes/INzbDroneProcessProvider.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using NzbDrone.Common.Model;
+
+namespace NzbDrone.Common.Processes
+{
+    public interface INzbDroneProcessProvider
+    {
+        List<ProcessInfo> FindNzbDroneProcesses();
+    }
+}
diff --git a/src/NzbDrone.Common/Processes/ProcessProvider.cs b/src/NzbDrone.Common/Processes/ProcessProvider.cs
index 785b029e5..cdb47174b 100644
--- a/src/NzbDrone.Common/Processes/ProcessProvider.cs
+++ b/src/NzbDrone.Common/Processes/ProcessProvider.cs
@@ -269,6 +269,7 @@ namespace NzbDrone.Common.Processes
 
             if (process.HasExited)
             {
+                Logger.Trace("Process has already exited");
                 return;
             }
 
diff --git a/src/NzbDrone.Common/Serializer/Json.cs b/src/NzbDrone.Common/Serializer/Json.cs
index 9a9b0e04b..1abb2019b 100644
--- a/src/NzbDrone.Common/Serializer/Json.cs
+++ b/src/NzbDrone.Common/Serializer/Json.cs
@@ -41,15 +41,22 @@ namespace NzbDrone.Common.Serializer
             return JsonConvert.DeserializeObject(json, type, SerializerSetting);
         }
 
-        public static T TryDeserialize<T>(string json) where T : new()
+        public static bool TryDeserialize<T>(string json, out T result) where T : new()
         {
             try
             {
-                return Deserialize<T>(json);
+                result = Deserialize<T>(json);
+                return true;
             }
             catch (JsonReaderException ex)
             {
-                return default(T);
+                result = default(T);
+                return false;
+            }
+            catch (JsonSerializationException ex)
+            {
+                result = default(T);
+                return false;
             }
         }
 
diff --git a/src/NzbDrone.Common/ServiceProvider.cs b/src/NzbDrone.Common/ServiceProvider.cs
index e669321cf..1137017f8 100644
--- a/src/NzbDrone.Common/ServiceProvider.cs
+++ b/src/NzbDrone.Common/ServiceProvider.cs
@@ -6,6 +6,7 @@ using System.Linq;
 using System.ServiceProcess;
 using NLog;
 using NzbDrone.Common.Instrumentation;
+using NzbDrone.Common.Processes;
 
 namespace NzbDrone.Common
 {
@@ -20,14 +21,22 @@ namespace NzbDrone.Common
         void Stop(string serviceName);
         void Start(string serviceName);
         ServiceControllerStatus GetStatus(string serviceName);
+        void Restart(string serviceName);
     }
 
     public class ServiceProvider : IServiceProvider
     {
         public const string NZBDRONE_SERVICE_NAME = "NzbDrone";
 
+        private readonly IProcessProvider _processProvider;
+
         private static readonly Logger Logger =  NzbDroneLogger.GetLogger();
 
+        public ServiceProvider(IProcessProvider processProvider)
+        {
+            _processProvider = processProvider;
+        }
+
         public virtual bool ServiceExist(string name)
         {
             Logger.Debug("Checking if service {0} exists.", name);
@@ -173,5 +182,12 @@ namespace NzbDrone.Common
                 Logger.Error("Service start request has timed out. {0}", service.Status);
             }
         }
+
+        public void Restart(string serviceName)
+        {
+            var args = String.Format("/C net.exe stop \"{0}\" && net.exe start \"{0}\"", serviceName);
+
+            _processProvider.Start("cmd.exe", args);
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Console/ConsoleApp.cs b/src/NzbDrone.Console/ConsoleApp.cs
index 1cf0aae11..a88122209 100644
--- a/src/NzbDrone.Console/ConsoleApp.cs
+++ b/src/NzbDrone.Console/ConsoleApp.cs
@@ -25,6 +25,9 @@ namespace NzbDrone.Console
                 Logger.FatalException("EPIC FAIL!", e);
                 System.Console.WriteLine("Press any key to exit...");
                 System.Console.ReadLine();
+                
+                //Need this to terminate on mono (thanks nlog)
+                LogManager.Configuration = null;
             }
         }
     }
diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs
index 75707c4e5..e852662e4 100644
--- a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs
+++ b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs
@@ -75,6 +75,7 @@ namespace NzbDrone.Core.Test.Datastore
         public void one_to_one()
         {
             var episodeFile = Builder<EpisodeFile>.CreateNew()
+                           .With(c => c.Quality = new QualityModel())
                            .BuildNew();
 
             Db.Insert(episodeFile);
diff --git a/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs b/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs
index aa07e1028..17b45da97 100644
--- a/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs
+++ b/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs
@@ -81,31 +81,30 @@ namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests
         [Test]
         public void should_read_existing_indexes()
         {
-            var indexes = _subject.GetIndexes("QualitySizes");
+            var indexes = _subject.GetIndexes("QualityDefinitions");
 
             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.Table == "QualityDefinitions");
             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");
+            var columns = _subject.GetColumns("QualityDefinitions");
+            var indexes = _subject.GetIndexes("QualityDefinitions");
 
-            _subject.CreateTable("QualityB", columns.Values, indexes);
+            _subject.CreateTable("QualityDefinitionsB", columns.Values, indexes);
 
-            var newIndexes = _subject.GetIndexes("QualityB");
+            var newIndexes = _subject.GetIndexes("QualityDefinitionsB");
 
             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()
         {
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs
index 5f511e3dc..b586622ea 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         private RemoteEpisode parseResultSingle;
         private Series series30minutes;
         private Series series60minutes;
-        private QualitySize qualityType;
+        private QualityDefinition qualityType;
 
         [SetUp]
         public void Setup()
@@ -47,10 +47,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
                 .With(c => c.Runtime = 60)
                 .Build();
 
-            qualityType = Builder<QualitySize>.CreateNew()
+            qualityType = Builder<QualityDefinition>.CreateNew()
                 .With(q => q.MinSize = 0)
                 .With(q => q.MaxSize = 10)
-                .With(q => q.QualityId = 1)
+                .With(q => q.Quality = Quality.SDTV)
                 .Build();
 
         }
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series30minutes;
             parseResultSingle.Release.Size = 184572800;
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series60minutes;
             parseResultSingle.Release.Size = 368572800;
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -99,7 +99,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series30minutes;
             parseResultSingle.Release.Size = 1.Gigabytes();
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series60minutes;
             parseResultSingle.Release.Size = 1.Gigabytes();
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -135,7 +135,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultMulti.Series = series30minutes;
             parseResultMulti.Release.Size = 184572800;
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -154,7 +154,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultMulti.Series = series60minutes;
             parseResultMulti.Release.Size = 368572800;
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -173,7 +173,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultMulti.Series = series30minutes;
             parseResultMulti.Release.Size = 1.Gigabytes();
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -192,7 +192,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultMulti.Series = series60minutes;
             parseResultMulti.Release.Size = 10.Gigabytes();
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -211,7 +211,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series30minutes;
             parseResultSingle.Release.Size = 184572800;
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -230,7 +230,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series60minutes;
             parseResultSingle.Release.Size = 368572800;
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -249,7 +249,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series30minutes;
             parseResultSingle.Release.Size = 1.Gigabytes();
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -270,7 +270,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Series = series60minutes;
             parseResultSingle.Release.Size = 10.Gigabytes();
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -292,7 +292,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Release.Size = 18457280000;
             qualityType.MaxSize = 0;
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -314,7 +314,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             parseResultSingle.Release.Size = 36857280000;
             qualityType.MaxSize = 0;
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -338,7 +338,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
 
             qualityType.MaxSize = (int)600.Megabytes();
 
-            Mocker.GetMock<IQualitySizeService>().Setup(s => s.Get(1)).Returns(qualityType);
+            Mocker.GetMock<IQualityDefinitionService>().Setup(s => s.Get(Quality.SDTV)).Returns(qualityType);
 
             Mocker.GetMock<IEpisodeService>().Setup(
                 s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny<int>()))
@@ -374,7 +374,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             Subject.IsSatisfiedBy(parseResult, null).Should().BeFalse();
 
 
-            Mocker.GetMock<IQualitySizeService>().Verify(c=>c.Get(It.IsAny<int>()),Times.Never());
+            Mocker.GetMock<IQualityDefinitionService>().Verify(c => c.Get(It.IsAny<Quality>()), Times.Never());
         }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs
index 7ff36a03b..861d39d32 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs
@@ -13,35 +13,35 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         [Test]
         public void should_return_true_if_current_episode_is_less_than_cutoff()
         {
-            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.Bluray1080p },
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() },
                                  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 },
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
                                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 },
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
                                 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 },
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
                                 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 },
+            Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
                                 new QualityModel(Quality.HDTV720p, true), new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse();
         }
     }
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs
index 0fe7b7ba0..54c8ff6e0 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
                                                        };
 
             _fakeSeries = Builder<Series>.CreateNew()
-                         .With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p })
+                         .With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() })
                          .Build();
 
             _parseResultMulti = new RemoteEpisode
@@ -62,11 +62,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             _upgradableQuality = new QualityModel(Quality.SDTV, false);
             _notupgradableQuality = new QualityModel(Quality.HDTV1080p, true);
 
-
-
-            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(1)).Returns(_notupgradableQuality);
-            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(2)).Returns(_notupgradableQuality);
-            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(3)).Returns<QualityModel>(null);
+            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 1)).Returns(_notupgradableQuality);
+            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 2)).Returns(_notupgradableQuality);
+            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 3)).Returns<QualityModel>(null);
 
             Mocker.GetMock<IProvideDownloadClient>()
                   .Setup(c => c.GetDownloadClient()).Returns(Mocker.GetMock<IDownloadClient>().Object);
@@ -74,12 +72,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
 
         private void WithFirstReportUpgradable()
         {
-            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(1)).Returns(_upgradableQuality);
+            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 1)).Returns(_upgradableQuality);
         }
 
         private void WithSecondReportUpgradable()
         {
-            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(2)).Returns(_upgradableQuality);
+            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 2)).Returns(_upgradableQuality);
         }
 
         private void GivenSabnzbdDownloadClient()
@@ -132,11 +130,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         [Test]
         public void should_not_be_upgradable_if_episode_is_of_same_quality_as_existing()
         {
-            _fakeSeries.QualityProfile = new QualityProfile { Cutoff = Quality.WEBDL1080p };
+            _fakeSeries.QualityProfile = new QualityProfile { Cutoff = Quality.WEBDL1080p, Items = Qualities.QualityFixture.GetDefaultQualities() };
             _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, false);
             _upgradableQuality = new QualityModel(Quality.WEBDL1080p, false);
 
-            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(1)).Returns(_upgradableQuality);
+            Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 1)).Returns(_upgradableQuality);
 
             _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse();
         }
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs
index 1ca3e9c41..baa24d5b4 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs
@@ -26,7 +26,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         [SetUp]
         public void Setup()
         {
-            _series = Builder<Series>.CreateNew().Build();
+            _series = Builder<Series>.CreateNew()
+                                     .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
+                                     .Build();
 
             _episode = Builder<Episode>.CreateNew()
                                        .With(e => e.SeriesId = _series.Id)
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs
index fb5ddde76..d8d46f3c0 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void should_allow_if_quality_is_defined_in_profile(Quality qualityType)
         {
             remoteEpisode.ParsedEpisodeInfo.Quality.Quality = qualityType;
-            remoteEpisode.Series.QualityProfile.Value.Allowed = new List<Quality> { Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p };
+            remoteEpisode.Series.QualityProfile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p);
 
             Subject.IsSatisfiedBy(remoteEpisode, null).Should().BeTrue();
         }
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         public void should_not_allow_if_quality_is_not_defined_in_profile(Quality qualityType)
         {
             remoteEpisode.ParsedEpisodeInfo.Quality.Quality = qualityType;
-            remoteEpisode.Series.QualityProfile.Value.Allowed = new List<Quality> { Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p };
+            remoteEpisode.Series.QualityProfile.Value.Items =  Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p);
 
             Subject.IsSatisfiedBy(remoteEpisode, null).Should().BeFalse();
         }
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs
index cc631b871..e91771776 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs
@@ -1,4 +1,5 @@
-using FluentAssertions;
+using System.Linq;
+using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Core.Configuration;
 using NzbDrone.Core.Qualities;
@@ -23,6 +24,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             new object[] { Quality.SDTV, false, Quality.SDTV, true, Quality.SDTV, true },
             new object[] { Quality.WEBDL1080p, false, Quality.WEBDL1080p, false, Quality.WEBDL1080p, false }
         };
+        
+        [SetUp]
+        public void Setup()
+        {
+
+        }
 
         private void GivenAutoDownloadPropers(bool autoDownloadPropers)
         {
@@ -36,7 +43,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             GivenAutoDownloadPropers(true);
 
-            Subject.IsUpgradable(new QualityModel(current, currentProper), new QualityModel(newQuality, newProper))
+            var qualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() };
+
+            Subject.IsUpgradable(qualityProfile, new QualityModel(current, currentProper), new QualityModel(newQuality, newProper))
                     .Should().Be(expected);
         }
 
@@ -45,8 +54,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
         {
             GivenAutoDownloadPropers(false);
 
-            Subject.IsUpgradable(new QualityModel(Quality.DVD, true),
-                                 new QualityModel(Quality.DVD, false)).Should().BeFalse();
+            var qualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() };
+
+            Subject.IsUpgradable(qualityProfile, new QualityModel(Quality.DVD, true), new QualityModel(Quality.DVD, false))
+                    .Should().BeFalse();
         }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs
index eb85c757d..c9e03d256 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
             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 })
+                         .With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() })
                          .Build();
 
             _parseResultMulti = new RemoteEpisode
diff --git a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs
index f13c81ccb..0563db94e 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs
@@ -37,6 +37,10 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
             remoteEpisode.Release = new ReleaseInfo();
             remoteEpisode.Release.PublishDate = DateTime.UtcNow;
 
+            remoteEpisode.Series = Builder<Series>.CreateNew()
+                .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
+                .Build();
+
             return remoteEpisode;
         }
 
diff --git a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs
index 370142d75..4c8aafaa4 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs
@@ -37,6 +37,10 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
             remoteEpisode.Release.PublishDate = DateTime.Now.AddDays(-Age);
             remoteEpisode.Release.Size = size;
 
+            remoteEpisode.Series = Builder<Series>.CreateNew()
+                                                  .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
+                                                  .Build();
+
             return remoteEpisode;
         }
 
diff --git a/src/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs
index cb3a0313c..f1a64dd7b 100644
--- a/src/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/DownloadServiceFixture.cs
@@ -87,7 +87,6 @@ namespace NzbDrone.Core.Test.Download
         {
             Mocker.GetMock<IDownloadClient>().Setup(c => c.IsConfigured).Returns(false);
 
-
             Subject.DownloadReport(_parseResult);
 
             Mocker.GetMock<IDownloadClient>().Verify(c => c.DownloadNzb(It.IsAny<RemoteEpisode>()), Times.Never());
diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs
new file mode 100644
index 000000000..717f9f915
--- /dev/null
+++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs
@@ -0,0 +1,59 @@
+using NUnit.Framework;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.History;
+using NzbDrone.Core.Qualities;
+using System.Collections.Generic;
+using NzbDrone.Core.Test.Qualities;
+using FluentAssertions;
+
+namespace NzbDrone.Core.Test.HistoryTests
+{
+    public class HistoryServiceFixture : CoreTest<HistoryService>
+    {
+        private QualityProfile _profile;
+        private QualityProfile _profileCustom;
+
+        [SetUp]
+        public void Setup()
+        {
+            _profile = new QualityProfile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities() };
+            _profileCustom = new QualityProfile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities(Quality.DVD) };
+        }
+
+        [Test]
+        public void should_return_null_if_no_history()
+        {
+            Mocker.GetMock<IHistoryRepository>()
+                .Setup(v => v.GetBestQualityInHistory(2))
+                .Returns(new List<QualityModel>());
+
+            var quality = Subject.GetBestQualityInHistory(_profile, 2);
+
+            quality.Should().BeNull();
+        }
+
+        [Test]
+        public void should_return_best_quality()
+        {
+            Mocker.GetMock<IHistoryRepository>()
+                .Setup(v => v.GetBestQualityInHistory(2))
+                .Returns(new List<QualityModel> { new QualityModel(Quality.DVD), new QualityModel(Quality.Bluray1080p) });
+
+            var quality = Subject.GetBestQualityInHistory(_profile, 2);
+
+            quality.Should().Be(new QualityModel(Quality.Bluray1080p));
+        }
+
+        [Test]
+        public void should_return_best_quality_with_custom_order()
+        {
+            Mocker.GetMock<IHistoryRepository>()
+                .Setup(v => v.GetBestQualityInHistory(2))
+                .Returns(new List<QualityModel> { new QualityModel(Quality.DVD), new QualityModel(Quality.Bluray1080p) });
+
+            var quality = Subject.GetBestQualityInHistory(_profileCustom, 2);
+
+            quality.Should().Be(new QualityModel(Quality.DVD));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
index 90208c4f5..8f33db4f0 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
@@ -80,6 +80,20 @@ namespace NzbDrone.Core.Test.MediaFiles
                   .Returns(true);
 
             Subject.Execute(new DownloadedEpisodesScanCommand());
+            
+            VerifyNoImport();
+        }
+
+        [Test]
+        public void should_skip_if_no_series_found()
+        {
+            Mocker.GetMock<IParsingService>().Setup(c => c.GetSeries("foldername")).Returns((Series)null);
+
+            Subject.Execute(new DownloadedEpisodesScanCommand());
+
+            Mocker.GetMock<IMakeImportDecision>()
+                .Verify(c => c.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), It.IsAny<bool>(), It.IsAny<Core.Qualities.QualityModel>()),
+                    Times.Never());
 
             VerifyNoImport();
         }
diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
index edcfa1a14..dcd8a12dc 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
@@ -13,6 +13,7 @@ using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Test.Framework;
 using NzbDrone.Core.Tv;
 using NzbDrone.Test.Common;
+using FizzWare.NBuilder;
 
 namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
 {
@@ -63,7 +64,10 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
             _fail3.Setup(c => c.RejectionReason).Returns("_fail3");
 
             _videoFiles = new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" };
-            _series = new Series();
+            _series = Builder<Series>.CreateNew()
+                                     .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
+                                     .Build();
+
             _quality = new QualityModel(Quality.DVD);
             _localEpisode = new LocalEpisode
             { 
@@ -80,7 +84,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
             Mocker.GetMock<IMediaFileService>()
                 .Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<int>()))
                 .Returns(_videoFiles);
-
         }
 
         private void GivenSpecifications(params Mock<IImportDecisionEngineSpecification>[] mocks)
@@ -162,7 +165,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
                 .Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<int>()))
                 .Returns(_videoFiles);
 
-            Subject.GetImportDecisions(_videoFiles, new Series(), false);
+            Subject.GetImportDecisions(_videoFiles, _series, false);
 
             Mocker.GetMock<IParsingService>()
                   .Verify(c => c.GetEpisodes(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()), Times.Exactly(_videoFiles.Count));
@@ -176,7 +179,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
             GivenSpecifications(_pass1, _pass2, _pass3);
             var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
 
-            var result = Subject.GetImportDecisions(_videoFiles, new Series(), false, null);
+            var result = Subject.GetImportDecisions(_videoFiles, _series, false, null);
 
             result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
         }
@@ -187,7 +190,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
             GivenSpecifications(_pass1, _pass2, _pass3);
             var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
 
-            var result = Subject.GetImportDecisions(_videoFiles, new Series(), false, new QualityModel(Quality.SDTV));
+            var result = Subject.GetImportDecisions(_videoFiles, _series, false, new QualityModel(Quality.SDTV));
 
             result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
         }
@@ -198,7 +201,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
             GivenSpecifications(_pass1, _pass2, _pass3);
             var expectedQuality = new QualityModel(Quality.Bluray1080p);
 
-            var result = Subject.GetImportDecisions(_videoFiles, new Series(), false, expectedQuality);
+            var result = Subject.GetImportDecisions(_videoFiles, _series, false, expectedQuality);
 
             result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
         }
diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs
index a0d0f7aef..a0bff8424 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs
@@ -22,13 +22,15 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
         public void Setup()
         {
             _series = Builder<Series>.CreateNew()
-                                     .With(s => s.SeriesType = SeriesTypes.Standard)
+                                     .With(s => s.SeriesType = SeriesTypes.Standard)                                     
+                                     .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
                                      .Build();
 
             _localEpisode = new LocalEpisode
                                 {
                                     Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
-                                    Quality = new QualityModel(Quality.HDTV720p, false)
+                                    Quality = new QualityModel(Quality.HDTV720p, false),
+                                    Series = _series
                                 };
         }
 
diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs
index 7946b53aa..59aa51f1c 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs
@@ -29,6 +29,7 @@ namespace NzbDrone.Core.Test.MediaFiles
             _approvedDecisions = new List<ImportDecision>();
 
             var series = Builder<Series>.CreateNew()
+                                        .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
                                         .Build();
 
             var episodes = Builder<Episode>.CreateListOfSize(5)
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index c9c4da8b6..5c72045ef 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -131,6 +131,7 @@
     <Compile Include="Framework\CoreTest.cs" />
     <Compile Include="Framework\DbTest.cs" />
     <Compile Include="Framework\NBuilderExtensions.cs" />
+    <Compile Include="HistoryTests\HistoryServiceFixture.cs" />
     <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" />
     <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFilesFixture.cs" />
     <Compile Include="Housekeeping\Housekeepers\CleanupAdditionalNamingSpecsFixture.cs" />
@@ -183,7 +184,7 @@
     <Compile Include="ParserTests\ParsingServiceTests\MapFixture.cs" />
     <Compile Include="ParserTests\SeriesTitleInfoFixture.cs" />
     <Compile Include="Providers\XemProxyFixture.cs" />
-    <Compile Include="Qualities\QualitySizeRepositoryFixture.cs" />
+    <Compile Include="Qualities\QualityDefinitionRepositoryFixture.cs" />
     <Compile Include="Qualities\QualityProfileRepositoryFixture.cs" />
     <Compile Include="RootFolderTests\FreeSpaceOnDrivesFixture.cs" />
     <Compile Include="Qualities\QualityFixture.cs" />
@@ -218,14 +219,14 @@
     <Compile Include="TvTests\SeriesServiceTests\UpdateSeriesFixture.cs" />
     <Compile Include="UpdateTests\UpdateServiceFixture.cs" />
     <Compile Include="DecisionEngineTests\AcceptableSizeSpecificationFixture.cs" />
-    <Compile Include="Qualities\QualitySizeServiceFixture.cs" />
+    <Compile Include="Qualities\QualityDefinitionServiceFixture.cs" />
     <Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest_GetEpisodesByParseResult.cs" />
     <Compile Include="FluentTest.cs" />
     <Compile Include="InstrumentationTests\DatabaseTargetFixture.cs" />
     <Compile Include="OrganizerTests\GetNewFilenameFixture.cs" />
     <Compile Include="DecisionEngineTests\MonitoredEpisodeSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\DownloadDecisionMakerFixture.cs" />
-    <Compile Include="TvTests\QualityModelFixture.cs" />
+    <Compile Include="Qualities\QualityModelComparerFixture.cs" />
     <Compile Include="RootFolderTests\RootFolderServiceFixture.cs" />
     <Compile Include="HistoryTests\HistoryRepositoryFixture.cs" />
     <Compile Include="MediaFiles\MediaFileServiceTest.cs" />
diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs
index b67e571ec..338e3059c 100644
--- a/src/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs
+++ b/src/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs
@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using FizzWare.NBuilder;
 using FluentAssertions;
 using NUnit.Framework;
@@ -50,6 +51,10 @@ namespace NzbDrone.Core.Test.OrganizerTests
                             .Build();
 
             _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "DRONE" };
+            
+            Mocker.GetMock<IQualityDefinitionService>()
+                .Setup(v => v.Get(Moq.It.IsAny<Quality>()))
+                .Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
         }
 
         private void GivenProper()
diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
index b716883cf..a3a7c4a58 100644
--- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
+++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Linq;
 using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Core.Qualities;
@@ -150,9 +151,9 @@ namespace NzbDrone.Core.Test.ParserTests
         }
 
         [Test, TestCaseSource("SelfQualityParserCases")]
-        public void parsing_our_own_quality_enum(Quality quality)
+        public void parsing_our_own_quality_enum_name(Quality quality)
         {
-            var fileName = String.Format("My series S01E01 [{0}]", quality);
+            var fileName = String.Format("My series S01E01 [{0}]", quality.Name);
             var result = Parser.QualityParser.ParseQuality(fileName);
             result.Quality.Should().Be(quality);
         }
diff --git a/src/NzbDrone.Core.Test/Qualities/QualitySizeRepositoryFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityDefinitionRepositoryFixture.cs
similarity index 57%
rename from src/NzbDrone.Core.Test/Qualities/QualitySizeRepositoryFixture.cs
rename to src/NzbDrone.Core.Test/Qualities/QualityDefinitionRepositoryFixture.cs
index 02454aa66..6156d9ab0 100644
--- a/src/NzbDrone.Core.Test/Qualities/QualitySizeRepositoryFixture.cs
+++ b/src/NzbDrone.Core.Test/Qualities/QualityDefinitionRepositoryFixture.cs
@@ -4,25 +4,27 @@ using NzbDrone.Core.Lifecycle;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Test.Framework;
 using FluentAssertions;
+using System.Collections.Generic;
 
 namespace NzbDrone.Core.Test.Qualities
 {
     [TestFixture]
-
-    public class QualitySizeRepositoryFixture : DbTest<QualitySizeRepository, QualitySize>
+    public class QualityDefinitionRepositoryFixture : DbTest<QualityDefinitionRepository, QualityDefinition>
     {
         [SetUp]
         public void Setup()
         {
-            Mocker.SetConstant<IQualitySizeRepository>(Subject);
-            Mocker.Resolve<QualitySizeService>().Handle(new ApplicationStartedEvent());
+            foreach (var qualityDefault in Quality.DefaultQualityDefinitions)
+            {
+                qualityDefault.Id = 0;
+                Storage.Insert(qualityDefault);
+            }
         }
 
-
         [Test]
-        public void should_get_quality_size_by_id()
+        public void should_get_qualitydefinition_by_id()
         {
-            var size = Subject.GetByQualityId(Quality.Bluray1080p.Id);
+            var size = Subject.GetByQualityId((int)Quality.Bluray1080p);
 
             size.Should().NotBeNull();
         }
diff --git a/src/NzbDrone.Core.Test/Qualities/QualityDefinitionServiceFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityDefinitionServiceFixture.cs
new file mode 100644
index 000000000..90f25b5f7
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Qualities/QualityDefinitionServiceFixture.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Test.Framework;
+
+namespace NzbDrone.Core.Test.Qualities
+{
+    [TestFixture]
+    public class QualityDefinitionServiceFixture : CoreTest<QualityDefinitionService>
+    {
+        [Test]
+        public void init_should_add_all_definitions()
+        {
+            Subject.Handle(new ApplicationStartedEvent());
+
+            Mocker.GetMock<IQualityDefinitionRepository>()
+                .Verify(v => v.Insert(It.IsAny<QualityDefinition>()), Times.Exactly(Quality.All.Count));
+        }
+
+        [Test]
+        public void init_should_insert_any_missing_definitions()
+        {
+            Mocker.GetMock<IQualityDefinitionRepository>()
+                  .Setup(s => s.All())
+                  .Returns(new List<QualityDefinition>
+                      {
+                              new QualityDefinition(Quality.SDTV) { Weight = 1, MinSize = 0, MaxSize = 100, Id = 20 }
+                      });
+
+            Subject.Handle(new ApplicationStartedEvent());
+
+            Mocker.GetMock<IQualityDefinitionRepository>()
+                .Verify(v => v.Insert(It.IsAny<QualityDefinition>()), Times.Exactly(Quality.All.Count - 1));
+        }
+
+        [Test]
+        public void init_should_insert_missing_definitions_preserving_weight()
+        {
+            // User moved HDTV1080p to a higher weight.
+            var currentQualities = new List<QualityDefinition>
+            {
+                new QualityDefinition(Quality.SDTV)        { Id = 5, Title = "SDTV",         Weight = 1,  MinSize=0, MaxSize=100 },
+                new QualityDefinition(Quality.WEBDL720p)   { Id = 2, Title = "720p WEB-DL",  Weight = 2,  MinSize=0, MaxSize=100 },
+                new QualityDefinition(Quality.HDTV1080p)   { Id = 4, Title = "1080p HDTV",   Weight = 3,  MinSize=0, MaxSize=100 },
+                new QualityDefinition(Quality.WEBDL1080p)  { Id = 8, Title = "1080p WEB-DL", Weight = 4,  MinSize=0, MaxSize=100 },
+            };
+
+            // Expected to insert Bluray720p above HDTV1080p.
+            // Expected to insert Bluray1080p above WEBDL1080p.
+            var addBluray1080p = new List<QualityDefinition>
+            {
+                new QualityDefinition(Quality.SDTV)        { Title = "SDTV",         Weight = 1,  MinSize=0, MaxSize=100 },
+                new QualityDefinition(Quality.HDTV1080p)   { Title = "1080p HDTV",   Weight = 2,  MinSize=0, MaxSize=100 },
+                new QualityDefinition(Quality.WEBDL720p)   { Title = "720p WEB-DL",  Weight = 3,  MinSize=0, MaxSize=100 },
+                new QualityDefinition(Quality.Bluray720p)  { Title = "720p BluRay",  Weight = 4,  MinSize=0, MaxSize=100 },
+                new QualityDefinition(Quality.WEBDL1080p)  { Title = "1080p WEB-DL", Weight = 5,  MinSize=0, MaxSize=100 },
+                new QualityDefinition(Quality.Bluray1080p) { Title = "1080p BluRay", Weight = 6,  MinSize=0, MaxSize=100 }
+            };
+
+            Mocker.GetMock<IQualityDefinitionRepository>()
+                .Setup(v => v.All())
+                .Returns(currentQualities);
+                        
+            Subject.InsertMissingDefinitions(addBluray1080p);
+
+            Mocker.GetMock<IQualityDefinitionRepository>()
+                .Verify(v => v.Insert(It.Is<QualityDefinition>(p => p.Quality == Quality.Bluray720p && p.Weight == 4)), Times.Once());
+
+            Mocker.GetMock<IQualityDefinitionRepository>()
+                .Verify(v => v.Update(It.Is<QualityDefinition>(p => p.Quality == Quality.WEBDL1080p && p.Weight == 5)), Times.Once());
+            
+            Mocker.GetMock<IQualityDefinitionRepository>()
+                .Verify(v => v.Insert(It.Is<QualityDefinition>(p => p.Quality == Quality.Bluray1080p && p.Weight == 6)), Times.Once());           
+            
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs
index a3fabfec0..ddab8f19b 100644
--- a/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs
+++ b/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs
@@ -1,4 +1,6 @@
-using FluentAssertions;
+using System.Linq;
+using System.Collections.Generic;
+using FluentAssertions;
 using NUnit.Framework;
 using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Test.Framework;
@@ -42,81 +44,31 @@ namespace NzbDrone.Core.Test.Qualities
             i.Should().Be(expected);
         }
 
-
-        [Test]
-        public void Icomparer_greater_test()
+        public static List<QualityProfileItem> GetDefaultQualities(params Quality[] allowed)
         {
-            var first = Quality.DVD;
-            var second = Quality.Bluray1080p;
+            var qualities = new List<Quality>
+            {
+                Quality.SDTV,
+                Quality.WEBDL480p,
+                Quality.DVD,
+                Quality.HDTV720p,
+                Quality.HDTV1080p,
+                Quality.RAWHD,
+                Quality.WEBDL720p,
+                Quality.Bluray720p,
+                Quality.WEBDL1080p,
+                Quality.Bluray1080p
+            };
 
-            second.Should().BeGreaterThan(first);
-        }
+            if (allowed.Length == 0)
+                allowed = qualities.ToArray();
 
-        [Test]
-        public void Icomparer_lesser()
-        {
-            var first = Quality.DVD;
-            var second = Quality.Bluray1080p;
+            var items = qualities
+                .Except(allowed)
+                .Concat(allowed)
+                .Select(v => new QualityProfileItem { Quality = v, Allowed = allowed.Contains(v) }).ToList();
 
-            first.Should().BeLessThan(second);
-        }
-
-        [Test]
-        public void equal_operand()
-        {
-            var first = Quality.Bluray1080p;
-            var second = Quality.Bluray1080p;
-
-            (first == second).Should().BeTrue();
-            (first >= second).Should().BeTrue();
-            (first <= second).Should().BeTrue();
-        }
-
-        [Test]
-        public void equal_operand_false()
-        {
-            var first = Quality.Bluray1080p;
-            var second = Quality.Unknown;
-
-            (first == second).Should().BeFalse();
-        }
-
-        [Test]
-        public void not_equal_operand()
-        {
-            var first = Quality.Bluray1080p;
-            var second = Quality.Bluray1080p;
-
-            (first != second).Should().BeFalse();
-        }
-
-        [Test]
-        public void not_equal_operand_false()
-        {
-            var first = Quality.Bluray1080p;
-            var second = Quality.Unknown;
-
-            (first != second).Should().BeTrue();
-        }
-
-        [Test]
-        public void greater_operand()
-        {
-            var first = Quality.DVD;
-            var second = Quality.Bluray1080p;
-
-            (first < second).Should().BeTrue();
-            (first <= second).Should().BeTrue();
-        }
-
-        [Test]
-        public void lesser_operand()
-        {
-            var first = Quality.DVD;
-            var second = Quality.Bluray1080p;
-
-            (second > first).Should().BeTrue();
-            (second >= first).Should().BeTrue();
+            return items;
         }
     }
 }
diff --git a/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs
new file mode 100644
index 000000000..187455abb
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs
@@ -0,0 +1,91 @@
+using System.Linq;
+using System.Collections.Generic;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Tv;
+using NzbDrone.Core.Test.Framework;
+
+namespace NzbDrone.Core.Test.Qualities
+{
+    [TestFixture]
+    public class QualityModelComparerFixture : CoreTest
+    {
+        public QualityModelComparer Subject { get; set; }
+
+        private void GivenDefaultQualityProfile()
+        {
+            Subject = new QualityModelComparer(new QualityProfile { Items = QualityFixture.GetDefaultQualities() });
+        }
+
+        private void GivenCustomQualityProfile()
+        {
+            Subject = new QualityModelComparer(new QualityProfile { Items = QualityFixture.GetDefaultQualities(Quality.Bluray720p, Quality.DVD) });
+        }
+
+        [Test]
+        public void Icomparer_greater_test()
+        {
+            GivenDefaultQualityProfile();
+
+            var first = new QualityModel(Quality.DVD, true);
+            var second = new QualityModel(Quality.Bluray1080p, true);
+
+            var compare = Subject.Compare(second, first);
+
+            compare.Should().BeGreaterThan(0);
+        }
+
+        [Test]
+        public void Icomparer_greater_proper()
+        {
+            GivenDefaultQualityProfile();
+
+            var first = new QualityModel(Quality.Bluray1080p, false);
+            var second = new QualityModel(Quality.Bluray1080p, true);
+
+            var compare = Subject.Compare(second, first);
+
+            compare.Should().BeGreaterThan(0);
+        }
+
+        [Test]
+        public void Icomparer_lesser()
+        {
+            GivenDefaultQualityProfile();
+
+            var first = new QualityModel(Quality.DVD, true);
+            var second = new QualityModel(Quality.Bluray1080p, true);
+
+            var compare = Subject.Compare(first, second);
+
+            compare.Should().BeLessThan(0);
+        }
+
+        [Test]
+        public void Icomparer_lesser_proper()
+        {
+            GivenDefaultQualityProfile();
+
+            var first = new QualityModel(Quality.DVD, false);
+            var second = new QualityModel(Quality.DVD, true);
+
+            var compare = Subject.Compare(first, second);
+
+            compare.Should().BeLessThan(0);
+        }
+
+        [Test]
+        public void Icomparer_greater_custom_order()
+        {
+            GivenCustomQualityProfile();
+
+            var first = new QualityModel(Quality.DVD, true);
+            var second = new QualityModel(Quality.Bluray720p, true);
+
+            var compare = Subject.Compare(first, second);
+
+            compare.Should().BeGreaterThan(0);
+        }
+    }
+}
diff --git a/src/NzbDrone.Core.Test/Qualities/QualityProfileRepositoryFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityProfileRepositoryFixture.cs
index f82879953..10b8ece92 100644
--- a/src/NzbDrone.Core.Test/Qualities/QualityProfileRepositoryFixture.cs
+++ b/src/NzbDrone.Core.Test/Qualities/QualityProfileRepositoryFixture.cs
@@ -7,7 +7,6 @@ using NzbDrone.Core.Test.Framework;
 namespace NzbDrone.Core.Test.Qualities
 {
     [TestFixture]
-
     public class QualityProfileRepositoryFixture : DbTest<QualityProfileRepository, QualityProfile>
     {
         [Test]
@@ -15,13 +14,7 @@ namespace NzbDrone.Core.Test.Qualities
         {
             var profile = new QualityProfile
                 {
-                    Allowed = new List<Quality>
-                        {
-                            Quality.Bluray1080p,
-                            Quality.DVD,
-                            Quality.HDTV720p
-                        },
-
+                    Items =  Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
                     Cutoff = Quality.Bluray1080p,
                     Name = "TestProfile"
                 };
@@ -30,8 +23,8 @@ namespace NzbDrone.Core.Test.Qualities
 
             StoredModel.Name.Should().Be(profile.Name);
             StoredModel.Cutoff.Should().Be(profile.Cutoff);
-
-            StoredModel.Allowed.Should().BeEquivalentTo(profile.Allowed);
+            
+            StoredModel.Items.Should().Equal(profile.Items, (a,b) => a.Quality == b.Quality && a.Allowed == b.Allowed);
 
 
         }
diff --git a/src/NzbDrone.Core.Test/Qualities/QualitySizeServiceFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualitySizeServiceFixture.cs
deleted file mode 100644
index 9c0b0b626..000000000
--- a/src/NzbDrone.Core.Test/Qualities/QualitySizeServiceFixture.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Collections.Generic;
-using Moq;
-using NUnit.Framework;
-using NzbDrone.Core.Lifecycle;
-using NzbDrone.Core.Qualities;
-using NzbDrone.Core.Test.Framework;
-
-namespace NzbDrone.Core.Test.Qualities
-{
-    [TestFixture]
-
-    public class QualitySizeServiceFixture : CoreTest<QualitySizeService>
-    {
-        [Test]
-        public void Init_should_add_all_sizes()
-        {
-            Subject.Handle(new ApplicationStartedEvent());
-
-            Mocker.GetMock<IQualitySizeRepository>()
-                .Verify(v => v.Insert(It.IsAny<QualitySize>()), Times.Exactly(Quality.All().Count));
-        }
-
-        [Test]
-        public void Init_should_insert_any_missing_sizes()
-        {
-            Mocker.GetMock<IQualitySizeRepository>()
-                  .Setup(s => s.All())
-                  .Returns(new List<QualitySize>
-                      {
-                              new QualitySize { QualityId = 1, Name = "SDTV", MinSize = 0, MaxSize = 100 }
-                      });
-
-            Subject.Handle(new ApplicationStartedEvent());
-
-            Mocker.GetMock<IQualitySizeRepository>()
-                .Verify(v => v.Insert(It.IsAny<QualitySize>()), Times.Exactly(Quality.All().Count - 1));
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/TvTests/QualityModelFixture.cs b/src/NzbDrone.Core.Test/TvTests/QualityModelFixture.cs
deleted file mode 100644
index 322ef2e79..000000000
--- a/src/NzbDrone.Core.Test/TvTests/QualityModelFixture.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using FluentAssertions;
-using NUnit.Framework;
-using NzbDrone.Core.Qualities;
-using NzbDrone.Core.Tv;
-using NzbDrone.Core.Test.Framework;
-
-namespace NzbDrone.Core.Test.TvTests
-{
-    [TestFixture]
-    
-    public class QualityModelFixture : CoreTest
-    {
-        [Test]
-        public void Icomparer_greater_test()
-        {
-            var first = new QualityModel(Quality.DVD, true);
-            var second = new QualityModel(Quality.Bluray1080p, true);
-
-            second.Should().BeGreaterThan(first);
-        }
-
-        [Test]
-        public void Icomparer_greater_proper()
-        {
-            var first = new QualityModel(Quality.Bluray1080p, false);
-            var second = new QualityModel(Quality.Bluray1080p, true);
-
-            second.Should().BeGreaterThan(first);
-        }
-
-        [Test]
-        public void Icomparer_lesser()
-        {
-            var first = new QualityModel(Quality.DVD, true);
-            var second = new QualityModel(Quality.Bluray1080p, true);
-
-            first.Should().BeLessThan(second);
-        }
-
-        [Test]
-        public void Icomparer_lesser_proper()
-        {
-            var first = new QualityModel(Quality.DVD, false);
-            var second = new QualityModel(Quality.DVD, true);
-
-            first.Should().BeLessThan(second);
-        }
-
-        [Test]
-        public void equal_operand()
-        {
-            var first = new QualityModel(Quality.Bluray1080p, true);
-            var second = new QualityModel(Quality.Bluray1080p, true);
-
-            (first == second).Should().BeTrue();
-            (first >= second).Should().BeTrue();
-            (first <= second).Should().BeTrue();
-        }
-
-        [Test]
-        public void equal_operand_false()
-        {
-            var first = new QualityModel(Quality.Bluray1080p, true);
-            var second = new QualityModel(Quality.Unknown, true);
-
-            (first == second).Should().BeFalse();
-        }
-
-        [Test]
-        public void equal_operand_false_proper()
-        {
-            var first = new QualityModel(Quality.Bluray1080p, true);
-            var second = new QualityModel(Quality.Bluray1080p, false);
-
-            (first == second).Should().BeFalse();
-        }
-
-        [Test]
-        public void not_equal_operand()
-        {
-            var first = new QualityModel(Quality.Bluray1080p, true);
-            var second = new QualityModel(Quality.Bluray1080p, true);
-
-            (first != second).Should().BeFalse();
-        }
-
-        [Test]
-        public void not_equal_operand_false()
-        {
-            var first = new QualityModel(Quality.Bluray1080p, true);
-            var second = new QualityModel(Quality.Unknown, true);
-
-            (first != second).Should().BeTrue();
-        }
-
-        [Test]
-        public void not_equal_operand_false_proper()
-        {
-            var first = new QualityModel(Quality.Bluray1080p, true);
-            var second = new QualityModel(Quality.Bluray1080p, false);
-
-            (first != second).Should().BeTrue();
-        }
-
-        [Test]
-        public void greater_operand()
-        {
-            var first = new QualityModel(Quality.DVD, true);
-            var second = new QualityModel(Quality.Bluray1080p, true);
-
-            (first < second).Should().BeTrue();
-            (first <= second).Should().BeTrue();
-        }
-
-        [Test]
-        public void lesser_operand()
-        {
-            var first = new QualityModel(Quality.DVD, true);
-            var second = new QualityModel(Quality.Bluray1080p, true);
-
-            (second > first).Should().BeTrue();
-            (second >= first).Should().BeTrue();
-        }
-    }
-}
diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/QualityProfileRepositoryFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/QualityProfileRepositoryFixture.cs
index 4829e3417..0685ccc0f 100644
--- a/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/QualityProfileRepositoryFixture.cs
+++ b/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/QualityProfileRepositoryFixture.cs
@@ -17,12 +17,7 @@ namespace NzbDrone.Core.Test.TvTests.SeriesRepositoryTests
         {
             var profile = new QualityProfile
                 {
-                    Allowed = new List<Quality>
-                        {
-                            Quality.Bluray1080p,
-                            Quality.DVD,
-                            Quality.HDTV720p
-                        },
+                    Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
 
                     Cutoff = Quality.Bluray1080p,
                     Name = "TestProfile"
diff --git a/src/NzbDrone.Core/Blacklisting/Blacklist.cs b/src/NzbDrone.Core/Blacklisting/Blacklist.cs
index 94cc5ffed..c68f55ff3 100644
--- a/src/NzbDrone.Core/Blacklisting/Blacklist.cs
+++ b/src/NzbDrone.Core/Blacklisting/Blacklist.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Blacklisting
diff --git a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs
index e144d4cf4..806bbf5f4 100644
--- a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs
+++ b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs
@@ -1,12 +1,36 @@
 using System;
 using Marr.Data.Converters;
 using Marr.Data.Mapping;
-using NzbDrone.Common.Serializer;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using Newtonsoft.Json.Converters;
 
 namespace NzbDrone.Core.Datastore.Converters
 {
     public class EmbeddedDocumentConverter : IConverter
     {
+        private readonly JsonSerializerSettings SerializerSetting;
+
+        public EmbeddedDocumentConverter(params JsonConverter[] converters)
+        {
+            SerializerSetting = new JsonSerializerSettings
+            {
+                DateTimeZoneHandling = DateTimeZoneHandling.Utc,
+                NullValueHandling = NullValueHandling.Ignore,
+                Formatting = Formatting.Indented,
+                DefaultValueHandling = DefaultValueHandling.Include,
+                ContractResolver = new CamelCasePropertyNamesContractResolver()
+            };
+
+            SerializerSetting.Converters.Add(new StringEnumConverter { CamelCaseText = true });
+            SerializerSetting.Converters.Add(new VersionConverter());
+
+            foreach (var converter in converters)
+            {
+                SerializerSetting.Converters.Add(converter);
+            }
+        }
+
         public virtual object FromDB(ConverterContext context)
         {
             if (context.DbValue == DBNull.Value)
@@ -20,8 +44,7 @@ namespace NzbDrone.Core.Datastore.Converters
             {
                 return null;
             }
-
-            return Json.Deserialize(stringValue, context.ColumnMap.FieldType);
+            return JsonConvert.DeserializeObject(stringValue, context.ColumnMap.FieldType, SerializerSetting);
         }
 
         public object FromDB(ColumnMap map, object dbValue)
@@ -33,7 +56,7 @@ namespace NzbDrone.Core.Datastore.Converters
         {
             if (clrValue == null) return null;
 
-            return clrValue.ToJson();
+            return JsonConvert.SerializeObject(clrValue, SerializerSetting);
         }
 
         public Type DbType
diff --git a/src/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs
index 6dc9d6c24..f423758f1 100644
--- a/src/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs
+++ b/src/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs
@@ -2,10 +2,13 @@
 using Marr.Data.Converters;
 using Marr.Data.Mapping;
 using NzbDrone.Core.Qualities;
+using System.Collections.Generic;
+using NzbDrone.Common.Serializer;
+using Newtonsoft.Json;
 
 namespace NzbDrone.Core.Datastore.Converters
 {
-    public class QualityIntConverter : IConverter
+    public class QualityIntConverter : JsonConverter, IConverter
     {
         public object FromDB(ConverterContext context)
         {
@@ -26,7 +29,7 @@ namespace NzbDrone.Core.Datastore.Converters
 
         public object ToDB(object clrValue)
         {
-            if(clrValue == null) return 0;
+            if(clrValue == DBNull.Value) return 0;
 
             if(clrValue as Quality == null)
             {
@@ -44,5 +47,21 @@ namespace NzbDrone.Core.Datastore.Converters
                 return typeof(int);
             }
         }
+
+        public override bool CanConvert(Type objectType)
+        {
+            return objectType == typeof(Quality);
+        }
+
+        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+        {
+            var item = reader.Value;
+            return (Quality)Convert.ToInt32(item);
+        }
+
+        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+        {
+            writer.WriteValue(ToDB(value));
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs b/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs
new file mode 100644
index 000000000..ea6f38fdd
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs
@@ -0,0 +1,107 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+using System.Data;
+using System.Linq;
+using NzbDrone.Common.Serializer;
+using NzbDrone.Core.Qualities;
+using System.Collections.Generic;
+using NzbDrone.Core.Datastore.Converters;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+    [Migration(36)]
+    public class update_with_quality_converters : NzbDroneMigrationBase
+    {
+        protected override void MainDbUpgrade()
+        {
+            if (!Schema.Table("QualityProfiles").Column("Items").Exists())
+            {
+                Alter.Table("QualityProfiles").AddColumn("Items").AsString().Nullable();
+            }
+
+            Execute.WithConnection(ConvertQualityProfiles);
+            Execute.WithConnection(ConvertQualityModels);
+        }
+        
+        private void ConvertQualityProfiles(IDbConnection conn, IDbTransaction tran)
+        {
+            var qualityProfileItemConverter = new EmbeddedDocumentConverter(new QualityIntConverter());
+
+            // Convert 'Allowed' column in QualityProfiles from Json List<object> to Json List<int> (int = Quality)
+            using (IDbCommand qualityProfileCmd = conn.CreateCommand())
+            {
+                qualityProfileCmd.Transaction = tran;
+                qualityProfileCmd.CommandText = @"SELECT Id, Allowed FROM QualityProfiles";
+                using (IDataReader qualityProfileReader = qualityProfileCmd.ExecuteReader())
+                {
+                    while (qualityProfileReader.Read())
+                    {
+                        var id = qualityProfileReader.GetInt32(0);
+                        var allowedJson = qualityProfileReader.GetString(1);
+
+                        var allowed = Json.Deserialize<List<Quality>>(allowedJson);
+
+                        var items = Quality.DefaultQualityDefinitions.OrderBy(v => v.Weight).Select(v => new QualityProfileItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }).ToList();
+
+                        var allowedNewJson = qualityProfileItemConverter.ToDB(items);
+
+                        using (IDbCommand updateCmd = conn.CreateCommand())
+                        {
+                            updateCmd.Transaction = tran;
+                            updateCmd.CommandText = "UPDATE QualityProfiles SET Items = ? WHERE Id = ?";
+                            updateCmd.AddParameter(allowedNewJson);
+                            updateCmd.AddParameter(id);
+
+                            updateCmd.ExecuteNonQuery();
+                        }
+                    }
+                }
+            }
+        }
+
+        private void ConvertQualityModels(IDbConnection conn, IDbTransaction tran)
+        {
+            // Converts the QualityModel JSON objects to their new format (only storing the QualityId instead of the entire object)
+            ConvertQualityModel(conn, tran, "Blacklist");
+            ConvertQualityModel(conn, tran, "EpisodeFiles");
+            ConvertQualityModel(conn, tran, "History");
+        }
+
+        private void ConvertQualityModel(IDbConnection conn, IDbTransaction tran, string tableName)
+        {
+            var qualityModelConverter = new EmbeddedDocumentConverter(new QualityIntConverter());
+
+            using (IDbCommand qualityModelCmd = conn.CreateCommand())
+            {
+                qualityModelCmd.Transaction = tran;
+                qualityModelCmd.CommandText = @"SELECT Distinct Quality FROM " + tableName;
+                using (IDataReader qualityModelReader = qualityModelCmd.ExecuteReader())
+                {
+                    while (qualityModelReader.Read())
+                    {
+                        var qualityJson = qualityModelReader.GetString(0);
+
+                        QualityModel quality;
+
+                        if (!Json.TryDeserialize<QualityModel>(qualityJson, out quality))
+                        {
+                            continue;
+                        }
+
+                        var qualityNewJson = qualityModelConverter.ToDB(quality);
+
+                        using (IDbCommand updateCmd = conn.CreateCommand())
+                        {
+                            updateCmd.Transaction = tran;
+                            updateCmd.CommandText = "UPDATE " + tableName + " SET Quality = ? WHERE Quality = ?";
+                            updateCmd.AddParameter(qualityNewJson);
+                            updateCmd.AddParameter(qualityJson);
+
+                            updateCmd.ExecuteNonQuery();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Datastore/Migration/037_add_configurable_qualities.cs b/src/NzbDrone.Core/Datastore/Migration/037_add_configurable_qualities.cs
new file mode 100644
index 000000000..fddd8e5a5
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/037_add_configurable_qualities.cs
@@ -0,0 +1,63 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+using System.Data;
+using System.Linq;
+using NzbDrone.Core.Qualities;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+    [Migration(37)]
+    public class add_configurable_qualities : NzbDroneMigrationBase
+    {
+        protected override void MainDbUpgrade()
+        {
+            SqLiteAlter.DropColumns("QualityProfiles", new[] { "Allowed" });
+            Alter.Column("Items").OnTable("QualityProfiles").AsString().NotNullable();
+
+            Create.TableForModel("QualityDefinitions")
+                    .WithColumn("Quality").AsInt32().Unique()
+                    .WithColumn("Title").AsString().Unique()
+                    .WithColumn("Weight").AsInt32().Unique()
+                    .WithColumn("MinSize").AsInt32()
+                    .WithColumn("MaxSize").AsInt32();
+
+            Execute.WithConnection(ConvertQualities);
+
+            Delete.Table("QualitySizes");
+        }
+
+        private void ConvertQualities(IDbConnection conn, IDbTransaction tran)
+        {
+            // Convert QualitySizes to a more generic QualityDefinitions table.
+            using (IDbCommand qualitySizeCmd = conn.CreateCommand())
+            {
+                qualitySizeCmd.Transaction = tran;
+                qualitySizeCmd.CommandText = @"SELECT QualityId, MinSize, MaxSize FROM QualitySizes";
+                using (IDataReader qualitySizeReader = qualitySizeCmd.ExecuteReader())
+                {
+                    while (qualitySizeReader.Read())
+                    {
+                        var qualityId = qualitySizeReader.GetInt32(0);
+                        var minSize = qualitySizeReader.GetInt32(1);
+                        var maxSize = qualitySizeReader.GetInt32(2);
+
+                        var defaultConfig = Quality.DefaultQualityDefinitions.Single(p => (int)p.Quality == qualityId);
+
+                        using (IDbCommand updateCmd = conn.CreateCommand())
+                        {
+                            updateCmd.Transaction = tran;
+                            updateCmd.CommandText = "INSERT INTO QualityDefinitions (Quality, Title, Weight, MinSize, MaxSize) VALUES (?, ?, ?, ?, ?)";
+                            updateCmd.AddParameter(qualityId);
+                            updateCmd.AddParameter(defaultConfig.Title);
+                            updateCmd.AddParameter(defaultConfig.Weight);
+                            updateCmd.AddParameter(minSize);
+                            updateCmd.AddParameter(maxSize);
+
+                            updateCmd.ExecuteNonQuery();
+                        }
+                    }
+                }
+            }
+        }        
+    }
+}
diff --git a/src/NzbDrone.Core/Datastore/Migration/038_add_on_upgrade_to_notifications.cs b/src/NzbDrone.Core/Datastore/Migration/038_add_on_upgrade_to_notifications.cs
new file mode 100644
index 000000000..f5cae2ba0
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/038_add_on_upgrade_to_notifications.cs
@@ -0,0 +1,16 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+    [Migration(38)]
+    public class add_on_upgrade_to_notifications : NzbDroneMigrationBase
+    {
+        protected override void MainDbUpgrade()
+        {
+            Alter.Table("Notifications").AddColumn("OnUpgrade").AsBoolean().Nullable();
+
+            Execute.Sql("UPDATE Notifications SET OnUpgrade = OnDownload");
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationExtension.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationExtension.cs
index 565630165..807e73e2a 100644
--- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationExtension.cs
+++ b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationExtension.cs
@@ -10,5 +10,11 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
             return expressionRoot.Table(name).WithColumn("Id").AsInt32().PrimaryKey().Identity();
         }
 
+        public static void AddParameter(this System.Data.IDbCommand command, object value)
+        {
+            var parameter = command.CreateParameter();
+            parameter.Value = value;
+            command.Parameters.Add(parameter);
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs
index 42f8f8e7b..3bb08dace 100644
--- a/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs
+++ b/src/NzbDrone.Core/Datastore/Migration/Framework/SQLiteMigrationHelper.cs
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
         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)\)$",
+        private static readonly Regex IndexRegex = new Regex(@"\((?:""|')(?<col>.*)(?:""|')\s(?<direction>ASC|DESC)\)$",
              RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
 
         public SqLiteMigrationHelper(IConnectionStringFactory connectionStringFactory, Logger logger)
@@ -96,8 +96,6 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
 
             var reader = command.ExecuteReader();
             var sqls = ReadArray<string>(reader).ToList();
-
-
             var indexes = new List<SQLiteIndex>();
 
             foreach (var indexSql in sqls)
@@ -105,6 +103,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
                 var newIndex = new SQLiteIndex();
                 var matches = IndexRegex.Match(indexSql);
 
+                if (!matches.Success) continue;;
+
                 newIndex.Column = matches.Groups["col"].Value;
                 newIndex.Unique = indexSql.Contains("UNIQUE");
                 newIndex.Table = tableName;
diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs
index c8d423a1c..848470de3 100644
--- a/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs
+++ b/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs
@@ -45,7 +45,6 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
 
                 var newIndexes = originalIndexes.Union(indexes);
 
-
                 CreateTable(tableName, columns, newIndexes);
 
                 transaction.Commit();
@@ -57,7 +56,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
             using (var transaction = _sqLiteMigrationHelper.BeginTransaction())
             {
                 var originalColumns = _sqLiteMigrationHelper.GetColumns(tableName);
-                var originalIndexes = _sqLiteMigrationHelper.GetIndexes(tableName);
+                var indexes = _sqLiteMigrationHelper.GetIndexes(tableName);
 
                 var newColumns = originalColumns.Select(c =>
                 {
@@ -82,9 +81,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
                     return c.Value;
                 }).ToList();
 
-                var newIndexes = originalIndexes;
-
-                CreateTable(tableName, newColumns, newIndexes);
+                CreateTable(tableName, newColumns, indexes);
 
                 transaction.Commit();
             }
diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs
index f7ce5cd43..f97514f16 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Datastore
 
             Mapper.Entity<QualityProfile>().RegisterModel("QualityProfiles");
 
-            Mapper.Entity<QualitySize>().RegisterModel("QualitySizes");
+            Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions");
 
             Mapper.Entity<Log>().RegisterModel("Logs");
 
@@ -81,6 +81,8 @@ namespace NzbDrone.Core.Datastore
             MapRepository.Instance.RegisterTypeConverter(typeof(Boolean), new BooleanIntConverter());
             MapRepository.Instance.RegisterTypeConverter(typeof(Enum), new EnumIntConverter());
             MapRepository.Instance.RegisterTypeConverter(typeof(Quality), new QualityIntConverter());
+            MapRepository.Instance.RegisterTypeConverter(typeof(List<QualityProfileItem>), new EmbeddedDocumentConverter(new QualityIntConverter()));
+            MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
             MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
             MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
         }
diff --git a/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs
index 8bec31bc1..3330be7ab 100644
--- a/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.DecisionEngine
 {
     public interface IQualityUpgradableSpecification
     {
-        bool IsUpgradable(QualityModel currentQuality, QualityModel newQuality = null);
+        bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
         bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
         bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality);
     }
@@ -20,11 +20,12 @@ namespace NzbDrone.Core.DecisionEngine
             _logger = logger;
         }
 
-        public bool IsUpgradable(QualityModel currentQuality, QualityModel newQuality = null)
+        public bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
         {
             if (newQuality != null)
             {
-                if (currentQuality >= newQuality)
+                int compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality);
+                if (compare <= 0)
                 {
                     _logger.Trace("existing item has better or equal quality. skipping");
                     return false;
@@ -41,7 +42,9 @@ namespace NzbDrone.Core.DecisionEngine
 
         public bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
         {
-            if (currentQuality.Quality >= profile.Cutoff)
+            int compare = new QualityModelComparer(profile).Compare(currentQuality.Quality, profile.Cutoff);
+
+            if (compare >= 0)
             {
                 if (newQuality != null && IsProperUpgrade(currentQuality, newQuality))
                 {
@@ -57,7 +60,9 @@ namespace NzbDrone.Core.DecisionEngine
 
         public bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality)
         {
-            if (currentQuality.Quality == newQuality.Quality && newQuality > currentQuality)
+            int compare = newQuality.Proper.CompareTo(currentQuality.Proper);
+
+            if (currentQuality.Quality == newQuality.Quality && compare > 0)
             {
                 _logger.Trace("New quality is a proper for existing quality");
                 return true;
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs
index 1d6f5af7f..e63edaa8c 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs
@@ -9,13 +9,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
 {
     public class AcceptableSizeSpecification : IDecisionEngineSpecification
     {
-        private readonly IQualitySizeService _qualityTypeProvider;
+        private readonly IQualityDefinitionService _qualityDefinitionService;
         private readonly IEpisodeService _episodeService;
         private readonly Logger _logger;
 
-        public AcceptableSizeSpecification(IQualitySizeService qualityTypeProvider, IEpisodeService episodeService, Logger logger)
+        public AcceptableSizeSpecification(IQualityDefinitionService qualityDefinitionService, IEpisodeService episodeService, Logger logger)
         {
-            _qualityTypeProvider = qualityTypeProvider;
+            _qualityDefinitionService = qualityDefinitionService;
             _episodeService = episodeService;
             _logger = logger;
         }
@@ -44,15 +44,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
                 return false;
             }
 
-            var qualityType = _qualityTypeProvider.Get(quality.Id);
+            var qualityDefinition = _qualityDefinitionService.Get(quality);
 
-            if (qualityType.MaxSize == 0)
+            if (qualityDefinition.MaxSize == 0)
             {
                 _logger.Trace("Max size is 0 (unlimited) - skipping check.");
                 return true;
             }
 
-            var maxSize = qualityType.MaxSize.Megabytes();
+            var maxSize = qualityDefinition.MaxSize.Megabytes();
 
             //Multiply maxSize by Series.Runtime
             maxSize = maxSize * subject.Series.Runtime * subject.Episodes.Count;
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs
index 635c95909..dc800715e 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
 
             if (_blacklistService.Blacklisted(subject.Release.Title))
             {
-                _logger.Trace("{0} is blacklisted", subject.Release.Title);
+                _logger.Trace("{0} is blacklisted, rejecting.", subject.Release.Title);
                 return false;
             }
 
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs
index f43df2836..e087c6ef3 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs
@@ -33,6 +33,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
                 
                 if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality))
                 {
+                    _logger.Trace("Cutoff already met, rejecting.");
                     return false;
                 }
             }
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs
index c0ccf0ecf..fa6312258 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs
@@ -4,6 +4,8 @@ using NLog;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.IndexerSearch.Definitions;
 using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.DecisionEngine.Specifications
 {
@@ -38,13 +40,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
 
             var queue = downloadClient.GetQueue().Select(q => q.RemoteEpisode);
 
-            return !IsInQueue(subject, queue);
+            if (IsInQueue(subject, queue))
+            {
+                _logger.Trace("Already in queue, rejecting.");
+                return false;
+            }
+
+            return true;
         }
 
         private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable<RemoteEpisode> queue)
         {
             var matchingSeries = queue.Where(q => q.Series.Id == newEpisode.Series.Id);
-            var matchingSeriesAndQuality = matchingSeries.Where(q => q.ParsedEpisodeInfo.Quality >= newEpisode.ParsedEpisodeInfo.Quality);
+            var matchingSeriesAndQuality = matchingSeries.Where(q => new QualityModelComparer(q.Series.QualityProfile).Compare(q.ParsedEpisodeInfo.Quality, newEpisode.ParsedEpisodeInfo.Quality) >= 0);
 
             return matchingSeriesAndQuality.Any(q => q.Episodes.Select(e => e.Id).Intersect(newEpisode.Episodes.Select(e => e.Id)).Any());
         }
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs
index afcb7149a..5d6a2da77 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs
@@ -1,16 +1,24 @@
-using NzbDrone.Core.IndexerSearch.Definitions;
+using NLog;
+using NzbDrone.Core.IndexerSearch.Definitions;
 using NzbDrone.Core.Parser.Model;
 
 namespace NzbDrone.Core.DecisionEngine.Specifications
 {
     public class NotSampleSpecification : IDecisionEngineSpecification
     {
+        private readonly Logger _logger;
         public string RejectionReason { get { return "Sample"; } }
 
+        public NotSampleSpecification(Logger logger)
+        {
+            _logger = logger;
+        }
+
         public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
         {
             if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes())
             {
+                _logger.Trace("Sample release, rejecting.");
                 return false;
             }
 
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs
index 40c0a3ad0..b4bcc1f83 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
         public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
         {
             _logger.Trace("Checking if report meets quality requirements. {0}", subject.ParsedEpisodeInfo.Quality);
-            if (!subject.Series.QualityProfile.Value.Allowed.Contains(subject.ParsedEpisodeInfo.Quality.Quality))
+            if (!subject.Series.QualityProfile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedEpisodeInfo.Quality.Quality))
             {
                 _logger.Trace("Quality {0} rejected by Series' quality profile", subject.ParsedEpisodeInfo.Quality);
                 return false;
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs
index e6d6ef366..b2e034cac 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs
@@ -51,6 +51,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
 
                     if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
                     {
+                        _logger.Trace("Latest history item is downloading, rejecting.");
                         return false;
                     }
                 }
@@ -59,11 +60,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
 
             foreach (var episode in subject.Episodes)
             {
-                var bestQualityInHistory = _historyService.GetBestQualityInHistory(episode.Id);
+                var bestQualityInHistory = _historyService.GetBestQualityInHistory(subject.Series.QualityProfile, episode.Id);
                 if (bestQualityInHistory != null)
                 {
                     _logger.Trace("Comparing history quality with report. History is {0}", bestQualityInHistory);
-                    if (!_qualityUpgradableSpecification.IsUpgradable(bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
+                    if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
                         return false;
                 }
             }
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs
index f0a133409..6d099eeba 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
                 {
                     if (file.DateAdded < DateTime.Today.AddDays(-7))
                     {
-                        _logger.Trace("Proper for old file, skipping: {0}", subject);
+                        _logger.Trace("Proper for old file, rejecting: {0}", subject);
                         return false;
                     }
 
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs
index 004f20134..8d2c5f631 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
             {
                 _logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality);
 
-                if (!_qualityUpgradableSpecification.IsUpgradable(file.Quality, subject.ParsedEpisodeInfo.Quality))
+                if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality))
                 {
                     return false;
                 }
diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabCommunicationProxy.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabCommunicationProxy.cs
index a372185eb..8e181a25e 100644
--- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabCommunicationProxy.cs
+++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabCommunicationProxy.cs
@@ -31,10 +31,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
             var action = String.Format("mode=addfile&cat={0}&priority={1}", category, priority);
 
             request.AddFile("name", ReadFully(nzb), title, "application/x-nzb");
-            
-            var response = Json.TryDeserialize<SabAddResponse>(ProcessRequest(request, action));
 
-            if (response == null)
+            SabAddResponse response;
+
+            if (!Json.TryDeserialize<SabAddResponse>(ProcessRequest(request, action), out response))
             {
                 response = new SabAddResponse();
                 response.Status = true;
@@ -87,9 +87,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
                 throw new ApplicationException("Unable to connect to SABnzbd, please check your settings");
             }
 
-            var result = Json.TryDeserialize<SabJsonError>(response.Content);
+            SabJsonError result;
 
-            if (result == null)
+            if (!Json.TryDeserialize<SabJsonError>(response.Content, out result))
             {
                 //Handle plain text responses from SAB
                 result = new SabJsonError();
diff --git a/src/NzbDrone.Core/Download/DownloadApprovedReports.cs b/src/NzbDrone.Core/Download/DownloadApprovedReports.cs
index 46f54d1aa..1added8cc 100644
--- a/src/NzbDrone.Core/Download/DownloadApprovedReports.cs
+++ b/src/NzbDrone.Core/Download/DownloadApprovedReports.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
 using System.Linq;
 using NLog;
 using NzbDrone.Core.DecisionEngine.Specifications;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Download
 {
@@ -16,7 +18,7 @@ namespace NzbDrone.Core.Download
         private readonly IDownloadService _downloadService;
         private readonly Logger _logger;
 
-        public DownloadApprovedReports(IDownloadService downloadService, Logger logger)
+        public DownloadApprovedReports(IDownloadService downloadService,  Logger logger)
         {
             _downloadService = downloadService;
             _logger = logger;
@@ -57,11 +59,13 @@ namespace NzbDrone.Core.Download
         public List<DownloadDecision> GetQualifiedReports(IEnumerable<DownloadDecision> decisions)
         {
             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.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count)
-                            .ThenBy(c => c.RemoteEpisode.Release.Age)
-                            .ToList();
+                .GroupBy(c => c.RemoteEpisode.Series.Id, (i,s) => s
+                    .OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.QualityProfile))
+                    .ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault())
+                    .ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count)
+                    .ThenBy(c => c.RemoteEpisode.Release.Age))
+                .SelectMany(c => c)
+                .ToList();
         }
     }
 }
diff --git a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs
index 188ab24f9..6950081c2 100644
--- a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs
+++ b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Download
diff --git a/src/NzbDrone.Core/History/History.cs b/src/NzbDrone.Core/History/History.cs
index 94b345e59..4208039e4 100644
--- a/src/NzbDrone.Core/History/History.cs
+++ b/src/NzbDrone.Core/History/History.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.History
diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs
index b1fdcad7a..b53c7faca 100644
--- a/src/NzbDrone.Core/History/HistoryRepository.cs
+++ b/src/NzbDrone.Core/History/HistoryRepository.cs
@@ -4,6 +4,7 @@ using System.Linq;
 using Marr.Data.QGen;
 using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.History
diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs
index 4c8758618..4b3282b8f 100644
--- a/src/NzbDrone.Core/History/HistoryService.cs
+++ b/src/NzbDrone.Core/History/HistoryService.cs
@@ -6,6 +6,7 @@ using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.MediaFiles.Events;
 using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.History
@@ -15,7 +16,7 @@ namespace NzbDrone.Core.History
         List<History> All();
         void Purge();
         void Trim();
-        QualityModel GetBestQualityInHistory(int episodeId);
+        QualityModel GetBestQualityInHistory(QualityProfile qualityProfile, int episodeId);
         PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
         List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType);
         List<History> Failed();
@@ -80,9 +81,12 @@ namespace NzbDrone.Core.History
             _historyRepository.Trim();
         }
 
-        public QualityModel GetBestQualityInHistory(int episodeId)
+        public QualityModel GetBestQualityInHistory(QualityProfile qualityProfile, int episodeId)
         {
-            return _historyRepository.GetBestQualityInHistory(episodeId).OrderByDescending(q => q).FirstOrDefault();
+            var comparer = new QualityModelComparer(qualityProfile);
+            return _historyRepository.GetBestQualityInHistory(episodeId)
+                .OrderByDescending(q => q, comparer)
+                .FirstOrDefault();
         }
 
         public void Handle(EpisodeGrabbedEvent message)
diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs
index bca173fcc..e59bcf4c9 100644
--- a/src/NzbDrone.Core/Indexers/IndexerBase.cs
+++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Indexers
         }
 
         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);
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Indexers
 
         public override string ToString()
         {
-            return GetType().Name;
+            return Definition.Name;
         }
     }
 
diff --git a/src/NzbDrone.Core/Jobs/Scheduler.cs b/src/NzbDrone.Core/Jobs/Scheduler.cs
index e0d6e8194..6588d3de8 100644
--- a/src/NzbDrone.Core/Jobs/Scheduler.cs
+++ b/src/NzbDrone.Core/Jobs/Scheduler.cs
@@ -27,16 +27,6 @@ namespace NzbDrone.Core.Jobs
             _logger = logger;
         }
 
-        public void Handle(ApplicationStartedEvent message)
-        {
-            _cancellationTokenSource = new CancellationTokenSource();
-            Timer.Interval = 1000 * 30;
-            Timer.Elapsed += (o, args) => Task.Factory.StartNew(ExecuteCommands, _cancellationTokenSource.Token)
-                .LogExceptions();
-            
-            Timer.Start();
-        }
-
         private void ExecuteCommands()
         {
             try
@@ -70,8 +60,19 @@ namespace NzbDrone.Core.Jobs
             }
         }
 
+        public void Handle(ApplicationStartedEvent message)
+        {
+            _cancellationTokenSource = new CancellationTokenSource();
+            Timer.Interval = 1000 * 30;
+            Timer.Elapsed += (o, args) => Task.Factory.StartNew(ExecuteCommands, _cancellationTokenSource.Token)
+                .LogExceptions();
+
+            Timer.Start();
+        }
+
         public void Handle(ApplicationShutdownRequested message)
         {
+            _logger.Info("Shutting down scheduler");
             _cancellationTokenSource.Cancel(true);
             Timer.Stop();
         }
diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs
index 0470c17ee..e40e08f8e 100644
--- a/src/NzbDrone.Core/Jobs/TaskManager.cs
+++ b/src/NzbDrone.Core/Jobs/TaskManager.cs
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Jobs
                     new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
                 };
 
-            var currentTasks = _scheduledTaskRepository.All();
+            var currentTasks = _scheduledTaskRepository.All().ToList();
 
             _logger.Debug("Initializing jobs. Available: {0} Existing:{1}", defaultTasks.Count(), currentTasks.Count());
 
@@ -76,6 +76,11 @@ namespace NzbDrone.Core.Jobs
 
                 currentDefinition.Interval = defaultTask.Interval;
 
+                if (currentDefinition.Id == 0)
+                {
+                    currentDefinition.LastExecution = DateTime.UtcNow;
+                }
+
                 _scheduledTaskRepository.Upsert(currentDefinition);
             }
         }
diff --git a/src/NzbDrone.Core/Lifecycle/ApplicationShutdownRequested.cs b/src/NzbDrone.Core/Lifecycle/ApplicationShutdownRequested.cs
index 6c343546e..50446ed1d 100644
--- a/src/NzbDrone.Core/Lifecycle/ApplicationShutdownRequested.cs
+++ b/src/NzbDrone.Core/Lifecycle/ApplicationShutdownRequested.cs
@@ -4,6 +4,15 @@ namespace NzbDrone.Core.Lifecycle
 {
     public class ApplicationShutdownRequested : IEvent
     {
+        public bool Restarting { get; set; }
 
+        public ApplicationShutdownRequested()
+        {
+        }
+
+        public ApplicationShutdownRequested(bool restarting)
+        {
+            Restarting = restarting;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Lifecycle/Commands/RestartCommand.cs b/src/NzbDrone.Core/Lifecycle/Commands/RestartCommand.cs
new file mode 100644
index 000000000..82c20cc07
--- /dev/null
+++ b/src/NzbDrone.Core/Lifecycle/Commands/RestartCommand.cs
@@ -0,0 +1,8 @@
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.Lifecycle.Commands
+{
+    public class RestartCommand : Command
+    {
+    }
+}
diff --git a/src/NzbDrone.Core/Lifecycle/Commands/ShutdownCommand.cs b/src/NzbDrone.Core/Lifecycle/Commands/ShutdownCommand.cs
new file mode 100644
index 000000000..b0fffd8e5
--- /dev/null
+++ b/src/NzbDrone.Core/Lifecycle/Commands/ShutdownCommand.cs
@@ -0,0 +1,8 @@
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.Lifecycle.Commands
+{
+    public class ShutdownCommand : Command
+    {
+    }
+}
diff --git a/src/NzbDrone.Core/Lifecycle/LifecycleService.cs b/src/NzbDrone.Core/Lifecycle/LifecycleService.cs
new file mode 100644
index 000000000..25199b49f
--- /dev/null
+++ b/src/NzbDrone.Core/Lifecycle/LifecycleService.cs
@@ -0,0 +1,69 @@
+using System;
+using NLog;
+using NzbDrone.Common;
+using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Processes;
+using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Lifecycle.Commands;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
+using IServiceProvider = NzbDrone.Common.IServiceProvider;
+
+namespace NzbDrone.Core.Lifecycle
+{
+    public class LifecycleService: IExecute<ShutdownCommand>, IExecute<RestartCommand>
+    {
+        private readonly IEventAggregator _eventAggregator;
+        private readonly IRuntimeInfo _runtimeInfo;
+        private readonly IServiceProvider _serviceProvider;
+        private readonly IProcessProvider _processProvider;
+        private readonly Logger _logger;
+
+
+        public LifecycleService(IEventAggregator eventAggregator,
+                                IRuntimeInfo runtimeInfo,
+                                IServiceProvider serviceProvider,
+                                IProcessProvider processProvider,
+                                Logger logger)
+        {
+            _eventAggregator = eventAggregator;
+            _runtimeInfo = runtimeInfo;
+            _serviceProvider = serviceProvider;
+            _processProvider = processProvider;
+            _logger = logger;
+        }
+
+        public void Execute(ShutdownCommand message)
+        {
+            _logger.Info("Shutdown requested.");
+            _eventAggregator.PublishEvent(new ApplicationShutdownRequested());
+            
+            if (_runtimeInfo.IsWindowsService)
+            {
+                _serviceProvider.Stop(ServiceProvider.NZBDRONE_SERVICE_NAME);
+            }
+        }
+
+        public void Execute(RestartCommand message)
+        {
+            _logger.Info("Restart requested.");
+
+            if (OsInfo.IsLinux)
+            {
+                _processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, "--terminateexisting --nobrowser");
+            }
+
+            _eventAggregator.PublishEvent(new ApplicationShutdownRequested(true));
+
+            if (_runtimeInfo.IsWindowsService)
+            {
+                _serviceProvider.Restart(ServiceProvider.NZBDRONE_SERVICE_NAME);
+            }
+
+            else
+            {
+                _processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, "--terminateexisting --nobrowser");
+            }
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RescanSeriesCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RescanSeriesCommand.cs
new file mode 100644
index 000000000..81ee0951f
--- /dev/null
+++ b/src/NzbDrone.Core/MediaFiles/Commands/RescanSeriesCommand.cs
@@ -0,0 +1,17 @@
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.MediaFiles.Commands
+{
+    public class RescanSeriesCommand : Command
+    {
+        public int SeriesId { get; set; }
+
+        public override bool SendUpdatesToClient
+        {
+            get
+            {
+                return true;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs
index 6c237fa6d..0b1e51be6 100644
--- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs
+++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
 using System.Linq;
 using NLog;
 using NzbDrone.Common;
@@ -21,13 +22,15 @@ namespace NzbDrone.Core.MediaFiles
 
     public class DiskScanService :
         IDiskScanService,
-        IHandle<SeriesUpdatedEvent>
+        IHandle<SeriesUpdatedEvent>,
+        IExecute<RescanSeriesCommand>
     {
         private readonly IDiskProvider _diskProvider;
         private readonly IMakeImportDecision _importDecisionMaker;
         private readonly IImportApprovedEpisodes _importApprovedEpisodes;
         private readonly ICommandExecutor _commandExecutor;
         private readonly IConfigService _configService;
+        private readonly ISeriesService _seriesService;
         private readonly Logger _logger;
 
         public DiskScanService(IDiskProvider diskProvider,
@@ -35,6 +38,7 @@ namespace NzbDrone.Core.MediaFiles
                                 IImportApprovedEpisodes importApprovedEpisodes,
                                 ICommandExecutor commandExecutor,
                                 IConfigService configService,
+                                ISeriesService seriesService,
                                 Logger logger)
         {
             _diskProvider = diskProvider;
@@ -42,6 +46,7 @@ namespace NzbDrone.Core.MediaFiles
             _importApprovedEpisodes = importApprovedEpisodes;
             _commandExecutor = commandExecutor;
             _configService = configService;
+            _seriesService = seriesService;
             _logger = logger;
         }
 
@@ -90,5 +95,12 @@ namespace NzbDrone.Core.MediaFiles
         {
             Scan(message.Series);
         }
+
+        public void Execute(RescanSeriesCommand message)
+        {
+            var series = _seriesService.GetSeries(message.SeriesId);
+
+            Scan(series);
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
index 174693c7c..cf82620de 100644
--- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
+++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
@@ -10,6 +10,7 @@ using NzbDrone.Core.MediaFiles.Commands;
 using NzbDrone.Core.MediaFiles.EpisodeImport;
 using NzbDrone.Core.Messaging.Commands;
 using NzbDrone.Core.Parser;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.MediaFiles
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs
index 7fac2d1ae..12dccb7b0 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs
@@ -1,5 +1,6 @@
 using System;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.MediaFiles
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs
index c9fe15e58..a18b435c9 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs
@@ -7,6 +7,8 @@ using NzbDrone.Common;
 using NzbDrone.Common.Disk;
 using NzbDrone.Core.MediaFiles.Events;
 using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Tv;
 
 
 namespace NzbDrone.Core.MediaFiles.EpisodeImport
@@ -40,8 +42,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
         public List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownload = false)
         {
             var qualifiedImports = decisions.Where(c => c.Approved)
-                .OrderByDescending(c => c.LocalEpisode.Quality)
-                .ThenByDescending(c => c.LocalEpisode.Size);
+                .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s
+                    .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.QualityProfile))
+                    .ThenByDescending(c => c.LocalEpisode.Size))
+                .SelectMany(c => c)
+                .ToList();
 
             var imported = new List<ImportDecision>();
 
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
index 56e49d2c2..8b7c620bb 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
@@ -7,6 +7,7 @@ using NzbDrone.Common.Disk;
 using NzbDrone.Core.DecisionEngine;
 using NzbDrone.Core.Parser;
 using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 
@@ -60,7 +61,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
                     
                     if (parsedEpisode != null)
                     {
-                        if (quality != null && quality > parsedEpisode.Quality)
+                        if (quality != null && new QualityModelComparer(parsedEpisode.Series.QualityProfile).Compare(quality, parsedEpisode.Quality) > 0)
                         {
                             _logger.Trace("Using quality from folder: {0}", quality);
                             parsedEpisode.Quality = quality;
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs
index 58b5ac75d..c637ac5cd 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs
@@ -1,6 +1,8 @@
 using System.Linq;
 using NLog;
 using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
 {
@@ -17,7 +19,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
 
         public bool IsSatisfiedBy(LocalEpisode localEpisode)
         {
-            if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && e.EpisodeFile.Value.Quality > localEpisode.Quality))
+            var qualityComparer = new QualityModelComparer(localEpisode.Series.QualityProfile);
+            if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && qualityComparer.Compare(e.EpisodeFile.Value.Quality, localEpisode.Quality) > 0))
             {
                 _logger.Trace("This file isn't an upgrade for all episodes. Skipping {0}", localEpisode.Path);
                 return false;
diff --git a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs
index a48d2b28d..b1143db3f 100644
--- a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs
+++ b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs
@@ -7,5 +7,6 @@ namespace NzbDrone.Core.Notifications
     {
         public Boolean OnGrab { get; set; }
         public Boolean OnDownload { get; set; }
+        public Boolean OnUpgrade { get; set; }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Notifications/NotificationFactory.cs b/src/NzbDrone.Core/Notifications/NotificationFactory.cs
index 54037acef..4a4fc9bc2 100644
--- a/src/NzbDrone.Core/Notifications/NotificationFactory.cs
+++ b/src/NzbDrone.Core/Notifications/NotificationFactory.cs
@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Notifications
     {
         List<INotification> OnGrabEnabled();
         List<INotification> OnDownloadEnabled();
+        List<INotification> OnUpgradeEnabled();
     }
 
     public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory
@@ -28,5 +29,10 @@ namespace NzbDrone.Core.Notifications
         {
             return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnDownload).ToList();
         }
+
+        public List<INotification> OnUpgradeEnabled()
+        {
+            return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnUpgrade).ToList();
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs
index a948fa830..e52dd1fac 100644
--- a/src/NzbDrone.Core/Notifications/NotificationService.cs
+++ b/src/NzbDrone.Core/Notifications/NotificationService.cs
@@ -5,6 +5,7 @@ using NLog;
 using NzbDrone.Core.Download;
 using NzbDrone.Core.MediaFiles.Events;
 using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Notifications
@@ -79,6 +80,11 @@ namespace NzbDrone.Core.Notifications
             {
                 try
                 {
+                    if (downloadMessage.OldFiles.Any() && !((NotificationDefinition) notification.Definition).OnUpgrade)
+                    {
+                        continue;
+                    }
+
                     notification.OnDownload(downloadMessage);
                 }
 
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 3c42a4d2a..c09e55f48 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -192,6 +192,9 @@
     <Compile Include="Datastore\Migration\033_add_api_key_to_pushover.cs" />
     <Compile Include="Datastore\Migration\034_remove_series_contraints.cs" />
     <Compile Include="Datastore\Migration\035_add_series_folder_format_to_naming_config.cs" />
+    <Compile Include="Datastore\Migration\036_update_with_quality_converters.cs" />
+    <Compile Include="Datastore\Migration\037_add_configurable_qualities.cs" />
+    <Compile Include="Datastore\Migration\038_add_on_upgrade_to_notifications.cs" />
     <Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
     <Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
     <Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
@@ -287,6 +290,10 @@
     <Compile Include="Instrumentation\Commands\DeleteLogFilesCommand.cs" />
     <Compile Include="Instrumentation\Commands\TrimLogCommand.cs" />
     <Compile Include="Instrumentation\DeleteLogFilesService.cs" />
+    <Compile Include="MediaFiles\Commands\RescanSeriesCommand.cs" />
+    <Compile Include="Lifecycle\Commands\ShutdownCommand.cs" />
+    <Compile Include="Lifecycle\Commands\RestartCommand.cs" />
+    <Compile Include="Lifecycle\LifecycleService.cs" />
     <Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
     <Compile Include="MediaFiles\EpisodeFileMoveResult.cs" />
     <Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" />
@@ -459,12 +466,14 @@
     <Compile Include="Parser\Parser.cs" />
     <Compile Include="Parser\ParsingService.cs" />
     <Compile Include="Parser\QualityParser.cs" />
+    <Compile Include="Qualities\QualityModelComparer.cs" />
+    <Compile Include="Qualities\QualityProfileItem.cs" />
     <Compile Include="Rest\JsonNetSerializer.cs" />
     <Compile Include="RootFolders\RootFolderRepository.cs" />
     <Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
     <Compile Include="ThingiProvider\IProvider.cs" />
     <Compile Include="Qualities\QualityProfileInUseException.cs" />
-    <Compile Include="Qualities\QualitySizeRepository.cs" />
+    <Compile Include="Qualities\QualityDefinitionRepository.cs" />
     <Compile Include="Qualities\QualityProfileRepository.cs" />
     <Compile Include="Queue\Queue.cs" />
     <Compile Include="Queue\UpdateQueueEvent.cs" />
@@ -481,6 +490,7 @@
     <Compile Include="ThingiProvider\ProviderRepository.cs" />
     <Compile Include="ThingiProvider\ProviderFactory.cs" />
     <Compile Include="Tv\EpisodeService.cs" />
+    <Compile Include="Tv\Events\SeriesEditedEvent.cs" />
     <Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
     <Compile Include="Tv\Events\EpisodeInfoDeletedEvent.cs" />
     <Compile Include="Tv\Events\EpisodeInfoUpdatedEvent.cs" />
@@ -492,7 +502,7 @@
     <Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
     <Compile Include="Tv\RefreshEpisodeService.cs" />
     <Compile Include="Tv\SeriesRepository.cs" />
-    <Compile Include="Tv\QualityModel.cs" />
+    <Compile Include="Qualities\QualityModel.cs" />
     <Compile Include="Download\Clients\Sabnzbd\SabAddResponse.cs" />
     <Compile Include="Download\Clients\Sabnzbd\SabHistoryItem.cs" />
     <Compile Include="Download\Clients\Sabnzbd\SabHistory.cs" />
@@ -578,7 +588,7 @@
     <Compile Include="Qualities\QualityProfileService.cs">
       <SubType>Code</SubType>
     </Compile>
-    <Compile Include="Qualities\QualitySizeService.cs">
+    <Compile Include="Qualities\QualityDefinitionService.cs">
       <SubType>Code</SubType>
     </Compile>
     <Compile Include="RootFolders\UnmappedFolder.cs" />
@@ -600,7 +610,7 @@
     <Compile Include="Tv\Episode.cs" />
     <Compile Include="Instrumentation\Log.cs" />
     <Compile Include="History\History.cs" />
-    <Compile Include="Qualities\QualitySize.cs" />
+    <Compile Include="Qualities\QualityDefinition.cs" />
     <Compile Include="Qualities\QualityProfile.cs" />
     <Compile Include="RootFolders\RootFolder.cs" />
     <Compile Include="Tv\Series.cs" />
diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
index 72a4ab75c..2997be7d5 100644
--- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
 using NLog;
 using NzbDrone.Common.Cache;
 using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Organizer
@@ -22,6 +23,7 @@ namespace NzbDrone.Core.Organizer
     public class FileNameBuilder : IBuildFileNames
     {
         private readonly INamingConfigService _namingConfigService;
+        private readonly IQualityDefinitionService _qualityDefinitionService;
         private readonly ICached<EpisodeFormat> _patternCache;
         private readonly Logger _logger;
 
@@ -43,10 +45,12 @@ namespace NzbDrone.Core.Organizer
                                                                             RegexOptions.Compiled | RegexOptions.IgnoreCase);
 
         public FileNameBuilder(INamingConfigService namingConfigService,
+                               IQualityDefinitionService qualityDefinitionService,
                                ICacheManger cacheManger,
                                Logger logger)
         {
             _namingConfigService = namingConfigService;
+            _qualityDefinitionService = qualityDefinitionService;
             _patternCache = cacheManger.GetCache<EpisodeFormat>(GetType());
             _logger = logger;
         }
@@ -87,12 +91,10 @@ namespace NzbDrone.Core.Organizer
                 sortedEpisodes.First().Title
             };
 
-            var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance)
-            {
-                {"{Series Title}", series.Title},
-                {"Original Title", episodeFile.SceneName}
-            };
+            var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
 
+            tokenValues.Add("{Series Title}", series.Title);
+            tokenValues.Add("{Original Title}", episodeFile.SceneName);
             tokenValues.Add("{Release Group}", episodeFile.ReleaseGroup);
 
             if (series.SeriesType == SeriesTypes.Daily)
@@ -146,9 +148,8 @@ namespace NzbDrone.Core.Organizer
             }
 
             tokenValues.Add("{Episode Title}", GetEpisodeTitle(episodeTitles));
-            tokenValues.Add("{Quality Title}", episodeFile.Quality.ToString());
+            tokenValues.Add("{Quality Title}", GetQualityTitle(episodeFile.Quality));
             
-
             return CleanFilename(ReplaceTokens(pattern, tokenValues).Trim());
         }
 
@@ -341,6 +342,14 @@ namespace NzbDrone.Core.Organizer
 
             return String.Join(" + ", episodeTitles.Select(Parser.Parser.CleanupEpisodeTitle).Distinct());
         }
+
+        private string GetQualityTitle(QualityModel quality)
+        {
+            if (quality.Proper)
+                return _qualityDefinitionService.Get(quality.Quality).Title + " Proper";
+            else
+                return _qualityDefinitionService.Get(quality.Quality).Title;
+        }
     }
 
     public enum MultiEpisodeStyle
diff --git a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs
index 06f620a96..447055328 100644
--- a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs
+++ b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs
@@ -1,7 +1,8 @@
 using System;
-using System.Collections.Generic;
-using NzbDrone.Core.Tv;
 using System.Linq;
+using System.Collections.Generic;
+using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Parser.Model
 {
diff --git a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs
index 7ae94f647..fffc79c96 100644
--- a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs
+++ b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Linq;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Parser.Model
diff --git a/src/NzbDrone.Core/Qualities/Quality.cs b/src/NzbDrone.Core/Qualities/Quality.cs
index 5d62d3b04..673abc5f7 100644
--- a/src/NzbDrone.Core/Qualities/Quality.cs
+++ b/src/NzbDrone.Core/Qualities/Quality.cs
@@ -2,65 +2,24 @@
 using System.Collections.Generic;
 using System.Linq;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Datastore.Converters;
+
 
 namespace NzbDrone.Core.Qualities
 {
-    public class Quality : IComparable<Quality>, IEmbeddedDocument
+    public class Quality : IEmbeddedDocument, IEquatable<Quality>
     {
         public int Id { get; set; }
         public string Name { get; set; }
-        public int Weight { get; set; }
 
-        public int CompareTo(Quality other)
+        public Quality()
         {
-            if (other.Weight > Weight)
-                return -1;
-
-            if (other.Weight < Weight)
-                return 1;
-
-            if (other.Weight == Weight)
-                return 0;
-
-            return 0;
         }
 
-        public static bool operator !=(Quality x, Quality y)
+        private Quality(int id, string name)
         {
-            return !(x == y);
-        }
-
-        public static bool operator ==(Quality x, Quality y)
-        {
-            var xObj = (Object)x;
-            var yObj = (object)y;
-
-            if (xObj == null || yObj == null)
-            {
-                return xObj == yObj;
-            }
-
-            return x.CompareTo(y) == 0;
-        }
-
-        public static bool operator >(Quality x, Quality y)
-        {
-            return x.CompareTo(y) > 0;
-        }
-
-        public static bool operator <(Quality x, Quality y)
-        {
-            return x.CompareTo(y) < 0;
-        }
-
-        public static bool operator <=(Quality x, Quality y)
-        {
-            return x.CompareTo(y) <= 0;
-        }
-
-        public static bool operator >=(Quality x, Quality y)
-        {
-            return x.CompareTo(y) >= 0;
+            Id = id;
+            Name = name;
         }
 
         public override string ToString()
@@ -70,110 +29,96 @@ namespace NzbDrone.Core.Qualities
 
         public override int GetHashCode()
         {
-            unchecked // Overflow is fine, just wrap
-            {
-                int hash = 17;
-                hash = hash * 23 + Weight.GetHashCode();
-                return hash;
-            }
+            return Id.GetHashCode();
         }
 
         public bool Equals(Quality other)
         {
             if (ReferenceEquals(null, other)) return false;
             if (ReferenceEquals(this, other)) return true;
-            return Equals(other.Weight, Weight);
+            return Id.Equals(other.Id);
         }
 
         public override bool Equals(object obj)
         {
             if (ReferenceEquals(null, obj)) return false;
             if (ReferenceEquals(this, obj)) return true;
-            if (obj.GetType() != typeof(Quality)) return false;
-            return Equals((Quality)obj);
+
+            return Equals(obj as Quality);
         }
 
-        public static Quality Unknown
+        public static bool operator ==(Quality left, Quality right)
         {
-            get { return new Quality { Id = 0, Name = "Unknown", Weight = 0 }; }
+            return Equals(left, right);
         }
 
-        public static Quality SDTV
+        public static bool operator !=(Quality left, Quality right)
         {
-            get { return new Quality { Id = 1, Name = "SDTV", Weight = 1 }; }
+            return !Equals(left, right);
         }
 
-        public static Quality WEBDL480p
+        public static Quality Unknown     { get { return new Quality(0, "Unknown"); } }
+        public static Quality SDTV        { get { return new Quality(1, "SDTV"); } }
+        public static Quality DVD         { get { return new Quality(2, "DVD"); } }
+        public static Quality WEBDL1080p  { get { return new Quality(3, "WEBDL-1080p"); } }
+        public static Quality HDTV720p    { get { return new Quality(4, "HDTV-720p"); } }
+        public static Quality WEBDL720p   { get { return new Quality(5, "WEBDL-720p"); } }
+        public static Quality Bluray720p  { get { return new Quality(6, "Bluray-720p"); } }
+        public static Quality Bluray1080p { get { return new Quality(7, "Bluray-1080p"); } }
+        public static Quality WEBDL480p   { get { return new Quality(8, "WEBDL-480p"); } }
+        public static Quality HDTV1080p   { get { return new Quality(9, "HDTV-1080p"); } }
+        public static Quality RAWHD       { get { return new Quality(10, "Raw-HD"); } }
+        //public static Quality HDTV480p    { get { return new Quality(11, "HDTV-480p"); } }
+        
+        public static List<Quality> All
         {
-            get { return new Quality { Id = 8, Name = "WEBDL-480p", Weight = 2 }; }
+            get
+            {
+                return new List<Quality>
+                {
+                    SDTV,
+                    DVD,
+                    WEBDL1080p,
+                    HDTV720p,
+                    WEBDL720p,
+                    Bluray720p,
+                    Bluray1080p,
+                    WEBDL480p,
+                    HDTV1080p,
+                    RAWHD
+                };
+            }
         }
 
-        public static Quality DVD
+        public static HashSet<QualityDefinition> DefaultQualityDefinitions
         {
-            get { return new Quality { Id = 2, Name = "DVD", Weight = 3 }; }
-        }
-
-        public static Quality HDTV720p
-        {
-            get { return new Quality { Id = 4, Name = "HDTV-720p", Weight = 4 }; }
-        }
-
-        public static Quality HDTV1080p
-        {
-            get { return new Quality { Id = 9, Name = "HDTV-1080p", Weight = 5 }; }
-        }
-
-        public static Quality RAWHD
-        {
-            get { return new Quality { Id = 10, Name = "Raw-HD", Weight = 6 }; }
-        }
-
-        public static Quality WEBDL720p
-        {
-            get { return new Quality { Id = 5, Name = "WEBDL-720p", Weight = 7 }; }
-        }
-
-        public static Quality Bluray720p
-        {
-            get { return new Quality { Id = 6, Name = "Bluray720p", Weight = 8 }; }
-        }
-
-        public static Quality WEBDL1080p
-        {
-            get { return new Quality { Id = 3, Name = "WEBDL-1080p", Weight = 9 }; }
-        }
-
-        public static Quality Bluray1080p
-        {
-            get { return new Quality { Id = 7, Name = "Bluray1080p", Weight = 10 }; }
-        }
-
-        public static List<Quality> All()
-        {
-            return new List<Quality>
-                       {
-                               SDTV,
-                               WEBDL480p,
-                               DVD,
-                               HDTV720p,
-                               HDTV1080p,
-                               RAWHD,
-                               WEBDL720p,
-                               WEBDL1080p,
-                               Bluray720p,
-                               Bluray1080p
-                       };
+            get
+            {
+                return new HashSet<QualityDefinition>
+                {
+                    new QualityDefinition(Quality.SDTV)        { /*Title = "SDTV",        */ Weight = 1,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.WEBDL480p)   { /*Title = "WEB-DL",      */ Weight = 2,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.DVD)         { /*Title = "DVD",         */ Weight = 3,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.HDTV720p)    { /*Title = "720p HDTV",   */ Weight = 4,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.HDTV1080p)   { /*Title = "1080p HDTV",  */ Weight = 5,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.RAWHD)       { /*Title = "RawHD",       */ Weight = 6,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.WEBDL720p)   { /*Title = "720p WEB-DL", */ Weight = 7,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.Bluray720p)  { /*Title = "720p BluRay", */ Weight = 8,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.WEBDL1080p)  { /*Title = "1080p WEB-DL",*/ Weight = 9,  MinSize=0, MaxSize=100 },
+                    new QualityDefinition(Quality.Bluray1080p) { /*Title = "1080p BluRay",*/ Weight = 10, MinSize=0, MaxSize=100 }
+                };
+            }
         }
 
         public static Quality FindById(int id)
         {
             if (id == 0) return Unknown;
 
-            var quality = All().SingleOrDefault(q => q.Id == id);
+            Quality quality = All.FirstOrDefault(v => v.Id == id);
 
             if (quality == null)
                 throw new ArgumentException("ID does not match a known quality", "id");
-
+                        
             return quality;
         }
 
diff --git a/src/NzbDrone.Core/Qualities/QualityDefinition.cs b/src/NzbDrone.Core/Qualities/QualityDefinition.cs
new file mode 100644
index 000000000..14cc1f008
--- /dev/null
+++ b/src/NzbDrone.Core/Qualities/QualityDefinition.cs
@@ -0,0 +1,33 @@
+using NzbDrone.Core.Datastore;
+
+
+namespace NzbDrone.Core.Qualities
+{
+    public class QualityDefinition : ModelBase
+    {
+        public Quality Quality { get; set; }
+
+        public string Title { get; set; }
+
+        public int Weight { get; set; }
+
+        public int MinSize { get; set; }
+        public int MaxSize { get; set; }
+
+        public QualityDefinition()
+        {
+
+        }
+
+        public QualityDefinition(Quality quality)
+        {
+            Quality = quality;
+            Title = quality.Name;
+        }
+
+        public override string ToString()
+        {
+            return Quality.Name;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Qualities/QualityDefinitionRepository.cs b/src/NzbDrone.Core/Qualities/QualityDefinitionRepository.cs
new file mode 100644
index 000000000..f73fb2de8
--- /dev/null
+++ b/src/NzbDrone.Core/Qualities/QualityDefinitionRepository.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Linq;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging.Events;
+
+
+namespace NzbDrone.Core.Qualities
+{
+    public interface IQualityDefinitionRepository : IBasicRepository<QualityDefinition>
+    {
+        QualityDefinition GetByQualityId(int qualityId);
+    }
+
+    public class QualityDefinitionRepository : BasicRepository<QualityDefinition>, IQualityDefinitionRepository
+    {
+        public QualityDefinitionRepository(IDatabase database, IEventAggregator eventAggregator)
+            : base(database, eventAggregator)
+        {
+        }
+
+        public QualityDefinition GetByQualityId(int qualityId)
+        {
+            try
+            {
+                return Query.Single(q => (int)q.Quality == qualityId);
+            }
+            catch (InvalidOperationException e)
+            {
+                throw new ModelNotFoundException(typeof(QualityDefinition), qualityId);
+            }
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Qualities/QualityDefinitionService.cs b/src/NzbDrone.Core/Qualities/QualityDefinitionService.cs
new file mode 100644
index 000000000..9895461ec
--- /dev/null
+++ b/src/NzbDrone.Core/Qualities/QualityDefinitionService.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using System.Linq;
+using NLog;
+using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging.Events;
+using System;
+
+namespace NzbDrone.Core.Qualities
+{
+    public interface IQualityDefinitionService
+    {
+        void Update(QualityDefinition qualityDefinition);
+        List<QualityDefinition> All();
+        QualityDefinition Get(Quality quality);
+    }
+
+    public class QualityDefinitionService : IQualityDefinitionService, IHandle<ApplicationStartedEvent>
+    {
+        private readonly IQualityDefinitionRepository _qualityDefinitionRepository;
+        private readonly Logger _logger;
+
+        public QualityDefinitionService(IQualityDefinitionRepository qualityDefinitionRepository, Logger logger)
+        {
+            _qualityDefinitionRepository = qualityDefinitionRepository;
+            _logger = logger;
+        }
+
+        public void Update(QualityDefinition qualityDefinition)
+        {
+            _qualityDefinitionRepository.Update(qualityDefinition);
+        }
+
+        public List<QualityDefinition> All()
+        {
+            return _qualityDefinitionRepository.All().ToList();
+        }
+        
+        public QualityDefinition Get(Quality quality)
+        {
+            if (quality == Quality.Unknown)
+                return new QualityDefinition(Quality.Unknown);
+
+            return _qualityDefinitionRepository.GetByQualityId((int)quality);
+        }
+        
+        public void InsertMissingDefinitions(List<QualityDefinition> allDefinitions)
+        {
+            allDefinitions.OrderBy(v => v.Weight).ToList();
+            var existingDefinitions = _qualityDefinitionRepository.All().OrderBy(v => v.Weight).ToList();
+
+            // Try insert each item intelligently to merge the lists preserving the Weight the user set.
+            for (int i = 0; i < allDefinitions.Count;i++)
+            {
+                // Skip if this definition isn't missing.
+                if (existingDefinitions.Any(v => v.Quality == allDefinitions[i].Quality))
+                    continue;
+
+                int targetIndexMinimum = 0;
+                for (int j = 0; j < i; j++)
+                    targetIndexMinimum = Math.Max(targetIndexMinimum, existingDefinitions.FindIndex(v => v.Quality == allDefinitions[j].Quality) + 1);
+
+                int targetIndexMaximum = existingDefinitions.Count;
+                for (int j = i + 1; j < allDefinitions.Count; j++)
+                {
+                    var index = existingDefinitions.FindIndex(v => v.Quality == allDefinitions[j].Quality);
+                    if (index != -1)
+                        targetIndexMaximum = Math.Min(targetIndexMaximum, index);
+                }
+
+                // Rounded down average sounds reasonable.
+                int targetIndex = (targetIndexMinimum + targetIndexMaximum) / 2;
+
+                existingDefinitions.Insert(targetIndex, allDefinitions[i]);
+            }
+            
+            // Update all Weights.
+            List<QualityDefinition> insertList = new List<QualityDefinition>();
+            List<QualityDefinition> updateList = new List<QualityDefinition>();
+            for (int i = 0; i < existingDefinitions.Count; i++)
+            {
+                if (existingDefinitions[i].Id == 0)
+                {
+                    existingDefinitions[i].Weight = i + 1;
+                    _qualityDefinitionRepository.Insert(existingDefinitions[i]);
+                }
+                else if (existingDefinitions[i].Weight != i + 1)
+                {
+                    existingDefinitions[i].Weight = i + 1;
+                    _qualityDefinitionRepository.Update(existingDefinitions[i]);
+                }
+            }
+        }
+
+        public void Handle(ApplicationStartedEvent message)
+        {            
+            _logger.Debug("Setting up default quality config");
+
+            InsertMissingDefinitions(Quality.DefaultQualityDefinitions.ToList());
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Qualities/QualityModel.cs b/src/NzbDrone.Core/Qualities/QualityModel.cs
new file mode 100644
index 000000000..ef4dc9bd0
--- /dev/null
+++ b/src/NzbDrone.Core/Qualities/QualityModel.cs
@@ -0,0 +1,73 @@
+using System;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Qualities;
+
+namespace NzbDrone.Core.Qualities
+{
+    public class QualityModel : IEmbeddedDocument, IEquatable<QualityModel>
+    {
+        public Quality Quality { get; set; }
+        
+        public Boolean Proper { get; set; }
+        
+        public QualityModel()
+            : this(Quality.Unknown)
+        {
+
+        }
+
+        public QualityModel(Quality quality, Boolean proper = false)
+        {
+            Quality = quality;
+            Proper = proper;
+        }
+
+
+        public override string ToString()
+        {
+            string result = Quality.ToString();
+            if (Proper)
+            {
+                result += " Proper";
+            }
+
+            return result;
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked // Overflow is fine, just wrap
+            {
+                int hash = 17;
+                hash = hash * 23 + Proper.GetHashCode();
+                hash = hash * 23 + Quality.GetHashCode();
+                return hash;
+            }
+        }
+
+        public bool Equals(QualityModel other)
+        {
+            if (ReferenceEquals(null, other)) return false;
+            if (ReferenceEquals(this, other)) return true;
+            return other.Quality.Equals(Quality) && other.Proper.Equals(Proper);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+
+            return Equals(obj as QualityModel);
+        }
+
+        public static bool operator ==(QualityModel left, QualityModel right)
+        {
+            return Equals(left, right);
+        }
+
+        public static bool operator !=(QualityModel left, QualityModel right)
+        {
+            return !Equals(left, right);
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Qualities/QualityModelComparer.cs b/src/NzbDrone.Core/Qualities/QualityModelComparer.cs
new file mode 100644
index 000000000..bb66dcd7f
--- /dev/null
+++ b/src/NzbDrone.Core/Qualities/QualityModelComparer.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NLog;
+using NzbDrone.Common.EnsureThat;
+
+namespace NzbDrone.Core.Qualities
+{
+    public class QualityModelComparer : IComparer<Quality>, IComparer<QualityModel>
+    {
+        private readonly QualityProfile _qualityProfile;
+
+        public QualityModelComparer(QualityProfile qualityProfile)
+        {
+            Ensure.That(qualityProfile, () => qualityProfile).IsNotNull();
+            Ensure.That(qualityProfile.Items, () => qualityProfile.Items).HasItems();
+
+            _qualityProfile = qualityProfile;
+        }
+
+        public int Compare(Quality left, Quality right)
+        {
+            int leftIndex = _qualityProfile.Items.FindIndex(v => v.Quality == left);
+            int rightIndex = _qualityProfile.Items.FindIndex(v => v.Quality == right);
+
+            return leftIndex.CompareTo(rightIndex);
+        }
+
+        public int Compare(QualityModel left, QualityModel right)
+        {
+            int result = Compare(left.Quality, right.Quality);
+
+            if (result == 0)
+                result = left.Proper.CompareTo(right.Proper);
+
+            return result;
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Qualities/QualityProfile.cs b/src/NzbDrone.Core/Qualities/QualityProfile.cs
index 892983ecd..b578d1962 100644
--- a/src/NzbDrone.Core/Qualities/QualityProfile.cs
+++ b/src/NzbDrone.Core/Qualities/QualityProfile.cs
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.Qualities
     public class QualityProfile : ModelBase
     {
         public string Name { get; set; }
-        public List<Quality> Allowed { get; set; }
         public Quality Cutoff { get; set; }
+        public List<QualityProfileItem> Items { get; set; }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Qualities/QualityProfileItem.cs b/src/NzbDrone.Core/Qualities/QualityProfileItem.cs
new file mode 100644
index 000000000..9d7d839d2
--- /dev/null
+++ b/src/NzbDrone.Core/Qualities/QualityProfileItem.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using NzbDrone.Core.Datastore;
+
+namespace NzbDrone.Core.Qualities
+{
+    public class QualityProfileItem : IEmbeddedDocument
+    {
+        public Quality Quality { get; set; }
+        public bool Allowed { get; set; }
+    }
+}
diff --git a/src/NzbDrone.Core/Qualities/QualityProfileService.cs b/src/NzbDrone.Core/Qualities/QualityProfileService.cs
index 82baaaa39..13b18afad 100644
--- a/src/NzbDrone.Core/Qualities/QualityProfileService.cs
+++ b/src/NzbDrone.Core/Qualities/QualityProfileService.cs
@@ -60,68 +60,46 @@ namespace NzbDrone.Core.Qualities
             return _qualityProfileRepository.Get(id);
         }
 
+        private QualityProfile AddDefaultQualityProfile(string name, Quality cutoff, params Quality[] allowed)
+        {
+            var items = Quality.DefaultQualityDefinitions
+                            .OrderBy(v => v.Weight)
+                            .Select(v => new QualityProfileItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) })
+                            .ToList();
+
+            var qualityProfile = new QualityProfile { Name = name, Cutoff = cutoff, Items = items };
+
+            return Add(qualityProfile);
+        }
+
         public void Handle(ApplicationStartedEvent message)
         {
             if (All().Any()) return;
 
             _logger.Info("Setting up default quality profiles");
 
-            var sd = new QualityProfile
-            {
-                Name = "SD",
-                Allowed = new List<Quality>
-                              {
-                                  Quality.SDTV,
-                                  Quality.WEBDL480p,
-                                  Quality.DVD
-                              },
-                Cutoff = Quality.SDTV
-            };
+            AddDefaultQualityProfile("SD", Quality.SDTV,
+                Quality.SDTV,
+                Quality.WEBDL480p,
+                Quality.DVD);
 
-            var hd720p = new QualityProfile
-            {
-                Name = "HD 720p",
-                Allowed = new List<Quality>
-                              {
-                                  Quality.HDTV720p,
-                                  Quality.WEBDL720p,
-                                  Quality.Bluray720p
-                              },
-                Cutoff = Quality.HDTV720p
-            };
+            AddDefaultQualityProfile("HD-720p", Quality.HDTV720p,
+                Quality.HDTV720p,
+                Quality.WEBDL720p,
+                Quality.Bluray720p);
 
+            AddDefaultQualityProfile("HD-1080p", Quality.HDTV1080p,
+                Quality.HDTV1080p,
+                Quality.WEBDL1080p,
+                Quality.Bluray1080p);
 
-            var hd1080p = new QualityProfile
-            {
-                Name = "HD 1080p",
-                Allowed = new List<Quality>
-                              {
-                                  Quality.HDTV1080p,
-                                  Quality.WEBDL1080p,
-                                  Quality.Bluray1080p
-                              },
-                Cutoff = Quality.HDTV1080p
-            };
-
-            var hdAll = new QualityProfile
-            {
-                Name = "HD - All",
-                Allowed = new List<Quality>
-                              {
-                                  Quality.HDTV720p,
-                                  Quality.WEBDL720p,
-                                  Quality.Bluray720p,
-                                  Quality.HDTV1080p,
-                                  Quality.WEBDL1080p,
-                                  Quality.Bluray1080p
-                              },
-                Cutoff = Quality.HDTV720p
-            };
-
-            Add(sd);
-            Add(hd720p);
-            Add(hd1080p);
-            Add(hdAll);
+            AddDefaultQualityProfile("HD - All", Quality.HDTV720p,
+                Quality.HDTV720p,
+                Quality.HDTV1080p,
+                Quality.WEBDL720p,
+                Quality.WEBDL1080p,
+                Quality.Bluray720p,
+                Quality.Bluray1080p);
         }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Qualities/QualitySize.cs b/src/NzbDrone.Core/Qualities/QualitySize.cs
deleted file mode 100644
index 39f7f4e29..000000000
--- a/src/NzbDrone.Core/Qualities/QualitySize.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using NzbDrone.Core.Datastore;
-
-
-namespace NzbDrone.Core.Qualities
-{
-    public class QualitySize : ModelBase
-    {
-        public int QualityId { get; set; }
-        public string Name { get; set; }
-        public int MinSize { get; set; }
-        public int MaxSize { get; set; }
-
-        public override string ToString()
-        {
-            return Name;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Qualities/QualitySizeRepository.cs b/src/NzbDrone.Core/Qualities/QualitySizeRepository.cs
deleted file mode 100644
index f96f84be3..000000000
--- a/src/NzbDrone.Core/Qualities/QualitySizeRepository.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Linq;
-using NzbDrone.Core.Datastore;
-using NzbDrone.Core.Messaging.Events;
-
-
-namespace NzbDrone.Core.Qualities
-{
-    public interface IQualitySizeRepository : IBasicRepository<QualitySize>
-    {
-        QualitySize GetByQualityId(int qualityId);
-    }
-
-    public class QualitySizeRepository : BasicRepository<QualitySize>, IQualitySizeRepository
-    {
-        public QualitySizeRepository(IDatabase database, IEventAggregator eventAggregator)
-            : base(database, eventAggregator)
-        {
-        }
-
-        public QualitySize GetByQualityId(int qualityId)
-        {
-            try
-            {
-                return Query.Single(q => q.QualityId == qualityId);
-            }
-            catch (InvalidOperationException e)
-            {
-                throw new ModelNotFoundException(typeof(QualitySize), qualityId);
-            }
-        }
-    }
-}
diff --git a/src/NzbDrone.Core/Qualities/QualitySizeService.cs b/src/NzbDrone.Core/Qualities/QualitySizeService.cs
deleted file mode 100644
index 0671a6c10..000000000
--- a/src/NzbDrone.Core/Qualities/QualitySizeService.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using NLog;
-using NzbDrone.Core.Lifecycle;
-using NzbDrone.Core.Messaging.Events;
-
-namespace NzbDrone.Core.Qualities
-{
-    public interface IQualitySizeService
-    {
-        void Update(QualitySize qualitySize);
-        List<QualitySize> All();
-        QualitySize Get(int qualityId);
-    }
-
-    public class QualitySizeService : IQualitySizeService, IHandle<ApplicationStartedEvent>
-    {
-        private readonly IQualitySizeRepository _qualitySizeRepository;
-        private readonly Logger _logger;
-
-        public QualitySizeService(IQualitySizeRepository qualitySizeRepository, Logger logger)
-        {
-            _qualitySizeRepository = qualitySizeRepository;
-            _logger = logger;
-        }
-
-        public virtual void Update(QualitySize qualitySize)
-        {
-            _qualitySizeRepository.Update(qualitySize);
-        }
-
-
-        public virtual List<QualitySize> All()
-        {
-            return _qualitySizeRepository.All().ToList();
-        }
-
-        public virtual QualitySize Get(int qualityId)
-        {
-            return _qualitySizeRepository.GetByQualityId(qualityId);
-        }
-
-        public void Handle(ApplicationStartedEvent message)
-        {
-            var existing = All();
-
-            _logger.Debug("Setting up default quality sizes");
-
-            foreach (var quality in Quality.All())
-            {
-                if (!existing.Any(s => s.QualityId == quality.Id))
-                {
-                    _qualitySizeRepository.Insert(new QualitySize
-                    {
-                        QualityId = quality.Id,
-                        Name = quality.Name,
-                        MinSize = 0,
-                        MaxSize = 100
-                    });
-                }
-            }
-        }
-    }
-}
diff --git a/src/NzbDrone.Core/Queue/Queue.cs b/src/NzbDrone.Core/Queue/Queue.cs
index e1ceb3ff8..733ff2301 100644
--- a/src/NzbDrone.Core/Queue/Queue.cs
+++ b/src/NzbDrone.Core/Queue/Queue.cs
@@ -1,5 +1,6 @@
 using System;
 using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Qualities;
 using NzbDrone.Core.Tv;
 
 namespace NzbDrone.Core.Queue
diff --git a/src/NzbDrone.Core/Queue/QueueScheduler.cs b/src/NzbDrone.Core/Queue/QueueScheduler.cs
index 39be36628..55a8d69e4 100644
--- a/src/NzbDrone.Core/Queue/QueueScheduler.cs
+++ b/src/NzbDrone.Core/Queue/QueueScheduler.cs
@@ -1,5 +1,6 @@
 using System.Threading;
 using System.Threading.Tasks;
+using NLog;
 using NzbDrone.Common.TPL;
 using NzbDrone.Core.Lifecycle;
 using NzbDrone.Core.Messaging.Events;
@@ -11,12 +12,14 @@ namespace NzbDrone.Core.Queue
                                   IHandle<ApplicationShutdownRequested>
     {
         private readonly IEventAggregator _eventAggregator;
+        private readonly Logger _logger;
         private static readonly Timer Timer = new Timer();
         private static CancellationTokenSource _cancellationTokenSource;
 
-        public QueueScheduler(IEventAggregator eventAggregator)
+        public QueueScheduler(IEventAggregator eventAggregator, Logger logger)
         {
             _eventAggregator = eventAggregator;
+            _logger = logger;
         }
 
         private void CheckQueue()
@@ -47,6 +50,7 @@ namespace NzbDrone.Core.Queue
 
         public void Handle(ApplicationShutdownRequested message)
         {
+            _logger.Info("Shutting down queue scheduler");
             _cancellationTokenSource.Cancel(true);
             Timer.Stop();
         }
diff --git a/src/NzbDrone.Core/Tv/Events/SeriesEditedEvent.cs b/src/NzbDrone.Core/Tv/Events/SeriesEditedEvent.cs
new file mode 100644
index 000000000..33371a4e7
--- /dev/null
+++ b/src/NzbDrone.Core/Tv/Events/SeriesEditedEvent.cs
@@ -0,0 +1,14 @@
+using NzbDrone.Common.Messaging;
+
+namespace NzbDrone.Core.Tv.Events
+{
+    public class SeriesEditedEvent : IEvent
+    {
+        public Series Series { get; private set; }
+
+        public SeriesEditedEvent(Series series)
+        {
+            Series = series;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Tv/QualityModel.cs b/src/NzbDrone.Core/Tv/QualityModel.cs
deleted file mode 100644
index eaa7f7884..000000000
--- a/src/NzbDrone.Core/Tv/QualityModel.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using System;
-using NzbDrone.Core.Datastore;
-using NzbDrone.Core.Qualities;
-
-namespace NzbDrone.Core.Tv
-{
-    public class QualityModel : IComparable<QualityModel>, IEmbeddedDocument
-    {
-        public Quality Quality { get; set; }
-
-        public Boolean Proper { get; set; }
-
-        public QualityModel()
-            : this(Quality.Unknown)
-        {
-
-        }
-
-        public QualityModel(Quality quality, Boolean proper = false)
-        {
-            Quality = quality;
-            Proper = proper;
-        }
-
-        public int CompareTo(QualityModel other)
-        {
-            if (other.Quality > Quality)
-                return -1;
-
-            if (other.Quality < Quality)
-                return 1;
-
-            if (other.Quality == Quality && other.Proper == Proper)
-                return 0;
-
-            if (Proper && !other.Proper)
-                return 1;
-
-            if (!Proper && other.Proper)
-                return -1;
-
-            return 0;
-        }
-
-        public static bool operator !=(QualityModel x, QualityModel y)
-        {
-            return !(x == y);
-        }
-
-        public static bool operator ==(QualityModel x, QualityModel y)
-        {
-            var xObj = (Object)x;
-            var yObj = (object)y;
-
-            if (xObj == null || yObj == null)
-            {
-                return xObj == yObj;
-            }
-
-            return x.CompareTo(y) == 0;
-        }
-
-        public static bool operator >(QualityModel x, QualityModel y)
-        {
-            return x.CompareTo(y) > 0;
-        }
-
-        public static bool operator <(QualityModel x, QualityModel y)
-        {
-            return x.CompareTo(y) < 0;
-        }
-
-        public static bool operator <=(QualityModel x, QualityModel y)
-        {
-            return x.CompareTo(y) <= 0;
-        }
-
-        public static bool operator >=(QualityModel x, QualityModel y)
-        {
-            return x.CompareTo(y) >= 0;
-        }
-
-        public override string ToString()
-        {
-            string result = Quality.ToString();
-            if (Proper)
-            {
-                result += " Proper";
-            }
-
-            return result;
-        }
-
-        public override int GetHashCode()
-        {
-            unchecked // Overflow is fine, just wrap
-            {
-                int hash = 17;
-                hash = hash * 23 + Proper.GetHashCode();
-                hash = hash * 23 + Quality.GetHashCode();
-                return hash;
-            }
-        }
-
-        public bool Equals(QualityModel other)
-        {
-            if (ReferenceEquals(null, other)) return false;
-            if (ReferenceEquals(this, other)) return true;
-            return Equals(other.Quality, Quality) && other.Proper.Equals(Proper);
-        }
-
-        public override bool Equals(object obj)
-        {
-            if (ReferenceEquals(null, obj)) return false;
-            if (ReferenceEquals(this, obj)) return true;
-            if (obj.GetType() != typeof(QualityModel)) return false;
-            return Equals((QualityModel)obj);
-        }
-    }
-}
diff --git a/src/NzbDrone.Core/Tv/SeriesService.cs b/src/NzbDrone.Core/Tv/SeriesService.cs
index 5387f3704..ee7af6841 100644
--- a/src/NzbDrone.Core/Tv/SeriesService.cs
+++ b/src/NzbDrone.Core/Tv/SeriesService.cs
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Tv
             newSeries.CleanTitle = Parser.Parser.CleanSeriesTitle(newSeries.Title);
 
             _seriesRepository.Insert(newSeries);
-            _eventAggregator.PublishEvent(new SeriesAddedEvent(newSeries));
+            _eventAggregator.PublishEvent(new SeriesAddedEvent(GetSeries(newSeries.Id)));
 
             return newSeries;
         }
@@ -136,7 +136,10 @@ namespace NzbDrone.Core.Tv
                 }
             }
 
-            return _seriesRepository.Update(series);
+            var updatedSeries = _seriesRepository.Update(series);
+            _eventAggregator.PublishEvent(new SeriesEditedEvent(updatedSeries));
+
+            return updatedSeries;
         }
 
         public List<Series> UpdateSeries(List<Series> series)
@@ -148,6 +151,8 @@ namespace NzbDrone.Core.Tv
                     var folderName = new DirectoryInfo(s.Path).Name;
                     s.Path = Path.Combine(s.RootFolderPath, folderName);
                 }
+
+                _eventAggregator.PublishEvent(new SeriesEditedEvent(s));
             }
 
             _seriesRepository.UpdateMany(series);
diff --git a/src/NzbDrone.Host/ApplicationServer.cs b/src/NzbDrone.Host/ApplicationServer.cs
index 6ab53244f..946334ec6 100644
--- a/src/NzbDrone.Host/ApplicationServer.cs
+++ b/src/NzbDrone.Host/ApplicationServer.cs
@@ -1,19 +1,22 @@
-using System.ServiceProcess;
+using System;
+using System.ServiceProcess;
 using NLog;
 using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Processes;
 using NzbDrone.Core.Configuration;
+using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Host.Owin;
 
 namespace NzbDrone.Host
 {
     public interface INzbDroneServiceFactory
     {
-        bool IsServiceStopped { get; }
         ServiceBase Build();
         void Start();
     }
 
-    public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory
+    public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory, IHandle<ApplicationShutdownRequested>
     {
         private readonly IConfigFileProvider _configFileProvider;
         private readonly IRuntimeInfo _runtimeInfo;
@@ -21,10 +24,17 @@ namespace NzbDrone.Host
         private readonly PriorityMonitor _priorityMonitor;
         private readonly IStartupContext _startupContext;
         private readonly IBrowserService _browserService;
+        private readonly IProcessProvider _processProvider;
         private readonly Logger _logger;
 
-        public NzbDroneServiceFactory(IConfigFileProvider configFileProvider, IHostController hostController,
-            IRuntimeInfo runtimeInfo, PriorityMonitor priorityMonitor, IStartupContext startupContext, IBrowserService browserService, Logger logger)
+        public NzbDroneServiceFactory(IConfigFileProvider configFileProvider, 
+                                      IHostController hostController,
+                                      IRuntimeInfo runtimeInfo, 
+                                      PriorityMonitor priorityMonitor, 
+                                      IStartupContext startupContext, 
+                                      IBrowserService browserService, 
+                                      IProcessProvider processProvider, 
+                                      Logger logger)
         {
             _configFileProvider = configFileProvider;
             _hostController = hostController;
@@ -32,6 +42,7 @@ namespace NzbDrone.Host
             _priorityMonitor = priorityMonitor;
             _startupContext = startupContext;
             _browserService = browserService;
+            _processProvider = processProvider;
             _logger = logger;
         }
 
@@ -42,6 +53,12 @@ namespace NzbDrone.Host
 
         public void Start()
         {
+            if (OsInfo.IsLinux)
+            {
+                Console.CancelKeyPress += (sender, eventArgs) => _processProvider.Kill(_processProvider.GetCurrentProcess().Id);
+            }
+
+            _runtimeInfo.IsRunning = true;
             _hostController.StartServer();
 
             if (!_startupContext.Flags.Contains(StartupContext.NO_BROWSER)
@@ -55,18 +72,33 @@ namespace NzbDrone.Host
 
         protected override void OnStop()
         {
-            _logger.Info("Attempting to stop application.");
-            _hostController.StopServer();
-            _logger.Info("Application has finished stop routine.");
-            IsServiceStopped = true;
+            Shutdown();
         }
 
-        public bool IsServiceStopped { get; private set; }
-
         public ServiceBase Build()
         {
             return this;
         }
-    }
 
+        private void Shutdown()
+        {
+            _logger.Info("Attempting to stop application.");
+            _hostController.StopServer();
+            _logger.Info("Application has finished stop routine.");
+            _runtimeInfo.IsRunning = false;
+        }
+
+        public void Handle(ApplicationShutdownRequested message)
+        {
+            if (OsInfo.IsLinux)
+            {
+                _processProvider.Kill(_processProvider.GetCurrentProcess().Id);
+            }
+
+            if (!_runtimeInfo.IsWindowsService && !message.Restarting)
+            {
+                Shutdown();
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs
index 7909ea526..1b0d9fa70 100644
--- a/src/NzbDrone.Host/Bootstrap.cs
+++ b/src/NzbDrone.Host/Bootstrap.cs
@@ -36,7 +36,7 @@ namespace NzbDrone.Host
 
                 var appMode = GetApplicationMode(startupContext);
 
-                Start(appMode);
+                Start(appMode, startupContext);
 
                 if (startCallback != null)
                 {
@@ -51,14 +51,15 @@ namespace NzbDrone.Host
             catch (TerminateApplicationException e)
             {
                 Logger.Info(e.Message);
+                LogManager.Configuration = null;
             }
         }
 
-        private static void Start(ApplicationModes applicationModes)
+        private static void Start(ApplicationModes applicationModes, StartupContext startupContext)
         {
             if (!IsInUtilityMode(applicationModes))
             {
-                EnsureSingleInstance(applicationModes == ApplicationModes.Service);
+                EnsureSingleInstance(applicationModes == ApplicationModes.Service, startupContext);
             }
 
             DbFactory.RegisterDatabase(_container);
@@ -72,15 +73,15 @@ namespace NzbDrone.Host
                 return;
             }
 
-            var serviceFactory = _container.Resolve<INzbDroneServiceFactory>();
+            var runTimeInfo = _container.Resolve<IRuntimeInfo>();
 
-            while (!serviceFactory.IsServiceStopped)
+            while (runTimeInfo.IsRunning)
             {
                 Thread.Sleep(1000);
             }
         }
 
-        private static void EnsureSingleInstance(bool isService)
+        private static void EnsureSingleInstance(bool isService, StartupContext startupContext)
         {
             var instancePolicy = _container.Resolve<ISingleInstancePolicy>();
 
@@ -88,6 +89,10 @@ namespace NzbDrone.Host
             {
                 instancePolicy.KillAllOtherInstance();
             }
+            else if (startupContext.Flags.Contains(StartupContext.TERMINATE))
+            {
+                instancePolicy.KillAllOtherInstance();
+            }
             else
             {
                 instancePolicy.PreventStartIfAlreadyRunning();
diff --git a/src/NzbDrone.Host/MainAppContainerBuilder.cs b/src/NzbDrone.Host/MainAppContainerBuilder.cs
index ce7d53216..ea4ace057 100644
--- a/src/NzbDrone.Host/MainAppContainerBuilder.cs
+++ b/src/NzbDrone.Host/MainAppContainerBuilder.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using Nancy.Bootstrapper;
 using NzbDrone.Api;
 using NzbDrone.Common.Composition;
diff --git a/src/NzbDrone.Host/NzbDrone.Host.csproj b/src/NzbDrone.Host/NzbDrone.Host.csproj
index 7a0f05c83..8ad545c0d 100644
--- a/src/NzbDrone.Host/NzbDrone.Host.csproj
+++ b/src/NzbDrone.Host/NzbDrone.Host.csproj
@@ -117,7 +117,7 @@
     <Compile Include="AccessControl\FirewallAdapter.cs" />
     <Compile Include="AccessControl\UrlAclAdapter.cs" />
     <Compile Include="BrowserService.cs" />
-    <Compile Include="NzbDroneProcessService.cs" />
+    <Compile Include="SingleInstancePolicy.cs" />
     <Compile Include="IUserAlert.cs" />
     <Compile Include="Owin\NlogTextWriter.cs" />
     <Compile Include="Owin\OwinServiceProvider.cs" />
@@ -195,9 +195,7 @@
       <Name>NzbDrone.SignalR</Name>
     </ProjectReference>
   </ItemGroup>
-  <ItemGroup>
-    <Folder Include="SignalR\" />
-  </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PreBuildEvent>
diff --git a/src/NzbDrone.Host/NzbDroneProcessService.cs b/src/NzbDrone.Host/SingleInstancePolicy.cs
similarity index 72%
rename from src/NzbDrone.Host/NzbDroneProcessService.cs
rename to src/NzbDrone.Host/SingleInstancePolicy.cs
index c13a46f5b..2952997cb 100644
--- a/src/NzbDrone.Host/NzbDroneProcessService.cs
+++ b/src/NzbDrone.Host/SingleInstancePolicy.cs
@@ -16,12 +16,17 @@ namespace NzbDrone.Host
     {
         private readonly IProcessProvider _processProvider;
         private readonly IBrowserService _browserService;
+        private readonly INzbDroneProcessProvider _nzbDroneProcessProvider;
         private readonly Logger _logger;
 
-        public SingleInstancePolicy(IProcessProvider processProvider, IBrowserService browserService, Logger logger)
+        public SingleInstancePolicy(IProcessProvider processProvider,
+                                    IBrowserService browserService,
+                                    INzbDroneProcessProvider nzbDroneProcessProvider,
+                                    Logger logger)
         {
             _processProvider = processProvider;
             _browserService = browserService;
+            _nzbDroneProcessProvider = nzbDroneProcessProvider;
             _logger = logger;
         }
 
@@ -51,11 +56,10 @@ namespace NzbDrone.Host
         private List<int> GetOtherNzbDroneProcessIds()
         {
             var currentId = _processProvider.GetCurrentProcess().Id;
-            var consoleIds = _processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME)
-                .Select(c => c.Id);
-            var winformIds = _processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME).Select(c => c.Id);
-
-            var otherProcesses = consoleIds.Union(winformIds).Except(new[] { currentId }).ToList();
+            var otherProcesses = _nzbDroneProcessProvider.FindNzbDroneProcesses()
+                                                         .Select(c => c.Id)
+                                                         .Except(new[] {currentId})
+                                                         .ToList();
 
             if (otherProcesses.Any())
             {
diff --git a/src/NzbDrone.Integration.Test/Client/EpisodeClient.cs b/src/NzbDrone.Integration.Test/Client/EpisodeClient.cs
index 437226c2f..46d0b8e03 100644
--- a/src/NzbDrone.Integration.Test/Client/EpisodeClient.cs
+++ b/src/NzbDrone.Integration.Test/Client/EpisodeClient.cs
@@ -7,7 +7,7 @@ namespace NzbDrone.Integration.Test.Client
     public class EpisodeClient : ClientBase<EpisodeResource>
     {
         public EpisodeClient(IRestClient restClient, string apiKey)
-            : base(restClient, apiKey, "episodes")
+            : base(restClient, apiKey, "episode")
         {
         }
 
diff --git a/src/NzbDrone.Mono.Test/NzbDrone.Mono.Test.csproj b/src/NzbDrone.Mono.Test/NzbDrone.Mono.Test.csproj
index 4d31659ea..6b5b32c5b 100644
--- a/src/NzbDrone.Mono.Test/NzbDrone.Mono.Test.csproj
+++ b/src/NzbDrone.Mono.Test/NzbDrone.Mono.Test.csproj
@@ -50,6 +50,10 @@
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="FluentAssertions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll</HintPath>
+    </Reference>
     <Reference Include="nunit.framework">
       <HintPath>..\packages\NUnit.2.6.2\lib\nunit.framework.dll</HintPath>
     </Reference>
@@ -66,6 +70,7 @@
     <Compile Include="DiskProviderTests\FreeSpaceFixture.cs" />
     <Compile Include="DiskProviderTests\IsParentFixture.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ServiceFactoryFixture.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="packages.config" />
@@ -79,6 +84,14 @@
       <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>
+    <ProjectReference Include="..\NzbDrone.Host\NzbDrone.Host.csproj">
+      <Project>{95c11a9e-56ed-456a-8447-2c89c1139266}</Project>
+      <Name>NzbDrone.Host</Name>
+    </ProjectReference>
     <ProjectReference Include="..\NzbDrone.Mono\NzbDrone.Mono.csproj">
       <Project>{15ad7579-a314-4626-b556-663f51d97cd1}</Project>
       <Name>NzbDrone.Mono</Name>
diff --git a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs b/src/NzbDrone.Mono.Test/ServiceFactoryFixture.cs
similarity index 93%
rename from src/NzbDrone.Common.Test/ServiceFactoryFixture.cs
rename to src/NzbDrone.Mono.Test/ServiceFactoryFixture.cs
index 157d0893d..1d5e77f87 100644
--- a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs
+++ b/src/NzbDrone.Mono.Test/ServiceFactoryFixture.cs
@@ -1,13 +1,14 @@
 using System.Linq;
 using FluentAssertions;
 using NUnit.Framework;
+using NzbDrone.Common;
 using NzbDrone.Common.EnvironmentInfo;
 using NzbDrone.Core.Lifecycle;
 using NzbDrone.Core.Messaging.Events;
 using NzbDrone.Host;
 using NzbDrone.Test.Common;
 
-namespace NzbDrone.Common.Test
+namespace NzbDrone.Mono.Test
 {
     [TestFixture]
     public class ServiceFactoryFixture : TestBase<ServiceFactory>
diff --git a/src/NzbDrone.Mono.Test/packages.config b/src/NzbDrone.Mono.Test/packages.config
index 5c3ca54dd..e6606e669 100644
--- a/src/NzbDrone.Mono.Test/packages.config
+++ b/src/NzbDrone.Mono.Test/packages.config
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="FluentAssertions" version="2.1.0.0" targetFramework="net40" />
   <package id="NUnit" version="2.6.2" targetFramework="net40" />
 </packages>
\ No newline at end of file
diff --git a/src/NzbDrone.Mono/NzbDrone.Mono.csproj b/src/NzbDrone.Mono/NzbDrone.Mono.csproj
index 91d0efeb3..e342f9a16 100644
--- a/src/NzbDrone.Mono/NzbDrone.Mono.csproj
+++ b/src/NzbDrone.Mono/NzbDrone.Mono.csproj
@@ -70,6 +70,7 @@
   <ItemGroup>
     <Compile Include="DiskProvider.cs" />
     <Compile Include="LinuxPermissionsException.cs" />
+    <Compile Include="NzbDroneProcessProvider.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>
diff --git a/src/NzbDrone.Mono/NzbDroneProcessProvider.cs b/src/NzbDrone.Mono/NzbDroneProcessProvider.cs
new file mode 100644
index 000000000..630b6e665
--- /dev/null
+++ b/src/NzbDrone.Mono/NzbDroneProcessProvider.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NzbDrone.Common.Model;
+using NzbDrone.Common.Processes;
+
+namespace NzbDrone.Mono
+{
+    public class NzbDroneProcessProvider : INzbDroneProcessProvider
+    {
+        private readonly IProcessProvider _processProvider;
+
+        public NzbDroneProcessProvider(IProcessProvider processProvider)
+        {
+            _processProvider = processProvider;
+        }
+
+        public List<ProcessInfo> FindNzbDroneProcesses()
+        {
+            var monoProcesses = _processProvider.FindProcessByName("mono");
+
+            return monoProcesses.Where(c =>
+            {
+                var processArgs = _processProvider.StartAndCapture("ps", String.Format("--pid {0} -o args=", c.Id));
+
+                return processArgs.Standard.Any(p => p.Contains(ProcessProvider.NZB_DRONE_PROCESS_NAME + ".exe") ||
+                                                     p.Contains(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME + ".exe"));
+            }).ToList();
+        }
+    }
+}
diff --git a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs b/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs
index c30ee555f..815238022 100644
--- a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs
+++ b/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs
@@ -14,7 +14,6 @@ namespace NzbDrone.SignalR
             }
         }
 
-
         public void Execute(BroadcastSignalRMessage message)
         {
             Context.Connection.Broadcast(message.Body);
diff --git a/src/NzbDrone.Update/NzbDrone.Update.csproj b/src/NzbDrone.Update/NzbDrone.Update.csproj
index d09ab8021..908102ff9 100644
--- a/src/NzbDrone.Update/NzbDrone.Update.csproj
+++ b/src/NzbDrone.Update/NzbDrone.Update.csproj
@@ -60,6 +60,7 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="UpdateContainerBuilder.cs" />
     <Compile Include="UpdateEngine\BackupAndRestore.cs" />
+    <Compile Include="UpdateEngine\BackupAppData.cs" />
     <Compile Include="UpdateEngine\DetectApplicationType.cs" />
     <Compile Include="UpdateEngine\InstallUpdateService.cs" />
     <Compile Include="UpdateEngine\StartNzbDrone.cs" />
diff --git a/src/NzbDrone.Update/UpdateApp.cs b/src/NzbDrone.Update/UpdateApp.cs
index 83531b153..35d7febca 100644
--- a/src/NzbDrone.Update/UpdateApp.cs
+++ b/src/NzbDrone.Update/UpdateApp.cs
@@ -50,10 +50,10 @@ namespace NzbDrone.Update
 
         public void Start(string[] args)
         {
-            int processId = ParseProcessId(args);
+            var processId = ParseProcessId(args);
 
             var exeFileInfo = new FileInfo(_processProvider.GetProcessById(processId).StartPath);
-            string targetFolder = exeFileInfo.Directory.FullName;
+            var targetFolder = exeFileInfo.Directory.FullName;
 
             logger.Info("Starting update process. Target Path:{0}", targetFolder);
             _installUpdateService.Start(targetFolder);
diff --git a/src/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs b/src/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs
index 3787b25d3..a934e6fc6 100644
--- a/src/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs
+++ b/src/NzbDrone.Update/UpdateEngine/BackupAndRestore.cs
@@ -7,7 +7,7 @@ namespace NzbDrone.Update.UpdateEngine
 {
     public interface IBackupAndRestore
     {
-        void BackUp(string source);
+        void Backup(string source);
         void Restore(string target);
     }
 
@@ -24,7 +24,7 @@ namespace NzbDrone.Update.UpdateEngine
             _logger = logger;
         }
 
-        public void BackUp(string source)
+        public void Backup(string source)
         {
             _logger.Info("Creating backup of existing installation");
             _diskProvider.CopyFolder(source, _appFolderInfo.GetUpdateBackUpFolder());
diff --git a/src/NzbDrone.Update/UpdateEngine/BackupAppData.cs b/src/NzbDrone.Update/UpdateEngine/BackupAppData.cs
new file mode 100644
index 000000000..038732367
--- /dev/null
+++ b/src/NzbDrone.Update/UpdateEngine/BackupAppData.cs
@@ -0,0 +1,37 @@
+using System.IO;
+using NLog;
+using NzbDrone.Common;
+using NzbDrone.Common.Disk;
+using NzbDrone.Common.EnvironmentInfo;
+
+namespace NzbDrone.Update.UpdateEngine
+{
+    public interface IBackupAppData
+    {
+        void Backup();
+    }
+
+    public class BackupAppData : IBackupAppData
+    {
+        private readonly IAppFolderInfo _appFolderInfo;
+        private readonly IDiskProvider _diskProvider;
+        private readonly Logger _logger;
+
+        public BackupAppData(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
+        {
+            _appFolderInfo = appFolderInfo;
+            _diskProvider = diskProvider;
+            _logger = logger;
+        }
+
+        public void Backup()
+        {
+            _logger.Info("Backing up appdata (database/config)");
+            var backupFolderAppData = _appFolderInfo.GetUpdateBackUpAppDataFolder();
+
+            _diskProvider.CreateFolder(backupFolderAppData);
+            _diskProvider.CopyFile(_appFolderInfo.GetConfigPath(), _appFolderInfo.GetUpdateBackupConfigFile(), true);
+            _diskProvider.CopyFile(_appFolderInfo.GetNzbDroneDatabase(), _appFolderInfo.GetUpdateBackupDatabase(), true);
+        }
+    }
+}
diff --git a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
index 0e6001677..f1b731e34 100644
--- a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
+++ b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
@@ -19,17 +19,25 @@ namespace NzbDrone.Update.UpdateEngine
         private readonly ITerminateNzbDrone _terminateNzbDrone;
         private readonly IAppFolderInfo _appFolderInfo;
         private readonly IBackupAndRestore _backupAndRestore;
+        private readonly IBackupAppData _backupAppData;
         private readonly IStartNzbDrone _startNzbDrone;
         private readonly Logger _logger;
 
-        public InstallUpdateService(IDiskProvider diskProvider, IDetectApplicationType detectApplicationType, ITerminateNzbDrone terminateNzbDrone,
-            IAppFolderInfo appFolderInfo, IBackupAndRestore backupAndRestore, IStartNzbDrone startNzbDrone, Logger logger)
+        public InstallUpdateService(IDiskProvider diskProvider,
+                                    IDetectApplicationType detectApplicationType,
+                                    ITerminateNzbDrone terminateNzbDrone,
+                                    IAppFolderInfo appFolderInfo,
+                                    IBackupAndRestore backupAndRestore,
+                                    IBackupAppData backupAppData,
+                                    IStartNzbDrone startNzbDrone,
+                                    Logger logger)
         {
             _diskProvider = diskProvider;
             _detectApplicationType = detectApplicationType;
             _terminateNzbDrone = terminateNzbDrone;
             _appFolderInfo = appFolderInfo;
             _backupAndRestore = backupAndRestore;
+            _backupAppData = backupAppData;
             _startNzbDrone = startNzbDrone;
             _logger = logger;
         }
@@ -59,7 +67,8 @@ namespace NzbDrone.Update.UpdateEngine
             {
                 _terminateNzbDrone.Terminate();
 
-                _backupAndRestore.BackUp(installationFolder);
+                _backupAndRestore.Backup(installationFolder);
+                _backupAppData.Backup();
 
                 _logger.Info("Moving update package to target");
 
diff --git a/src/NzbDrone.Windows.Test/NzbDrone.Windows.Test.csproj b/src/NzbDrone.Windows.Test/NzbDrone.Windows.Test.csproj
index 098c95b80..309005f99 100644
--- a/src/NzbDrone.Windows.Test/NzbDrone.Windows.Test.csproj
+++ b/src/NzbDrone.Windows.Test/NzbDrone.Windows.Test.csproj
@@ -50,6 +50,10 @@
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="FluentAssertions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\FluentAssertions.2.1.0.0\lib\net40\FluentAssertions.dll</HintPath>
+    </Reference>
     <Reference Include="nunit.framework, Version=2.6.2.12296, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\NUnit.2.6.2\lib\nunit.framework.dll</HintPath>
@@ -67,6 +71,7 @@
     <Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
     <Compile Include="DiskProviderTests\FreeSpaceFixture.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ServiceFactoryFixture.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\NzbDrone.Common.Test\NzbDrone.Common.Test.csproj">
@@ -77,6 +82,14 @@
       <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>
+    <ProjectReference Include="..\NzbDrone.Host\NzbDrone.Host.csproj">
+      <Project>{95c11a9e-56ed-456a-8447-2c89c1139266}</Project>
+      <Name>NzbDrone.Host</Name>
+    </ProjectReference>
     <ProjectReference Include="..\NzbDrone.Test.Common\NzbDrone.Test.Common.csproj">
       <Project>{caddfce0-7509-4430-8364-2074e1eefca2}</Project>
       <Name>NzbDrone.Test.Common</Name>
diff --git a/src/NzbDrone.Windows.Test/ServiceFactoryFixture.cs b/src/NzbDrone.Windows.Test/ServiceFactoryFixture.cs
new file mode 100644
index 000000000..bde389da2
--- /dev/null
+++ b/src/NzbDrone.Windows.Test/ServiceFactoryFixture.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Common;
+using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Host;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Windows.Test
+{
+    [TestFixture]
+    public class ServiceFactoryFixture : TestBase<ServiceFactory>
+    {
+        [SetUp]
+        public void setup()
+        {
+            Mocker.SetConstant(MainAppContainerBuilder.BuildContainer(new StartupContext()));
+        }
+
+        [Test]
+        public void event_handlers_should_be_unique()
+        {
+            var handlers = Subject.BuildAll<IHandle<ApplicationShutdownRequested>>()
+                                  .Select(c => c.GetType().FullName);
+
+            handlers.Should().OnlyHaveUniqueItems();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Windows.Test/packages.config b/src/NzbDrone.Windows.Test/packages.config
index 5c3ca54dd..e6606e669 100644
--- a/src/NzbDrone.Windows.Test/packages.config
+++ b/src/NzbDrone.Windows.Test/packages.config
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="FluentAssertions" version="2.1.0.0" targetFramework="net40" />
   <package id="NUnit" version="2.6.2" targetFramework="net40" />
 </packages>
\ No newline at end of file
diff --git a/src/NzbDrone.Windows/NzbDrone.Windows.csproj b/src/NzbDrone.Windows/NzbDrone.Windows.csproj
index 607f09a4e..77e47f03c 100644
--- a/src/NzbDrone.Windows/NzbDrone.Windows.csproj
+++ b/src/NzbDrone.Windows/NzbDrone.Windows.csproj
@@ -63,6 +63,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="DiskProvider.cs" />
+    <Compile Include="NzbDroneProcessProvider.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>
diff --git a/src/NzbDrone.Windows/NzbDroneProcessProvider.cs b/src/NzbDrone.Windows/NzbDroneProcessProvider.cs
new file mode 100644
index 000000000..1e18b6c50
--- /dev/null
+++ b/src/NzbDrone.Windows/NzbDroneProcessProvider.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Linq;
+using NzbDrone.Common.Model;
+using NzbDrone.Common.Processes;
+
+namespace NzbDrone.Windows
+{
+    public class NzbDroneProcessProvider : INzbDroneProcessProvider
+    {
+        private readonly IProcessProvider _processProvider;
+
+        public NzbDroneProcessProvider(IProcessProvider processProvider)
+        {
+            _processProvider = processProvider;
+        }
+
+        public List<ProcessInfo> FindNzbDroneProcesses()
+        {
+            var consoleProcesses = _processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME);
+            var winformProcesses = _processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME);
+
+            return consoleProcesses.Concat(winformProcesses).ToList();
+        }
+    }
+}
diff --git a/src/NzbDrone.sln b/src/NzbDrone.sln
index 6607c1c36..fa28f72be 100644
--- a/src/NzbDrone.sln
+++ b/src/NzbDrone.sln
@@ -73,6 +73,9 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{0F0D4998-8F5D-4467-A909-BB192C4B3B4B}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{4EACDBBC-BCD7-4765-A57B-3E08331E4749}"
+	ProjectSection(SolutionItems) = preProject
+		NzbDrone.Common.Test\ServiceFactoryFixture.cs = NzbDrone.Common.Test\ServiceFactoryFixture.cs
+	EndProjectSection
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Windows.Test", "NzbDrone.Windows.Test\NzbDrone.Windows.Test.csproj", "{80B51429-7A0E-46D6-BEE3-C80DCB1C4EAA}"
 EndProject
diff --git a/src/NzbDrone/SysTray/SysTrayApp.cs b/src/NzbDrone/SysTray/SysTrayApp.cs
index bfbc96cb2..730ea346f 100644
--- a/src/NzbDrone/SysTray/SysTrayApp.cs
+++ b/src/NzbDrone/SysTray/SysTrayApp.cs
@@ -68,7 +68,15 @@ namespace NzbDrone.SysTray
                 _trayIcon.Dispose();
             }
 
-            base.Dispose(isDisposing);
+            if (InvokeRequired)
+            {
+                base.Invoke(new MethodInvoker(() => Dispose(isDisposing)));
+            }
+
+            else
+            {
+                base.Dispose(isDisposing);
+            }
         }
 
         private void OnExit(object sender, EventArgs e)
diff --git a/src/UI/.idea/jsLinters/jshint.xml b/src/UI/.idea/jsLinters/jshint.xml
index 96fa6c67a..e85398a55 100644
--- a/src/UI/.idea/jsLinters/jshint.xml
+++ b/src/UI/.idea/jsLinters/jshint.xml
@@ -8,24 +8,22 @@
     <option es3="false" />
     <option forin="true" />
     <option immed="true" />
-    <option latedef="true" />
     <option newcap="true" />
     <option noarg="true" />
     <option noempty="false" />
     <option nonew="true" />
     <option plusplus="false" />
-    <option regexp="false" />
     <option undef="true" />
-    <option unused="true" />
     <option strict="true" />
     <option trailing="false" />
+    <option latedef="true" />
+    <option unused="true" />
     <option quotmark="single" />
     <option maxdepth="3" />
     <option asi="false" />
     <option boss="false" />
     <option debug="false" />
     <option eqnull="false" />
-    <option es5="false" />
     <option esnext="false" />
     <option evil="false" />
     <option expr="false" />
@@ -38,8 +36,6 @@
     <option loopfunc="false" />
     <option multistr="false" />
     <option proto="false" />
-    <option onecase="false" />
-    <option regexdash="false" />
     <option scripturl="false" />
     <option smarttabs="false" />
     <option shadow="false" />
diff --git a/src/UI/AddSeries/AddSeriesLayout.js b/src/UI/AddSeries/AddSeriesLayout.js
index 1b778546d..e6e265abe 100644
--- a/src/UI/AddSeries/AddSeriesLayout.js
+++ b/src/UI/AddSeries/AddSeriesLayout.js
@@ -4,11 +4,11 @@ define(
         'vent',
         'AppLayout',
         'marionette',
-        'AddSeries/RootFolders/Layout',
+        'AddSeries/RootFolders/RootFolderLayout',
         'AddSeries/Existing/AddExistingSeriesCollectionView',
         'AddSeries/AddSeriesView',
         'Quality/QualityProfileCollection',
-        'AddSeries/RootFolders/Collection',
+        'AddSeries/RootFolders/RootFolderCollection',
         'Series/SeriesCollection'
     ], function (vent,
                  AppLayout,
diff --git a/src/UI/AddSeries/NotFoundTemplate.html b/src/UI/AddSeries/NotFoundTemplate.html
index cfe1e34e6..2b0d75a9b 100644
--- a/src/UI/AddSeries/NotFoundTemplate.html
+++ b/src/UI/AddSeries/NotFoundTemplate.html
@@ -2,6 +2,6 @@
     <h3>
         Sorry. We couldn't find any series matching '{{term}}'
     </h3>
-    <a href="https://github.com/NzbDrone/NzbDrone/wiki/FAQ#why-cant-i-add-a-new-show-to-nzbdrone-its-on-thetvdb">Why can't I find my show?</a>
+    <a href="https://github.com/NzbDrone/NzbDrone/wiki/FAQ#wiki-why-cant-i-add-a-new-show-to-nzbdrone-its-on-thetvdb">Why can't I find my show?</a>
 
 </div>
diff --git a/src/UI/AddSeries/RootFolders/Collection.js b/src/UI/AddSeries/RootFolders/RootFolderCollection.js
similarity index 89%
rename from src/UI/AddSeries/RootFolders/Collection.js
rename to src/UI/AddSeries/RootFolders/RootFolderCollection.js
index 157bf19ce..364cecea9 100644
--- a/src/UI/AddSeries/RootFolders/Collection.js
+++ b/src/UI/AddSeries/RootFolders/RootFolderCollection.js
@@ -2,7 +2,7 @@
 define(
     [
         'backbone',
-        'AddSeries/RootFolders/Model',
+        'AddSeries/RootFolders/RootFolderModel',
         'Mixins/backbone.signalr.mixin'
     ], function (Backbone, RootFolderModel) {
 
diff --git a/src/UI/AddSeries/RootFolders/CollectionView.js b/src/UI/AddSeries/RootFolders/RootFolderCollectionView.js
similarity index 85%
rename from src/UI/AddSeries/RootFolders/CollectionView.js
rename to src/UI/AddSeries/RootFolders/RootFolderCollectionView.js
index 689b16132..35ab928d6 100644
--- a/src/UI/AddSeries/RootFolders/CollectionView.js
+++ b/src/UI/AddSeries/RootFolders/RootFolderCollectionView.js
@@ -3,7 +3,7 @@
 define(
     [
         'marionette',
-        'AddSeries/RootFolders/ItemView'
+        'AddSeries/RootFolders/RootFolderItemView'
     ], function (Marionette, RootFolderItemView) {
 
 
diff --git a/src/UI/AddSeries/RootFolders/ItemView.js b/src/UI/AddSeries/RootFolders/RootFolderItemView.js
similarity index 91%
rename from src/UI/AddSeries/RootFolders/ItemView.js
rename to src/UI/AddSeries/RootFolders/RootFolderItemView.js
index 8135d69ab..1fe1e72e7 100644
--- a/src/UI/AddSeries/RootFolders/ItemView.js
+++ b/src/UI/AddSeries/RootFolders/RootFolderItemView.js
@@ -7,7 +7,7 @@ define(
 
         return Marionette.ItemView.extend({
 
-            template: 'AddSeries/RootFolders/ItemViewTemplate',
+            template: 'AddSeries/RootFolders/RootFolderItemViewTemplate',
             tagName : 'tr',
 
             initialize: function () {
diff --git a/src/UI/AddSeries/RootFolders/ItemViewTemplate.html b/src/UI/AddSeries/RootFolders/RootFolderItemViewTemplate.html
similarity index 100%
rename from src/UI/AddSeries/RootFolders/ItemViewTemplate.html
rename to src/UI/AddSeries/RootFolders/RootFolderItemViewTemplate.html
diff --git a/src/UI/AddSeries/RootFolders/Layout.js b/src/UI/AddSeries/RootFolders/RootFolderLayout.js
similarity index 79%
rename from src/UI/AddSeries/RootFolders/Layout.js
rename to src/UI/AddSeries/RootFolders/RootFolderLayout.js
index 3b54c90a1..7a0886c6f 100644
--- a/src/UI/AddSeries/RootFolders/Layout.js
+++ b/src/UI/AddSeries/RootFolders/RootFolderLayout.js
@@ -3,16 +3,16 @@
 define(
     [
         'marionette',
-        'AddSeries/RootFolders/CollectionView',
-        'AddSeries/RootFolders/Collection',
-        'AddSeries/RootFolders/Model',
+        'AddSeries/RootFolders/RootFolderCollectionView',
+        'AddSeries/RootFolders/RootFolderCollection',
+        'AddSeries/RootFolders/RootFolderModel',
         'Shared/LoadingView',
         'Mixins/AsValidatedView',
         'Mixins/AutoComplete'
     ], function (Marionette, RootFolderCollectionView, RootFolderCollection, RootFolderModel, LoadingView, AsValidatedView) {
 
         var layout = Marionette.Layout.extend({
-            template: 'AddSeries/RootFolders/LayoutTemplate',
+            template: 'AddSeries/RootFolders/RootFolderLayoutTemplate',
 
             ui: {
                 pathInput: '.x-path input'
@@ -23,7 +23,8 @@ define(
             },
 
             events: {
-                'click .x-add': '_addFolder'
+                'click .x-add': '_addFolder',
+                'keydown .x-path input': '_keydown'
             },
 
             initialize: function () {
@@ -65,6 +66,14 @@ define(
 
             _showCurrentDirs: function () {
                 this.currentDirs.show(this.rootfolderListView);
+            },
+
+            _keydown: function (e) {
+                if (e.keyCode !== 13) {
+                    return;
+                }
+
+                this._addFolder();
             }
         });
 
diff --git a/src/UI/AddSeries/RootFolders/LayoutTemplate.html b/src/UI/AddSeries/RootFolders/RootFolderLayoutTemplate.html
similarity index 100%
rename from src/UI/AddSeries/RootFolders/LayoutTemplate.html
rename to src/UI/AddSeries/RootFolders/RootFolderLayoutTemplate.html
diff --git a/src/UI/AddSeries/RootFolders/Model.js b/src/UI/AddSeries/RootFolders/RootFolderModel.js
similarity index 100%
rename from src/UI/AddSeries/RootFolders/Model.js
rename to src/UI/AddSeries/RootFolders/RootFolderModel.js
diff --git a/src/UI/AddSeries/SearchResultView.js b/src/UI/AddSeries/SearchResultView.js
index eced403ec..c176115ed 100644
--- a/src/UI/AddSeries/SearchResultView.js
+++ b/src/UI/AddSeries/SearchResultView.js
@@ -6,8 +6,8 @@ define(
         'underscore',
         'marionette',
         'Quality/QualityProfileCollection',
-        'AddSeries/RootFolders/Collection',
-        'AddSeries/RootFolders/Layout',
+        'AddSeries/RootFolders/RootFolderCollection',
+        'AddSeries/RootFolders/RootFolderLayout',
         'Series/SeriesCollection',
         'Config',
         'Shared/Messenger',
diff --git a/src/UI/Cells/SeriesStatusCell.js b/src/UI/Cells/SeriesStatusCell.js
index b180a172c..027e5aea3 100644
--- a/src/UI/Cells/SeriesStatusCell.js
+++ b/src/UI/Cells/SeriesStatusCell.js
@@ -1,9 +1,9 @@
 'use strict';
 define(
     [
-        'backgrid'
-    ], function (Backgrid) {
-        return Backgrid.Cell.extend({
+        'Cells/NzbDroneCell'
+    ], function (NzbDroneCell) {
+        return NzbDroneCell.extend({
             className: 'series-status-cell',
 
             render: function () {
@@ -13,20 +13,24 @@ define(
 
                 if (status === 'ended') {
                     this.$el.html('<i class="icon-stop grid-icon" title="Ended"></i>');
-                    this.model.set('statusWeight', 3);
+                    this._setStatusWeight(3);
                 }
 
                 else if (!monitored) {
                     this.$el.html('<i class="icon-pause grid-icon" title="Not Monitored"></i>');
-                    this.model.set('statusWeight', 2);
+                    this._setStatusWeight(2);
                 }
 
                 else {
                     this.$el.html('<i class="icon-play grid-icon" title="Continuing"></i>');
-                    this.model.set('statusWeight', 1);
+                    this._setStatusWeight(1);
                 }
 
                 return this;
+            },
+
+            _setStatusWeight: function (weight) {
+                this.model.set('statusWeight', weight, {silent: true});
             }
         });
     });
diff --git a/src/UI/Config.js b/src/UI/Config.js
index 797cdadb3..bbc6b54ed 100644
--- a/src/UI/Config.js
+++ b/src/UI/Config.js
@@ -9,8 +9,9 @@ define(
             },
             Keys  : {
                 DefaultQualityProfileId: 'DefaultQualityProfileId',
-                DefaultRootFolderId: 'DefaultRootFolderId',
-                UseSeasonFolder: 'UseSeasonFolder'
+                DefaultRootFolderId    : 'DefaultRootFolderId',
+                UseSeasonFolder        : 'UseSeasonFolder',
+                AdvancedSettings       : 'advancedSettings'
             },
 
             getValueBoolean: function (key, defaultValue) {
diff --git a/src/UI/Content/form.less b/src/UI/Content/form.less
index 3296620df..a4c5aaf9e 100644
--- a/src/UI/Content/form.less
+++ b/src/UI/Content/form.less
@@ -47,3 +47,12 @@ textarea.release-restrictions {
     .clickable;
   }
 }
+
+h3 {
+  .help-inline {
+    font-size: 16px;
+    padding-left: 0px;
+    margin-top: -5px;
+    text-transform: none;
+  }
+}
diff --git a/src/UI/Content/icons.less b/src/UI/Content/icons.less
index 3a648cf33..6b1d51aca 100644
--- a/src/UI/Content/icons.less
+++ b/src/UI/Content/icons.less
@@ -167,3 +167,12 @@
   .icon(@cloud-download);
   color: @errorText;
 }
+
+.icon-nd-shutdown:before {
+  .icon(@off);
+  color: @errorText;
+}
+
+.icon-nd-restart:before {
+  .icon(@repeat);
+}
\ No newline at end of file
diff --git a/src/UI/Content/theme.less b/src/UI/Content/theme.less
index e5916eb0c..2ce038698 100644
--- a/src/UI/Content/theme.less
+++ b/src/UI/Content/theme.less
@@ -218,4 +218,4 @@ body {
   width: 100%;
   height: 55px;
   opacity: 0;
-}
\ No newline at end of file
+}
diff --git a/src/UI/Handlebars/Helpers/EachReverse.js b/src/UI/Handlebars/Helpers/EachReverse.js
new file mode 100644
index 000000000..e2e628117
--- /dev/null
+++ b/src/UI/Handlebars/Helpers/EachReverse.js
@@ -0,0 +1,20 @@
+'use strict';
+define(
+    [
+        'handlebars'
+    ], function (Handlebars) {
+        Handlebars.registerHelper('eachReverse', function (context) {
+            var options = arguments[arguments.length - 1];
+            var ret = '';
+
+            if (context && context.length > 0) {
+                for (var i = context.length - 1; i >= 0; i--) {
+                    ret += options.fn(context[i]);
+                }
+            } else {
+                ret = options.inverse(this);
+            }
+
+            return ret;
+        });
+    });
diff --git a/src/UI/Handlebars/Helpers/Html.js b/src/UI/Handlebars/Helpers/Html.js
index 8f4b2b19a..78f81a4bd 100644
--- a/src/UI/Handlebars/Helpers/Html.js
+++ b/src/UI/Handlebars/Helpers/Html.js
@@ -2,15 +2,17 @@
 
 define(
     [
+        'jquery',
         'handlebars',
         'System/StatusModel'
-    ], function (Handlebars, StatusModel) {
+    ], function ($, Handlebars, StatusModel) {
 
-        var placeHolder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.jpg';
+        var placeholder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.jpg';
 
         window.NzbDrone.imageError = function (img) {
-            if (!img.src.contains(placeHolder)) {
-                img.src = placeHolder;
+            if (!img.src.contains(placeholder)) {
+                img.src = placeholder;
+                $(img).addClass('placeholder-image');
             }
             img.onerror = null;
         };
diff --git a/src/UI/Handlebars/backbone.marionette.templates.js b/src/UI/Handlebars/backbone.marionette.templates.js
index 2c589a5cb..6b0ed8ac5 100644
--- a/src/UI/Handlebars/backbone.marionette.templates.js
+++ b/src/UI/Handlebars/backbone.marionette.templates.js
@@ -10,6 +10,7 @@ define(
         'Handlebars/Helpers/Series',
         'Handlebars/Helpers/Quality',
         'Handlebars/Helpers/System',
+        'Handlebars/Helpers/EachReverse',
         'Handlebars/Handlebars.Debug'
     ], function (Templates) {
         return function () {
diff --git a/src/UI/JsLibraries/backbone.collectionview.js b/src/UI/JsLibraries/backbone.collectionview.js
new file mode 100644
index 000000000..29ec982ff
--- /dev/null
+++ b/src/UI/JsLibraries/backbone.collectionview.js
@@ -0,0 +1,1072 @@
+/*!
+* Backbone.CollectionView, v0.8.1
+* Copyright (c)2013 Rotunda Software, LLC.
+* Distributed under MIT license
+* http://github.com/rotundasoftware/backbone-collection-view
+*/
+
+
+(function() {
+        var mDefaultModelViewConstructor = Backbone.View;
+
+        var kDefaultReferenceBy = "model";
+
+        var kAllowedOptions = [
+                "collection", "modelView", "modelViewOptions", "itemTemplate", "emptyListCaption",
+                "selectable", "clickToSelect", "selectableModelsFilter", "visibleModelsFilter",
+                "selectMultiple", "clickToToggle", "processKeyEvents", "sortable", "sortableOptions", "sortableModelsFilter", "itemTemplateFunction", "detachedRendering"
+        ];
+
+        var kOptionsRequiringRerendering = [ "collection", "modelView", "modelViewOptions", "itemTemplate", "selectableModelsFilter", "sortableModelsFilter", "visibleModelsFilter", "itemTemplateFunction", "detachedRendering", "sortableOptions" ];
+
+        var kStylesForEmptyListCaption = {
+                "background" : "transparent",
+                "border" : "none",
+                "box-shadow" : "none"
+        };
+
+        Backbone.CollectionView = Backbone.View.extend( {
+
+                tagName : "ul",
+
+                events : {
+                        "mousedown li, td" : "_listItem_onMousedown",
+                        "dblclick li, td" : "_listItem_onDoubleClick",
+                        "click" : "_listBackground_onClick",
+                        "click ul.collection-list, table.collection-list" : "_listBackground_onClick",
+                        "keydown" : "_onKeydown"
+                },
+
+                // only used if Backbone.Courier is available
+                spawnMessages : {
+                        "focus" : "focus"
+                },
+
+                //only used if Backbone.Courier is available
+                passMessages : { "*" : "." },
+
+                initialize : function( options ) {
+                        var _this = this;
+
+                        this._hasBeenRendered = false;
+
+                        // default options
+                        options = _.extend( {}, {
+                                collection : null,
+                                modelView : this.modelView || null,
+                                modelViewOptions : {},
+                                itemTemplate : null,
+                                itemTemplateFunction : null,
+                                selectable : true,
+                                clickToSelect : true,
+                                selectableModelsFilter : null,
+                                visibleModelsFilter : null,
+                                sortableModelsFilter : null,
+                                selectMultiple : false,
+                                clickToToggle : false,
+                                processKeyEvents : true,
+                                sortable : false,
+                                sortableOptions : null,
+                                detachedRendering : false,
+                                emptyListCaption : null
+                        }, options );
+
+                        // add each of the white-listed options to the CollectionView object itself
+                        _.each( kAllowedOptions, function( option ) {
+                                _this[ option ] = options[option];
+                        } );
+
+                        if( ! this.collection ) this.collection = new Backbone.Collection();
+
+                        if( this._isBackboneCourierAvailable() ) {
+                                Backbone.Courier.add( this );
+                        }
+
+                        this.$el.data( "view", this ); // needed for connected sortable lists
+                        this.$el.addClass( "collection-list" );
+                        if( this.processKeyEvents )
+                                this.$el.attr( "tabindex", 0 ); // so we get keyboard events
+
+                        this.selectedItems = [];
+
+                        this._updateItemTemplate();
+
+                        if( this.collection )
+                                this._registerCollectionEvents();
+
+                        this.viewManager = new ChildViewContainer();
+
+                        //this.listenTo( this.collection, "change", function() { this.render(); this.spawn( "change" ); } ); // don't want changes to models bubbling up and triggering the list's render() function
+
+                        // note we do NOT call render here anymore, because if we inherit from this class we will likely call this
+                        // function using __super__ before the rest of the initialization logic for the decedent class. however, we may
+                        // override the render() function in that decedent class as well, and that will certainly expect all the initialization
+                        // to be done already. so we have to make sure to not jump the gun and start rending at this point.
+                        // this.render();
+                },
+
+                setOption : function( name, value ) {
+
+                        var _this = this;
+
+                        if( name === "collection" ) {
+                                this._setCollection( value );
+                        }
+                        else {
+                                if( _.contains( kAllowedOptions, name ) ) {
+
+                                        switch( name ) {
+                                                case "selectMultiple" :
+                                                        this[ name ] = value;
+                                                        if( !value && this.selectedItems.length > 1 )
+                                                                this.setSelectedModel( _.first( this.selectedItems ), { by : "cid" } );
+                                                        break;
+                                                case "selectable" :
+                                                        if( !value && this.selectedItems.length > 0 )
+                                                                this.setSelectedModels( [] );
+                                                        this[ name ] = value;
+                                                        break;
+                                                case "selectableModelsFilter" :
+                                                        this[ name ] = value;
+                                                        if( value && _.isFunction( value ) )
+                                                                this._validateSelection();
+                                                        break;
+                                                case "itemTemplate" :
+                                                        this[ name ] = value;
+                                                        this._updateItemTemplate();
+                                                        break;
+                                                case "processKeyEvents" :
+                                                        this[ name ] = value;
+                                                        if( value )  this.$el.attr( "tabindex", 0 ); // so we get keyboard events
+                                                        break;
+                                                case "modelView" :
+                                                        this[ name ] = value;
+                                                        //need to remove all old view instances
+                                                        this.viewManager.each( function( view ) {
+                                                                _this.viewManager.remove( view );
+                                                                // destroy the View itself
+                                                                view.remove();
+                                                        } );
+                                                        break;
+                                                default :
+                                                        this[ name ] = value;
+                                        }
+
+                                        if( _.contains( kOptionsRequiringRerendering, name ) )  this.render();
+                                }
+                                else throw name + " is not an allowed option";
+                        }
+                },
+
+                getSelectedModel : function( options ) {
+                        return _.first( this.getSelectedModels( options ) );
+                },
+
+                getSelectedModels : function ( options ) {
+                        var _this = this;
+
+                        options = _.extend( {}, {
+                                by : kDefaultReferenceBy
+                        }, options );
+
+                        var referenceBy = options.by;
+                        var items = [];
+
+                        switch( referenceBy ) {
+                                case "id" :
+                                        _.each( this.selectedItems, function ( item ) {
+                                                items.push( _this.collection.get( item ).id );
+                                        } );
+                                        break;
+                                case "cid" :
+                                        items = items.concat( this.selectedItems );
+                                        break;
+                                case "offset" :
+                                        var curLineNumber = 0;
+
+                                        var itemElements;
+                                        if( this._isRenderedAsTable() )
+                                                itemElements = this.$el.find( "> tbody > [data-model-cid]:not(.not-visible)" );
+                                        else if( this._isRenderedAsList() )
+                                                itemElements = this.$el.find( "> [data-model-cid]:not(.not-visible)" );
+
+                                        itemElements.each( function() {
+                                                var thisItemEl = $( this );
+                                                if( thisItemEl.is( ".selected" ) )
+                                                        items.push( curLineNumber );
+                                                curLineNumber++;
+                                        } );
+                                        break;
+                                case "model" :
+                                        _.each( this.selectedItems, function ( item ) {
+                                                items.push( _this.collection.get( item ) );
+                                        } );
+                                        break;
+                                case "view" :
+                                        _.each( this.selectedItems, function ( item ) {
+                                                items.push( _this.viewManager.findByModel( _this.collection.get( item ) ) );
+                                        } );
+                                        break;
+                        }
+
+                        return items;
+
+                },
+
+                setSelectedModels : function( newSelectedItems, options ) {
+                        if( ! this.selectable ) return; // used to throw error, but there are some circumstances in which a list can be selectable at times and not at others, don't want to have to worry about catching errors
+                        if( ! _.isArray( newSelectedItems ) ) throw "Invalid parameter value";
+
+                        options = _.extend( {}, {
+                                silent : false,
+                                by : kDefaultReferenceBy
+                        }, options );
+
+                        var referenceBy = options.by;
+                        var newSelectedCids = [];
+
+                        switch( referenceBy ) {
+                                case "cid" :
+                                        newSelectedCids = newSelectedItems;
+                                        break;
+                                case "id" :
+                                        this.collection.each( function( thisModel ) {
+                                                if( _.contains( newSelectedItems, thisModel.id ) ) newSelectedCids.push( thisModel.cid );
+                                        } );
+                                        break;
+                                case "model" :
+                                        newSelectedCids = _.pluck( newSelectedItems, "cid" );
+                                        break;
+                                case "view" :
+                                        _.each( newSelectedItems, function( item ) {
+                                                newSelectedCids.push( item.model.cid );
+                                        } );
+                                        break;
+                                case "offset" :
+                                        var curLineNumber = 0;
+                                        var selectedItems = [];
+
+                                        var itemElements;
+                                        if( this._isRenderedAsTable() )
+                                                itemElements = this.$el.find( "> tbody > [data-model-cid]:not(.not-visible)" );
+                                        else if( this._isRenderedAsList() )
+                                                itemElements = this.$el.find( "> [data-model-cid]:not(.not-visible)" );
+
+                                        itemElements.each( function() {
+                                                var thisItemEl = $( this );
+                                                if( _.contains( newSelectedItems, curLineNumber ) )
+                                                        newSelectedCids.push( thisItemEl.attr( "data-model-cid" ) );
+                                                curLineNumber++;
+                                        } );
+                                        break;
+                        }
+
+                        var oldSelectedModels = this.getSelectedModels();
+                        var oldSelectedCids = _.clone( this.selectedItems );
+
+                        this.selectedItems = this._convertStringsToInts( newSelectedCids );
+                        this._validateSelection();
+
+                        var newSelectedModels = this.getSelectedModels();
+
+                        if( ! this._containSameElements( oldSelectedCids, this.selectedItems ) )
+                        {
+                                this._addSelectedClassToSelectedItems( oldSelectedCids );
+
+                                if( ! options.silent )
+                                {
+                                        this.trigger( "selectionChanged", newSelectedModels, oldSelectedModels );
+                                        if( this._isBackboneCourierAvailable() ) {
+                                                this.spawn( "selectionChanged", {
+                                                        selectedModels : newSelectedModels,
+                                                        oldSelectedModels : oldSelectedModels
+                                                } );
+                                        }
+                                }
+
+                                this.updateDependentControls();
+                        }
+                },
+
+                setSelectedModel : function( newSelectedItem, options ) {
+                        if( ! newSelectedItem && newSelectedItem !== 0 )
+                                this.setSelectedModels( [], options );
+                        else
+                                this.setSelectedModels( [ newSelectedItem ], options );
+                },
+
+                render : function(){
+                        var _this = this;
+
+                        this._hasBeenRendered = true;
+
+                        if( this.selectable ) this._saveSelection();
+
+                        var modelViewContainerEl;
+
+                        // If collection view element is a table and it has a tbody
+                        // within it, render the model views inside of the tbody
+                        if( this._isRenderedAsTable() ) {
+                                var tbodyChild = this.$el.find( "> tbody" );
+                                if( tbodyChild.length > 0 )
+                                        modelViewContainerEl = tbodyChild;
+                        }
+
+                        if( _.isUndefined( modelViewContainerEl ) )
+                                modelViewContainerEl = this.$el;
+
+                        var oldViewManager = this.viewManager;
+                        this.viewManager = new ChildViewContainer();
+
+                        // detach each of our subviews that we have already created to represent models
+                        // in the collection. We are going to re-use the ones that represent models that
+                        // are still here, instead of creating new ones, so that we don't loose state
+                        // information in the views.
+                        oldViewManager.each( function( thisModelView ) {
+                                // to boost performance, only detach those views that will be sticking around.
+                                // we won't need the other ones later, so no need to detach them individually.
+                                if( _this.collection.get( thisModelView.model.cid ) )
+                                        thisModelView.$el.detach();
+                                else
+                                        thisModelView.remove();
+                        } );
+
+                        modelViewContainerEl.empty();
+                        var fragmentContainer;
+                        
+                        if( this.detachedRendering )
+                                fragmentContainer = document.createDocumentFragment();
+
+                        this.collection.each( function( thisModel ) {
+                                var thisModelView;
+
+                                thisModelView = oldViewManager.findByModelCid( thisModel.cid );
+                                if( _.isUndefined( thisModelView ) ) {
+                                        // if the model view was not already created on previous render,
+                                        // then create and initialize it now.
+
+                                        var modelViewOptions = this._getModelViewOptions( thisModel );
+                                        thisModelView = this._createNewModelView( thisModel, modelViewOptions );
+
+                                        thisModelView.collectionListView = _this;
+                                }
+
+                                var thisModelViewWrapped = this._wrapModelView( thisModelView );
+                                if( this.detachedRendering )
+                                        fragmentContainer.appendChild( thisModelViewWrapped[0] );
+                                else
+                                        modelViewContainerEl.append( thisModelViewWrapped );
+
+                                // we have to render the modelView after it has been put in context, as opposed to in the 
+                                // initialize function of the modelView, because some rendering might be dependent on
+                                // the modelView's context in the DOM tree. For example, if the modelView stretch()'s itself,
+                                // it must be in full context in the DOM tree or else the stretch will not behave as intended.
+                                var renderResult = thisModelView.render();
+
+                                // return false from the view's render function to hide this item
+                                if( renderResult === false ) {
+                                        thisModelViewWrapped.hide();
+                                        thisModelViewWrapped.addClass( "not-visible" );
+                                }
+
+                                if( _.isFunction( this.visibleModelsFilter ) ) {
+                                        if( ! this.visibleModelsFilter( thisModel ) ) {
+                                                if( thisModelViewWrapped.children().length === 1 )
+                                                        thisModelViewWrapped.hide();
+                                                else thisModelView.$el.hide();
+
+                                                thisModelViewWrapped.addClass( "not-visible" );
+                                        }
+                                }
+
+                                this.viewManager.add( thisModelView );
+                        }, this );
+
+                        if( this.detachedRendering )
+                                modelViewContainerEl.append( fragmentContainer );
+
+                        if( this.sortable )
+                        {
+                                var sortableOptions = _.extend( {
+                                        axis: "y",
+                                        distance: 10,
+                                        forcePlaceholderSize : true,
+                                        start : _.bind( this._sortStart, this ),
+                                        change : _.bind( this._sortChange, this ),
+                                        stop : _.bind( this._sortStop, this ),
+                                        receive : _.bind( this._receive, this ),
+                                        over : _.bind( this._over, this )
+                                }, _.result( this, "sortableOptions" ) );
+
+                                if( _this._isRenderedAsTable() ) {
+                                        sortableOptions.items = "> tbody > tr:not(.not-sortable)";
+                                }
+                                else if( _this._isRenderedAsList() ) {
+                                        sortableOptions.items = "> li:not(.not-sortable)";
+                                }
+
+                                this.$el = this.$el.sortable( sortableOptions );
+                        }
+
+                        if( this.emptyListCaption ) {
+                                var visibleView = this.viewManager.find( function( view ) {
+                                        return ! view.$el.hasClass( "not-visible" );
+                                } );
+
+                                if( _.isUndefined( visibleView ) ) {
+                                        var emptyListString;
+
+                                        if( _.isFunction( this.emptyListCaption ) )
+                                                emptyListString = this.emptyListCaption();
+                                        else
+                                                emptyListString = this.emptyListCaption;
+
+                                        var $emptyCaptionEl;
+                                        var $varEl = $( "<var class='empty-list-caption'>" + emptyListString + "</var>" );
+
+                                        //need to wrap the empty caption to make it fit the rendered list structure (either with an li or a tr td)
+                                        if( this._isRenderedAsList() )
+                                                $emptyListCaptionEl = $varEl.wrapAll( "<li class='not-sortable'></li>" ).parent().css( kStylesForEmptyListCaption );
+                                        else
+                                                $emptyListCaptionEl = $varEl.wrapAll( "<tr class='not-sortable'><td></td></tr>" ).parent().parent().css( kStylesForEmptyListCaption );
+
+                                        this.$el.append( $emptyListCaptionEl );
+
+                                }
+                        }
+
+                        this.trigger( "render" );
+                        if( this._isBackboneCourierAvailable() )
+                                this.spawn( "render" );
+
+                        if( this.selectable ) {
+                                this._restoreSelection();
+                                this.updateDependentControls();
+                        }
+
+                        if( _.isFunction( this.onAfterRender ) )
+                                this.onAfterRender();
+                },
+
+                updateDependentControls : function() {
+                        this.trigger( "updateDependentControls", this.getSelectedModels() );
+                        if( this._isBackboneCourierAvailable() ) {
+                                this.spawn( "updateDependentControls", {
+                                        selectedModels : this.getSelectedModels()
+                                } );
+                        }
+                },
+
+                // Override `Backbone.View.remove` to also destroy all Views in `viewManager`
+                remove : function() {
+                        this.viewManager.each( function( view ) {
+                                view.remove();
+                        } );
+
+                        Backbone.View.prototype.remove.apply( this, arguments );
+                },
+
+                _validateSelectionAndRender : function() {
+                        this._validateSelection();
+                        this.render();
+                },
+
+                _registerCollectionEvents : function() {
+                        this.listenTo( this.collection, "add", function() {
+                                if( this._hasBeenRendered ) this.render();
+                                if( this._isBackboneCourierAvailable() )
+                                        this.spawn( "add" );
+                        } );
+
+                        this.listenTo( this.collection, "remove", function() {
+                                if( this._hasBeenRendered ) this.render();
+                                if( this._isBackboneCourierAvailable() )
+                                        this.spawn( "remove" );
+                        } );
+
+                        this.listenTo( this.collection, "reset", function() {
+                                if( this._hasBeenRendered ) this.render();
+                                if( this._isBackboneCourierAvailable() )
+                                        this.spawn( "reset" );
+                        } );
+
+                        // It should be up to the model to rerender itself when it changes.
+                        // this.listenTo( this.collection, "change", function( model ) {
+                        //         if( this._hasBeenRendered ) this.viewManager.findByModel( model ).render();
+                        //         if( this._isBackboneCourierAvailable() )
+                        //                 this.spawn( "change", { model : model } );
+                        // } );
+
+                        this.listenTo( this.collection, "sort", function() {
+                                if( this._hasBeenRendered ) this.render();
+                                if( this._isBackboneCourierAvailable() )
+                                        this.spawn( "sort" );
+                        } );
+                },
+
+                _getClickedItemId : function( theEvent ) {
+                        var clickedItemId = null;
+
+                        // important to use currentTarget as opposed to target, since we could be bubbling
+                        // an event that took place within another collectionList
+                        var clickedItemEl = $( theEvent.currentTarget );
+                        if( clickedItemEl.closest( ".collection-list" ).get(0) !== this.$el.get(0) ) return;
+
+                        // determine which list item was clicked. If we clicked in the blank area
+                        // underneath all the elements, we want to know that too, since in this
+                        // case we will want to deselect all elements. so check to see if the clicked
+                        // DOM element is the list itself to find that out.
+                        var clickedItem = clickedItemEl.closest( "[data-model-cid]" );
+                        if( clickedItem.length > 0 )
+                        {
+                                clickedItemId = clickedItem.attr( "data-model-cid" );
+                                if( $.isNumeric( clickedItemId ) ) clickedItemId = parseInt( clickedItemId, 10 );
+                        }
+
+                        return clickedItemId;
+                },
+
+                _setCollection : function( newCollection ) {
+                        if( newCollection !== this.collection )
+                        {
+                                this.stopListening( this.collection );
+                                this.collection = newCollection;
+                                this._registerCollectionEvents();
+                        }
+
+                        if( this._hasBeenRendered ) this.render();
+                },
+
+                _updateItemTemplate : function() {
+                        var itemTemplateHtml;
+                        if( this.itemTemplate )
+                        {
+                                if( $( this.itemTemplate ).length === 0 )
+                                        throw "Could not find item template from selector: " + this.itemTemplate;
+
+                                itemTemplateHtml = $( this.itemTemplate ).html();
+                        }
+                        else
+                                itemTemplateHtml = this.$( ".item-template" ).html();
+
+                        if( itemTemplateHtml ) this.itemTemplateFunction = _.template( itemTemplateHtml );
+
+                },
+
+                _validateSelection : function() {
+                        // note can't use the collection's proxy to underscore because "cid" ais not an attribute,
+                        // but an element of the model object itself.
+                        var modelReferenceIds = _.pluck( this.collection.models, "cid" );
+                        this.selectedItems = _.intersection( modelReferenceIds, this.selectedItems );
+
+                        if( _.isFunction( this.selectableModelsFilter ) )
+                        {
+                                this.selectedItems = _.filter( this.selectedItems, function( thisItemId ) {
+                                        return this.selectableModelsFilter.call( this, this.collection.get( thisItemId ) );
+                                }, this );
+                        }
+                },
+
+                _saveSelection : function() {
+                        // save the current selection. use restoreSelection() to restore the selection to the state it was in the last time saveSelection() was called.
+                        if( ! this.selectable ) throw "Attempt to save selection on non-selectable list";
+                        this.savedSelection = {
+                                items : this.selectedItems,
+                                offset : this.getSelectedModel( { by : "offset" } )
+                        };
+                },
+
+                _restoreSelection : function() {
+                        if( ! this.savedSelection ) throw "Attempt to restore selection but no selection has been saved!";
+
+                        // reset selectedItems to empty so that we "redraw" all "selected" classes
+                        // when we set our new selection. We do this because it is likely that our
+                        // contents have been refreshed, and we have thus lost all old "selected" classes.
+                        this.setSelectedModels( [], { silent : true } );
+
+                        if( this.savedSelection.items.length > 0 )
+                        {
+                                // first try to restore the old selected items using their reference ids.
+                                this.setSelectedModels( this.savedSelection.items, { by : "cid", silent : true } );
+
+                                // all the items with the saved reference ids have been removed from the list.
+                                // ok. try to restore the selection based on the offset that used to be selected.
+                                // this is the expected behavior after a item is deleted from a list (i.e. select
+                                // the line that immediately follows the deleted line).
+                                if( this.selectedItems.length === 0 )
+                                        this.setSelectedModel( this.savedSelection.offset, { by : "offset" } );
+
+                                // Trigger a selection changed if the previously selected items were not all found
+                                if (this.selectedItems.length !== this.savedSelection.items.length)
+                                {
+                                        this.trigger( "selectionChanged", this.getSelectedModels(), [] );
+                                        if( this._isBackboneCourierAvailable() ) {
+                                                this.spawn( "selectionChanged", {
+                                                        selectedModels : this.getSelectedModels(),
+                                                        oldSelectedModels : []
+                                                } );
+                                        }
+                                }
+                        }
+
+                        delete this.savedSelection;
+                },
+
+                _addSelectedClassToSelectedItems : function( oldItemsIdsWithSelectedClass ) {
+                        if( _.isUndefined( oldItemsIdsWithSelectedClass ) ) oldItemsIdsWithSelectedClass = [];
+
+                        // oldItemsIdsWithSelectedClass is used for optimization purposes only. If this info is supplied then we
+                        // only have to add / remove the "selected" class from those items that "selected" state has changed.
+
+                        var itemsIdsFromWhichSelectedClassNeedsToBeRemoved = oldItemsIdsWithSelectedClass;
+                        itemsIdsFromWhichSelectedClassNeedsToBeRemoved = _.without( itemsIdsFromWhichSelectedClassNeedsToBeRemoved, this.selectedItems );
+
+                        _.each( itemsIdsFromWhichSelectedClassNeedsToBeRemoved, function( thisItemId ) {
+                                this.$el.find( "[data-model-cid=" + thisItemId + "]" ).removeClass( "selected" );
+                        }, this );
+
+                        var itemsIdsFromWhichSelectedClassNeedsToBeAdded = this.selectedItems;
+                        itemsIdsFromWhichSelectedClassNeedsToBeAdded = _.without( itemsIdsFromWhichSelectedClassNeedsToBeAdded, oldItemsIdsWithSelectedClass );
+
+                        _.each( itemsIdsFromWhichSelectedClassNeedsToBeAdded, function( thisItemId ) {
+                                this.$el.find( "[data-model-cid=" + thisItemId + "]" ).addClass( "selected" );
+                        }, this );
+                },
+
+                _reorderCollectionBasedOnHTML : function() {
+                        var _this = this;
+
+                        this.$el.children().each( function() {
+                                var thisModelCid = $( this ).attr( "data-model-cid" );
+
+                                if( thisModelCid )
+                                {
+                                        // remove the current model and then add it back (at the end of the collection).
+                                        // When we are done looping through all models, they will be in the correct order.
+                                        var thisModel = _this.collection.get( thisModelCid );
+                                        if( thisModel )
+                                        {
+                                                _this.collection.remove( thisModel, { silent : true } );
+                                                _this.collection.add( thisModel, { silent : true, sort : ! _this.collection.comparator } );
+                                        }
+                                }
+                        } );
+
+                        this.collection.trigger( "reorder" );
+
+                        if( this._isBackboneCourierAvailable() ) this.spawn( "reorder" );
+
+                        if( this.collection.comparator ) this.collection.sort();
+
+                },
+
+                _getModelViewConstructor : function( thisModel ) {
+                        return this.modelView || mDefaultModelViewConstructor;
+                },
+
+                _getModelViewOptions : function( thisModel ) {
+                        return _.extend( { model : thisModel }, this.modelViewOptions );
+                },
+
+                _createNewModelView : function( model, modelViewOptions ) {
+                        var modelViewConstructor = this._getModelViewConstructor( model );
+                        if( _.isUndefined( modelViewConstructor ) ) throw "Could not find modelView constructor for model";
+
+                        return new ( modelViewConstructor )( modelViewOptions );
+                },
+
+                _wrapModelView : function( modelView ) {
+                        var _this = this;
+
+                        // we use items client ids as opposed to real ids, since we may not have a representation
+                        // of these models on the server
+                        var wrappedModelView;
+
+                        if( this._isRenderedAsTable() ) {
+                                // if we are rendering the collection in a table, the template $el is a tr so we just need to set the data-model-cid
+                                wrappedModelView = modelView.$el.attr( "data-model-cid", modelView.model.cid );
+                        }
+                        else if( this._isRenderedAsList() ) {
+                                // if we are rendering the collection in a list, we need wrap each item in an <li></li> (if its not already an <li>)
+                                // and set the data-model-cid
+                                if( modelView.$el.prop( "tagName" ).toLowerCase() === "li" ) {
+                                        wrappedModelView = modelView.$el.attr( "data-model-cid", modelView.model.cid );
+                                } else {
+                                        wrappedModelView = modelView.$el.wrapAll( "<li data-model-cid='" + modelView.model.cid + "'></li>" ).parent();
+                                }
+                        }
+
+                        if( _.isFunction( this.sortableModelsFilter ) )
+                                if( ! this.sortableModelsFilter.call( _this, modelView.model ) )
+                                        wrappedModelView.addClass( "not-sortable" );
+
+                        if( _.isFunction( this.selectableModelsFilter ) )
+                                if( ! this.selectableModelsFilter.call( _this, modelView.model ) )
+                                        wrappedModelView.addClass( "not-selectable" );
+
+                        return wrappedModelView;
+                },
+
+                _convertStringsToInts : function( theArray ) {
+                        return _.map( theArray, function( thisEl ) {
+                                if( ! _.isString( thisEl ) ) return thisEl;
+                                var thisElAsNumber = parseInt( thisEl, 10 );
+                                return( thisElAsNumber == thisEl ? thisElAsNumber : thisEl );
+                        } );
+                },
+
+                _containSameElements : function( arrayA, arrayB ) {
+                        if( arrayA.length != arrayB.length ) return false;
+                        var intersectionSize = _.intersection( arrayA, arrayB ).length;
+                        return intersectionSize == arrayA.length; // and must also equal arrayB.length, since arrayA.length == arrayB.length
+                },
+
+                _isRenderedAsTable : function() {
+                        return this.$el.prop('tagName').toLowerCase() === 'table';
+                },
+
+
+                _isRenderedAsList : function() {
+                        return ! this._isRenderedAsTable();
+                },
+
+                _charCodes : {
+                        upArrow : 38,
+                        downArrow : 40
+                },
+
+                _isBackboneCourierAvailable : function() {
+                        return !_.isUndefined( Backbone.Courier );
+                },
+
+                _sortStart : function( event, ui ) {
+                        var modelBeingSorted = this.collection.get( ui.item.attr( "data-model-cid" ) );
+                        this.trigger( "sortStart", modelBeingSorted );
+                        if( this._isBackboneCourierAvailable() )
+                                this.spawn( "sortStart", { modelBeingSorted : modelBeingSorted } );
+                },
+
+                _sortChange : function( event, ui ) {
+                        var modelBeingSorted = this.collection.get( ui.item.attr( "data-model-cid" ) );
+                        this.trigger( "sortChange", modelBeingSorted );
+                        if( this._isBackboneCourierAvailable() )
+                                this.spawn( "sortChange", { modelBeingSorted : modelBeingSorted } );
+                },
+
+                _sortStop : function( event, ui ) {
+                        var modelBeingSorted = this.collection.get( ui.item.attr( "data-model-cid" ) );
+                        var modelViewContainerEl = (this._isRenderedAsTable()) ? this.$el.find( "> tbody" ) : this.$el;
+                        var newIndex = modelViewContainerEl.children().index( ui.item );
+
+                        if( newIndex == -1 ) {
+                                // the element was removed from this list. can happen if this sortable is connected
+                                // to another sortable, and the item was dropped into the other sortable.
+                                this.collection.remove( modelBeingSorted );
+                        }
+
+                        this._reorderCollectionBasedOnHTML();
+                        this.updateDependentControls();
+                        this.trigger( "sortStop", modelBeingSorted, newIndex );
+                        if( this._isBackboneCourierAvailable() )
+                                this.spawn( "sortStop", { modelBeingSorted : modelBeingSorted, newIndex : newIndex } );
+                },
+
+                _receive : function( event, ui ) {
+                        var senderListEl = ui.sender;
+                        var senderCollectionListView = senderListEl.data( "view" );
+                        if( ! senderCollectionListView || ! senderCollectionListView.collection ) return;
+
+                        var newIndex = this.$el.children().index( ui.item );
+                        var modelReceived = senderCollectionListView.collection.get( ui.item.attr( "data-model-cid" ) );
+                        this.collection.add( modelReceived, { at : newIndex } );
+                        modelReceived.collection = this.collection; // otherwise will not get properly set, since modelReceived.collection might already have a value.
+                        this.setSelectedModel( modelReceived );
+                },
+
+                _over : function( event, ui ) {
+                        // when an item is being dragged into the sortable,
+                        // hide the empty list caption if it exists
+                        this.$el.find( ".empty-list-caption" ).hide();
+                },
+
+                _onKeydown : function( event ) {
+                        if( ! this.processKeyEvents ) return true;
+
+                        var trap = false;
+
+                        if( this.getSelectedModels( { by : "offset" } ).length == 1 )
+                        {
+                                // need to trap down and up arrows or else the browser
+                                // will end up scrolling a autoscroll div.
+
+                                var currentOffset = this.getSelectedModel( { by : "offset" } );
+                                if( event.which === this._charCodes.upArrow && currentOffset !== 0 )
+                                {
+                                        this.setSelectedModel( currentOffset - 1, { by : "offset" } );
+                                        trap = true;
+                                }
+                                else if( event.which === this._charCodes.downArrow && currentOffset !== this.collection.length - 1 )
+                                {
+                                        this.setSelectedModel( currentOffset + 1, { by : "offset" } );
+                                        trap = true;
+                                }
+                        }
+
+                        return ! trap;
+                },
+
+                _listItem_onMousedown : function( theEvent ) {
+                        if( ! this.selectable || ! this.clickToSelect ) return;
+
+                        var clickedItemId = this._getClickedItemId( theEvent );
+
+                        if( clickedItemId )
+                        {
+                                // Exit if an unselectable item was clicked
+                                if( _.isFunction( this.selectableModelsFilter ) &&
+                                        ! this.selectableModelsFilter.call( this, this.collection.get( clickedItemId ) ) )
+                                {
+                                        return;
+                                }
+
+                                // a selectable list item was clicked
+                                if( this.selectMultiple && theEvent.shiftKey )
+                                {
+                                        var firstSelectedItemIndex = -1;
+
+                                        if( this.selectedItems.length > 0 )
+                                        {
+                                                this.collection.find( function( thisItemModel ) {
+                                                        firstSelectedItemIndex++;
+
+                                                        // exit when we find our first selected element
+                                                        return _.contains( this.selectedItems, thisItemModel.cid );
+                                                }, this );
+                                        }
+
+                                        var clickedItemIndex = -1;
+                                        this.collection.find( function( thisItemModel ) {
+                                                clickedItemIndex++;
+
+                                                // exit when we find the clicked element
+                                                return thisItemModel.cid == clickedItemId;
+                                        }, this );
+
+                                        var shiftKeyRootSelectedItemIndex = firstSelectedItemIndex == -1 ? clickedItemIndex : firstSelectedItemIndex;
+                                        var minSelectedItemIndex = Math.min( clickedItemIndex, shiftKeyRootSelectedItemIndex );
+                                        var maxSelectedItemIndex = Math.max( clickedItemIndex, shiftKeyRootSelectedItemIndex );
+
+                                        var newSelectedItems = [];
+                                        for( var thisIndex = minSelectedItemIndex; thisIndex <= maxSelectedItemIndex; thisIndex ++ )
+                                                newSelectedItems.push( this.collection.at( thisIndex ).cid );
+                                        this.setSelectedModels( newSelectedItems, { by : "cid" } );
+
+                                        // shift clicking will usually highlight selectable text, which we do not want.
+                                        // this is a cross browser (hopefully) snippet that deselects all text selection.
+                                        if( document.selection && document.selection.empty )
+                                                document.selection.empty();
+                                        else if(window.getSelection) {
+                                                var sel = window.getSelection();
+                                                if( sel && sel.removeAllRanges )
+                                                        sel.removeAllRanges();
+                                        }
+                                }
+                                else if( this.selectMultiple && ( this.clickToToggle || theEvent.metaKey ) )
+                                {
+                                        if( _.contains( this.selectedItems, clickedItemId ) )
+                                                this.setSelectedModels( _.without( this.selectedItems, clickedItemId ), { by : "cid" } );
+                                        else this.setSelectedModels( _.union( this.selectedItems, [ clickedItemId ] ), { by : "cid" } );
+                                }
+                                else
+                                        this.setSelectedModels( [ clickedItemId ], { by : "cid" } );
+                        }
+                        else
+                                // the blank area of the list was clicked
+                                this.setSelectedModels( [] );
+
+                },
+
+                _listItem_onDoubleClick : function( theEvent ) {
+                        var clickedItemId = this._getClickedItemId( theEvent );
+
+                        if( clickedItemId )
+                        {
+                                var clickedModel = this.collection.get( clickedItemId );
+                                this.trigger( "doubleClick", clickedModel );
+                                if( this._isBackboneCourierAvailable() )
+                                        this.spawn( "doubleClick", { clickedModel : clickedModel } );
+                        }
+                },
+
+                _listBackground_onClick : function( theEvent ) {
+                        if( ! this.selectable ) return;
+                        if( ! $( theEvent.target ).is( ".collection-list" ) ) return;
+
+                        this.setSelectedModels( [] );
+                }
+
+        }, {
+                setDefaultModelViewConstructor : function( theConstructor ) {
+                        mDefaultModelViewConstructor = theConstructor;
+                }
+        });
+
+
+        // Backbone.BabySitter
+        // -------------------
+        // v0.0.6
+        //
+        // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
+        // Distributed under MIT license
+        //
+        // http://github.com/babysitterjs/backbone.babysitter
+
+        // Backbone.ChildViewContainer
+        // ---------------------------
+        //
+        // Provide a container to store, retrieve and
+        // shut down child views.
+
+        ChildViewContainer = (function(Backbone, _){
+          
+          // Container Constructor
+          // ---------------------
+
+          var Container = function(views){
+            this._views = {};
+            this._indexByModel = {};
+            this._indexByCustom = {};
+            this._updateLength();
+
+            _.each(views, this.add, this);
+          };
+
+          // Container Methods
+          // -----------------
+
+          _.extend(Container.prototype, {
+
+            // Add a view to this container. Stores the view
+            // by `cid` and makes it searchable by the model
+            // cid (and model itself). Optionally specify
+            // a custom key to store an retrieve the view.
+            add: function(view, customIndex){
+              var viewCid = view.cid;
+
+              // store the view
+              this._views[viewCid] = view;
+
+              // index it by model
+              if (view.model){
+                this._indexByModel[view.model.cid] = viewCid;
+              }
+
+              // index by custom
+              if (customIndex){
+                this._indexByCustom[customIndex] = viewCid;
+              }
+
+              this._updateLength();
+            },
+
+            // Find a view by the model that was attached to
+            // it. Uses the model's `cid` to find it.
+            findByModel: function(model){
+              return this.findByModelCid(model.cid);
+            },
+
+            // Find a view by the `cid` of the model that was attached to
+            // it. Uses the model's `cid` to find the view `cid` and
+            // retrieve the view using it.
+            findByModelCid: function(modelCid){
+              var viewCid = this._indexByModel[modelCid];
+              return this.findByCid(viewCid);
+            },
+
+            // Find a view by a custom indexer.
+            findByCustom: function(index){
+              var viewCid = this._indexByCustom[index];
+              return this.findByCid(viewCid);
+            },
+
+            // Find by index. This is not guaranteed to be a
+            // stable index.
+            findByIndex: function(index){
+              return _.values(this._views)[index];
+            },
+
+            // retrieve a view by it's `cid` directly
+            findByCid: function(cid){
+              return this._views[cid];
+            },
+
+            // Remove a view
+            remove: function(view){
+              var viewCid = view.cid;
+
+              // delete model index
+              if (view.model){
+                delete this._indexByModel[view.model.cid];
+              }
+
+              // delete custom index
+              _.any(this._indexByCustom, function(cid, key) {
+                if (cid === viewCid) {
+                  delete this._indexByCustom[key];
+                  return true;
+                }
+              }, this);
+
+              // remove the view from the container
+              delete this._views[viewCid];
+
+              // update the length
+              this._updateLength();
+            },
+
+            // Call a method on every view in the container,
+            // passing parameters to the call method one at a
+            // time, like `function.call`.
+            call: function(method){
+              this.apply(method, _.tail(arguments));
+            },
+
+            // Apply a method on every view in the container,
+            // passing parameters to the call method one at a
+            // time, like `function.apply`.
+            apply: function(method, args){
+              _.each(this._views, function(view){
+                if (_.isFunction(view[method])){
+                  view[method].apply(view, args || []);
+                }
+              });
+            },
+
+            // Update the `.length` attribute on this container
+            _updateLength: function(){
+              this.length = _.size(this._views);
+            }
+          });
+
+          // Borrowing this code from Backbone.Collection:
+          // http://backbonejs.org/docs/backbone.html#section-106
+          //
+          // Mix in methods from Underscore, for iteration, and other
+          // collection related features.
+          var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter', 
+            'select', 'reject', 'every', 'all', 'some', 'any', 'include', 
+            'contains', 'invoke', 'toArray', 'first', 'initial', 'rest', 
+            'last', 'without', 'isEmpty', 'pluck'];
+
+          _.each(methods, function(method) {
+            Container.prototype[method] = function() {
+              var views = _.values(this._views);
+              var args = [views].concat(_.toArray(arguments));
+              return _[method].apply(_, args);
+            };
+          });
+
+          // return the public API
+          return Container;
+        })(Backbone, _);
+})();
\ No newline at end of file
diff --git a/src/UI/JsLibraries/jquery-ui.js b/src/UI/JsLibraries/jquery-ui.js
new file mode 100644
index 000000000..fe44a9c84
--- /dev/null
+++ b/src/UI/JsLibraries/jquery-ui.js
@@ -0,0 +1,4233 @@
+/*! jQuery UI - v1.10.4 - 2014-01-22
+* http://jqueryui.com
+* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.sortable.js, jquery.ui.slider.js
+* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */
+
+(function( $, undefined ) {
+
+var uuid = 0,
+	runiqueId = /^ui-id-\d+$/;
+
+// $.ui might exist from components with no dependencies, e.g., $.ui.position
+$.ui = $.ui || {};
+
+$.extend( $.ui, {
+	version: "1.10.4",
+
+	keyCode: {
+		BACKSPACE: 8,
+		COMMA: 188,
+		DELETE: 46,
+		DOWN: 40,
+		END: 35,
+		ENTER: 13,
+		ESCAPE: 27,
+		HOME: 36,
+		LEFT: 37,
+		NUMPAD_ADD: 107,
+		NUMPAD_DECIMAL: 110,
+		NUMPAD_DIVIDE: 111,
+		NUMPAD_ENTER: 108,
+		NUMPAD_MULTIPLY: 106,
+		NUMPAD_SUBTRACT: 109,
+		PAGE_DOWN: 34,
+		PAGE_UP: 33,
+		PERIOD: 190,
+		RIGHT: 39,
+		SPACE: 32,
+		TAB: 9,
+		UP: 38
+	}
+});
+
+// plugins
+$.fn.extend({
+	focus: (function( orig ) {
+		return function( delay, fn ) {
+			return typeof delay === "number" ?
+				this.each(function() {
+					var elem = this;
+					setTimeout(function() {
+						$( elem ).focus();
+						if ( fn ) {
+							fn.call( elem );
+						}
+					}, delay );
+				}) :
+				orig.apply( this, arguments );
+		};
+	})( $.fn.focus ),
+
+	scrollParent: function() {
+		var scrollParent;
+		if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
+			scrollParent = this.parents().filter(function() {
+				return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
+			}).eq(0);
+		} else {
+			scrollParent = this.parents().filter(function() {
+				return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
+			}).eq(0);
+		}
+
+		return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
+	},
+
+	zIndex: function( zIndex ) {
+		if ( zIndex !== undefined ) {
+			return this.css( "zIndex", zIndex );
+		}
+
+		if ( this.length ) {
+			var elem = $( this[ 0 ] ), position, value;
+			while ( elem.length && elem[ 0 ] !== document ) {
+				// Ignore z-index if position is set to a value where z-index is ignored by the browser
+				// This makes behavior of this function consistent across browsers
+				// WebKit always returns auto if the element is positioned
+				position = elem.css( "position" );
+				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+					// IE returns 0 when zIndex is not specified
+					// other browsers return a string
+					// we ignore the case of nested elements with an explicit value of 0
+					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+					value = parseInt( elem.css( "zIndex" ), 10 );
+					if ( !isNaN( value ) && value !== 0 ) {
+						return value;
+					}
+				}
+				elem = elem.parent();
+			}
+		}
+
+		return 0;
+	},
+
+	uniqueId: function() {
+		return this.each(function() {
+			if ( !this.id ) {
+				this.id = "ui-id-" + (++uuid);
+			}
+		});
+	},
+
+	removeUniqueId: function() {
+		return this.each(function() {
+			if ( runiqueId.test( this.id ) ) {
+				$( this ).removeAttr( "id" );
+			}
+		});
+	}
+});
+
+// selectors
+function focusable( element, isTabIndexNotNaN ) {
+	var map, mapName, img,
+		nodeName = element.nodeName.toLowerCase();
+	if ( "area" === nodeName ) {
+		map = element.parentNode;
+		mapName = map.name;
+		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
+			return false;
+		}
+		img = $( "img[usemap=#" + mapName + "]" )[0];
+		return !!img && visible( img );
+	}
+	return ( /input|select|textarea|button|object/.test( nodeName ) ?
+		!element.disabled :
+		"a" === nodeName ?
+			element.href || isTabIndexNotNaN :
+			isTabIndexNotNaN) &&
+		// the element and all of its ancestors must be visible
+		visible( element );
+}
+
+function visible( element ) {
+	return $.expr.filters.visible( element ) &&
+		!$( element ).parents().addBack().filter(function() {
+			return $.css( this, "visibility" ) === "hidden";
+		}).length;
+}
+
+$.extend( $.expr[ ":" ], {
+	data: $.expr.createPseudo ?
+		$.expr.createPseudo(function( dataName ) {
+			return function( elem ) {
+				return !!$.data( elem, dataName );
+			};
+		}) :
+		// support: jQuery <1.8
+		function( elem, i, match ) {
+			return !!$.data( elem, match[ 3 ] );
+		},
+
+	focusable: function( element ) {
+		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
+	},
+
+	tabbable: function( element ) {
+		var tabIndex = $.attr( element, "tabindex" ),
+			isTabIndexNaN = isNaN( tabIndex );
+		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
+	}
+});
+
+// support: jQuery <1.8
+if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
+	$.each( [ "Width", "Height" ], function( i, name ) {
+		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
+			type = name.toLowerCase(),
+			orig = {
+				innerWidth: $.fn.innerWidth,
+				innerHeight: $.fn.innerHeight,
+				outerWidth: $.fn.outerWidth,
+				outerHeight: $.fn.outerHeight
+			};
+
+		function reduce( elem, size, border, margin ) {
+			$.each( side, function() {
+				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
+				if ( border ) {
+					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
+				}
+				if ( margin ) {
+					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
+				}
+			});
+			return size;
+		}
+
+		$.fn[ "inner" + name ] = function( size ) {
+			if ( size === undefined ) {
+				return orig[ "inner" + name ].call( this );
+			}
+
+			return this.each(function() {
+				$( this ).css( type, reduce( this, size ) + "px" );
+			});
+		};
+
+		$.fn[ "outer" + name] = function( size, margin ) {
+			if ( typeof size !== "number" ) {
+				return orig[ "outer" + name ].call( this, size );
+			}
+
+			return this.each(function() {
+				$( this).css( type, reduce( this, size, true, margin ) + "px" );
+			});
+		};
+	});
+}
+
+// support: jQuery <1.8
+if ( !$.fn.addBack ) {
+	$.fn.addBack = function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter( selector )
+		);
+	};
+}
+
+// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
+if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
+	$.fn.removeData = (function( removeData ) {
+		return function( key ) {
+			if ( arguments.length ) {
+				return removeData.call( this, $.camelCase( key ) );
+			} else {
+				return removeData.call( this );
+			}
+		};
+	})( $.fn.removeData );
+}
+
+
+
+
+
+// deprecated
+$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
+
+$.support.selectstart = "onselectstart" in document.createElement( "div" );
+$.fn.extend({
+	disableSelection: function() {
+		return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
+			".ui-disableSelection", function( event ) {
+				event.preventDefault();
+			});
+	},
+
+	enableSelection: function() {
+		return this.unbind( ".ui-disableSelection" );
+	}
+});
+
+$.extend( $.ui, {
+	// $.ui.plugin is deprecated. Use $.widget() extensions instead.
+	plugin: {
+		add: function( module, option, set ) {
+			var i,
+				proto = $.ui[ module ].prototype;
+			for ( i in set ) {
+				proto.plugins[ i ] = proto.plugins[ i ] || [];
+				proto.plugins[ i ].push( [ option, set[ i ] ] );
+			}
+		},
+		call: function( instance, name, args ) {
+			var i,
+				set = instance.plugins[ name ];
+			if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
+				return;
+			}
+
+			for ( i = 0; i < set.length; i++ ) {
+				if ( instance.options[ set[ i ][ 0 ] ] ) {
+					set[ i ][ 1 ].apply( instance.element, args );
+				}
+			}
+		}
+	},
+
+	// only used by resizable
+	hasScroll: function( el, a ) {
+
+		//If overflow is hidden, the element might have extra content, but the user wants to hide it
+		if ( $( el ).css( "overflow" ) === "hidden") {
+			return false;
+		}
+
+		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
+			has = false;
+
+		if ( el[ scroll ] > 0 ) {
+			return true;
+		}
+
+		// TODO: determine which cases actually cause this to happen
+		// if the element doesn't have the scroll set, see if it's possible to
+		// set the scroll
+		el[ scroll ] = 1;
+		has = ( el[ scroll ] > 0 );
+		el[ scroll ] = 0;
+		return has;
+	}
+});
+
+})( jQuery );
+(function( $, undefined ) {
+
+var uuid = 0,
+	slice = Array.prototype.slice,
+	_cleanData = $.cleanData;
+$.cleanData = function( elems ) {
+	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+		try {
+			$( elem ).triggerHandler( "remove" );
+		// http://bugs.jquery.com/ticket/8235
+		} catch( e ) {}
+	}
+	_cleanData( elems );
+};
+
+$.widget = function( name, base, prototype ) {
+	var fullName, existingConstructor, constructor, basePrototype,
+		// proxiedPrototype allows the provided prototype to remain unmodified
+		// so that it can be used as a mixin for multiple widgets (#8876)
+		proxiedPrototype = {},
+		namespace = name.split( "." )[ 0 ];
+
+	name = name.split( "." )[ 1 ];
+	fullName = namespace + "-" + name;
+
+	if ( !prototype ) {
+		prototype = base;
+		base = $.Widget;
+	}
+
+	// create selector for plugin
+	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+		return !!$.data( elem, fullName );
+	};
+
+	$[ namespace ] = $[ namespace ] || {};
+	existingConstructor = $[ namespace ][ name ];
+	constructor = $[ namespace ][ name ] = function( options, element ) {
+		// allow instantiation without "new" keyword
+		if ( !this._createWidget ) {
+			return new constructor( options, element );
+		}
+
+		// allow instantiation without initializing for simple inheritance
+		// must use "new" keyword (the code above always passes args)
+		if ( arguments.length ) {
+			this._createWidget( options, element );
+		}
+	};
+	// extend with the existing constructor to carry over any static properties
+	$.extend( constructor, existingConstructor, {
+		version: prototype.version,
+		// copy the object used to create the prototype in case we need to
+		// redefine the widget later
+		_proto: $.extend( {}, prototype ),
+		// track widgets that inherit from this widget in case this widget is
+		// redefined after a widget inherits from it
+		_childConstructors: []
+	});
+
+	basePrototype = new base();
+	// we need to make the options hash a property directly on the new instance
+	// otherwise we'll modify the options hash on the prototype that we're
+	// inheriting from
+	basePrototype.options = $.widget.extend( {}, basePrototype.options );
+	$.each( prototype, function( prop, value ) {
+		if ( !$.isFunction( value ) ) {
+			proxiedPrototype[ prop ] = value;
+			return;
+		}
+		proxiedPrototype[ prop ] = (function() {
+			var _super = function() {
+					return base.prototype[ prop ].apply( this, arguments );
+				},
+				_superApply = function( args ) {
+					return base.prototype[ prop ].apply( this, args );
+				};
+			return function() {
+				var __super = this._super,
+					__superApply = this._superApply,
+					returnValue;
+
+				this._super = _super;
+				this._superApply = _superApply;
+
+				returnValue = value.apply( this, arguments );
+
+				this._super = __super;
+				this._superApply = __superApply;
+
+				return returnValue;
+			};
+		})();
+	});
+	constructor.prototype = $.widget.extend( basePrototype, {
+		// TODO: remove support for widgetEventPrefix
+		// always use the name + a colon as the prefix, e.g., draggable:start
+		// don't prefix for widgets that aren't DOM-based
+		widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
+	}, proxiedPrototype, {
+		constructor: constructor,
+		namespace: namespace,
+		widgetName: name,
+		widgetFullName: fullName
+	});
+
+	// If this widget is being redefined then we need to find all widgets that
+	// are inheriting from it and redefine all of them so that they inherit from
+	// the new version of this widget. We're essentially trying to replace one
+	// level in the prototype chain.
+	if ( existingConstructor ) {
+		$.each( existingConstructor._childConstructors, function( i, child ) {
+			var childPrototype = child.prototype;
+
+			// redefine the child widget using the same prototype that was
+			// originally used, but inherit from the new version of the base
+			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+		});
+		// remove the list of existing child constructors from the old constructor
+		// so the old child constructors can be garbage collected
+		delete existingConstructor._childConstructors;
+	} else {
+		base._childConstructors.push( constructor );
+	}
+
+	$.widget.bridge( name, constructor );
+};
+
+$.widget.extend = function( target ) {
+	var input = slice.call( arguments, 1 ),
+		inputIndex = 0,
+		inputLength = input.length,
+		key,
+		value;
+	for ( ; inputIndex < inputLength; inputIndex++ ) {
+		for ( key in input[ inputIndex ] ) {
+			value = input[ inputIndex ][ key ];
+			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+				// Clone objects
+				if ( $.isPlainObject( value ) ) {
+					target[ key ] = $.isPlainObject( target[ key ] ) ?
+						$.widget.extend( {}, target[ key ], value ) :
+						// Don't extend strings, arrays, etc. with objects
+						$.widget.extend( {}, value );
+				// Copy everything else by reference
+				} else {
+					target[ key ] = value;
+				}
+			}
+		}
+	}
+	return target;
+};
+
+$.widget.bridge = function( name, object ) {
+	var fullName = object.prototype.widgetFullName || name;
+	$.fn[ name ] = function( options ) {
+		var isMethodCall = typeof options === "string",
+			args = slice.call( arguments, 1 ),
+			returnValue = this;
+
+		// allow multiple hashes to be passed on init
+		options = !isMethodCall && args.length ?
+			$.widget.extend.apply( null, [ options ].concat(args) ) :
+			options;
+
+		if ( isMethodCall ) {
+			this.each(function() {
+				var methodValue,
+					instance = $.data( this, fullName );
+				if ( !instance ) {
+					return $.error( "cannot call methods on " + name + " prior to initialization; " +
+						"attempted to call method '" + options + "'" );
+				}
+				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+				}
+				methodValue = instance[ options ].apply( instance, args );
+				if ( methodValue !== instance && methodValue !== undefined ) {
+					returnValue = methodValue && methodValue.jquery ?
+						returnValue.pushStack( methodValue.get() ) :
+						methodValue;
+					return false;
+				}
+			});
+		} else {
+			this.each(function() {
+				var instance = $.data( this, fullName );
+				if ( instance ) {
+					instance.option( options || {} )._init();
+				} else {
+					$.data( this, fullName, new object( options, this ) );
+				}
+			});
+		}
+
+		return returnValue;
+	};
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+	widgetName: "widget",
+	widgetEventPrefix: "",
+	defaultElement: "<div>",
+	options: {
+		disabled: false,
+
+		// callbacks
+		create: null
+	},
+	_createWidget: function( options, element ) {
+		element = $( element || this.defaultElement || this )[ 0 ];
+		this.element = $( element );
+		this.uuid = uuid++;
+		this.eventNamespace = "." + this.widgetName + this.uuid;
+		this.options = $.widget.extend( {},
+			this.options,
+			this._getCreateOptions(),
+			options );
+
+		this.bindings = $();
+		this.hoverable = $();
+		this.focusable = $();
+
+		if ( element !== this ) {
+			$.data( element, this.widgetFullName, this );
+			this._on( true, this.element, {
+				remove: function( event ) {
+					if ( event.target === element ) {
+						this.destroy();
+					}
+				}
+			});
+			this.document = $( element.style ?
+				// element within the document
+				element.ownerDocument :
+				// element is window or document
+				element.document || element );
+			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+		}
+
+		this._create();
+		this._trigger( "create", null, this._getCreateEventData() );
+		this._init();
+	},
+	_getCreateOptions: $.noop,
+	_getCreateEventData: $.noop,
+	_create: $.noop,
+	_init: $.noop,
+
+	destroy: function() {
+		this._destroy();
+		// we can probably remove the unbind calls in 2.0
+		// all event bindings should go through this._on()
+		this.element
+			.unbind( this.eventNamespace )
+			// 1.9 BC for #7810
+			// TODO remove dual storage
+			.removeData( this.widgetName )
+			.removeData( this.widgetFullName )
+			// support: jquery <1.6.3
+			// http://bugs.jquery.com/ticket/9413
+			.removeData( $.camelCase( this.widgetFullName ) );
+		this.widget()
+			.unbind( this.eventNamespace )
+			.removeAttr( "aria-disabled" )
+			.removeClass(
+				this.widgetFullName + "-disabled " +
+				"ui-state-disabled" );
+
+		// clean up events and states
+		this.bindings.unbind( this.eventNamespace );
+		this.hoverable.removeClass( "ui-state-hover" );
+		this.focusable.removeClass( "ui-state-focus" );
+	},
+	_destroy: $.noop,
+
+	widget: function() {
+		return this.element;
+	},
+
+	option: function( key, value ) {
+		var options = key,
+			parts,
+			curOption,
+			i;
+
+		if ( arguments.length === 0 ) {
+			// don't return a reference to the internal hash
+			return $.widget.extend( {}, this.options );
+		}
+
+		if ( typeof key === "string" ) {
+			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+			options = {};
+			parts = key.split( "." );
+			key = parts.shift();
+			if ( parts.length ) {
+				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+				for ( i = 0; i < parts.length - 1; i++ ) {
+					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+					curOption = curOption[ parts[ i ] ];
+				}
+				key = parts.pop();
+				if ( arguments.length === 1 ) {
+					return curOption[ key ] === undefined ? null : curOption[ key ];
+				}
+				curOption[ key ] = value;
+			} else {
+				if ( arguments.length === 1 ) {
+					return this.options[ key ] === undefined ? null : this.options[ key ];
+				}
+				options[ key ] = value;
+			}
+		}
+
+		this._setOptions( options );
+
+		return this;
+	},
+	_setOptions: function( options ) {
+		var key;
+
+		for ( key in options ) {
+			this._setOption( key, options[ key ] );
+		}
+
+		return this;
+	},
+	_setOption: function( key, value ) {
+		this.options[ key ] = value;
+
+		if ( key === "disabled" ) {
+			this.widget()
+				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
+				.attr( "aria-disabled", value );
+			this.hoverable.removeClass( "ui-state-hover" );
+			this.focusable.removeClass( "ui-state-focus" );
+		}
+
+		return this;
+	},
+
+	enable: function() {
+		return this._setOption( "disabled", false );
+	},
+	disable: function() {
+		return this._setOption( "disabled", true );
+	},
+
+	_on: function( suppressDisabledCheck, element, handlers ) {
+		var delegateElement,
+			instance = this;
+
+		// no suppressDisabledCheck flag, shuffle arguments
+		if ( typeof suppressDisabledCheck !== "boolean" ) {
+			handlers = element;
+			element = suppressDisabledCheck;
+			suppressDisabledCheck = false;
+		}
+
+		// no element argument, shuffle and use this.element
+		if ( !handlers ) {
+			handlers = element;
+			element = this.element;
+			delegateElement = this.widget();
+		} else {
+			// accept selectors, DOM elements
+			element = delegateElement = $( element );
+			this.bindings = this.bindings.add( element );
+		}
+
+		$.each( handlers, function( event, handler ) {
+			function handlerProxy() {
+				// allow widgets to customize the disabled handling
+				// - disabled as an array instead of boolean
+				// - disabled class as method for disabling individual parts
+				if ( !suppressDisabledCheck &&
+						( instance.options.disabled === true ||
+							$( this ).hasClass( "ui-state-disabled" ) ) ) {
+					return;
+				}
+				return ( typeof handler === "string" ? instance[ handler ] : handler )
+					.apply( instance, arguments );
+			}
+
+			// copy the guid so direct unbinding works
+			if ( typeof handler !== "string" ) {
+				handlerProxy.guid = handler.guid =
+					handler.guid || handlerProxy.guid || $.guid++;
+			}
+
+			var match = event.match( /^(\w+)\s*(.*)$/ ),
+				eventName = match[1] + instance.eventNamespace,
+				selector = match[2];
+			if ( selector ) {
+				delegateElement.delegate( selector, eventName, handlerProxy );
+			} else {
+				element.bind( eventName, handlerProxy );
+			}
+		});
+	},
+
+	_off: function( element, eventName ) {
+		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
+		element.unbind( eventName ).undelegate( eventName );
+	},
+
+	_delay: function( handler, delay ) {
+		function handlerProxy() {
+			return ( typeof handler === "string" ? instance[ handler ] : handler )
+				.apply( instance, arguments );
+		}
+		var instance = this;
+		return setTimeout( handlerProxy, delay || 0 );
+	},
+
+	_hoverable: function( element ) {
+		this.hoverable = this.hoverable.add( element );
+		this._on( element, {
+			mouseenter: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-hover" );
+			},
+			mouseleave: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-hover" );
+			}
+		});
+	},
+
+	_focusable: function( element ) {
+		this.focusable = this.focusable.add( element );
+		this._on( element, {
+			focusin: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-focus" );
+			},
+			focusout: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-focus" );
+			}
+		});
+	},
+
+	_trigger: function( type, event, data ) {
+		var prop, orig,
+			callback = this.options[ type ];
+
+		data = data || {};
+		event = $.Event( event );
+		event.type = ( type === this.widgetEventPrefix ?
+			type :
+			this.widgetEventPrefix + type ).toLowerCase();
+		// the original event may come from any element
+		// so we need to reset the target on the new event
+		event.target = this.element[ 0 ];
+
+		// copy original event properties over to the new event
+		orig = event.originalEvent;
+		if ( orig ) {
+			for ( prop in orig ) {
+				if ( !( prop in event ) ) {
+					event[ prop ] = orig[ prop ];
+				}
+			}
+		}
+
+		this.element.trigger( event, data );
+		return !( $.isFunction( callback ) &&
+			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+			event.isDefaultPrevented() );
+	}
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+		if ( typeof options === "string" ) {
+			options = { effect: options };
+		}
+		var hasOptions,
+			effectName = !options ?
+				method :
+				options === true || typeof options === "number" ?
+					defaultEffect :
+					options.effect || defaultEffect;
+		options = options || {};
+		if ( typeof options === "number" ) {
+			options = { duration: options };
+		}
+		hasOptions = !$.isEmptyObject( options );
+		options.complete = callback;
+		if ( options.delay ) {
+			element.delay( options.delay );
+		}
+		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+			element[ method ]( options );
+		} else if ( effectName !== method && element[ effectName ] ) {
+			element[ effectName ]( options.duration, options.easing, callback );
+		} else {
+			element.queue(function( next ) {
+				$( this )[ method ]();
+				if ( callback ) {
+					callback.call( element[ 0 ] );
+				}
+				next();
+			});
+		}
+	};
+});
+
+})( jQuery );
+(function( $, undefined ) {
+
+var mouseHandled = false;
+$( document ).mouseup( function() {
+	mouseHandled = false;
+});
+
+$.widget("ui.mouse", {
+	version: "1.10.4",
+	options: {
+		cancel: "input,textarea,button,select,option",
+		distance: 1,
+		delay: 0
+	},
+	_mouseInit: function() {
+		var that = this;
+
+		this.element
+			.bind("mousedown."+this.widgetName, function(event) {
+				return that._mouseDown(event);
+			})
+			.bind("click."+this.widgetName, function(event) {
+				if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
+					$.removeData(event.target, that.widgetName + ".preventClickEvent");
+					event.stopImmediatePropagation();
+					return false;
+				}
+			});
+
+		this.started = false;
+	},
+
+	// TODO: make sure destroying one instance of mouse doesn't mess with
+	// other instances of mouse
+	_mouseDestroy: function() {
+		this.element.unbind("."+this.widgetName);
+		if ( this._mouseMoveDelegate ) {
+			$(document)
+				.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
+				.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
+		}
+	},
+
+	_mouseDown: function(event) {
+		// don't let more than one widget handle mouseStart
+		if( mouseHandled ) { return; }
+
+		// we may have missed mouseup (out of window)
+		(this._mouseStarted && this._mouseUp(event));
+
+		this._mouseDownEvent = event;
+
+		var that = this,
+			btnIsLeft = (event.which === 1),
+			// event.target.nodeName works around a bug in IE 8 with
+			// disabled inputs (#7620)
+			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
+		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
+			return true;
+		}
+
+		this.mouseDelayMet = !this.options.delay;
+		if (!this.mouseDelayMet) {
+			this._mouseDelayTimer = setTimeout(function() {
+				that.mouseDelayMet = true;
+			}, this.options.delay);
+		}
+
+		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+			this._mouseStarted = (this._mouseStart(event) !== false);
+			if (!this._mouseStarted) {
+				event.preventDefault();
+				return true;
+			}
+		}
+
+		// Click event may never have fired (Gecko & Opera)
+		if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
+			$.removeData(event.target, this.widgetName + ".preventClickEvent");
+		}
+
+		// these delegates are required to keep context
+		this._mouseMoveDelegate = function(event) {
+			return that._mouseMove(event);
+		};
+		this._mouseUpDelegate = function(event) {
+			return that._mouseUp(event);
+		};
+		$(document)
+			.bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
+			.bind("mouseup."+this.widgetName, this._mouseUpDelegate);
+
+		event.preventDefault();
+
+		mouseHandled = true;
+		return true;
+	},
+
+	_mouseMove: function(event) {
+		// IE mouseup check - mouseup happened when mouse was out of window
+		if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
+			return this._mouseUp(event);
+		}
+
+		if (this._mouseStarted) {
+			this._mouseDrag(event);
+			return event.preventDefault();
+		}
+
+		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+			this._mouseStarted =
+				(this._mouseStart(this._mouseDownEvent, event) !== false);
+			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
+		}
+
+		return !this._mouseStarted;
+	},
+
+	_mouseUp: function(event) {
+		$(document)
+			.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
+			.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
+
+		if (this._mouseStarted) {
+			this._mouseStarted = false;
+
+			if (event.target === this._mouseDownEvent.target) {
+				$.data(event.target, this.widgetName + ".preventClickEvent", true);
+			}
+
+			this._mouseStop(event);
+		}
+
+		return false;
+	},
+
+	_mouseDistanceMet: function(event) {
+		return (Math.max(
+				Math.abs(this._mouseDownEvent.pageX - event.pageX),
+				Math.abs(this._mouseDownEvent.pageY - event.pageY)
+			) >= this.options.distance
+		);
+	},
+
+	_mouseDelayMet: function(/* event */) {
+		return this.mouseDelayMet;
+	},
+
+	// These are placeholder methods, to be overriden by extending plugin
+	_mouseStart: function(/* event */) {},
+	_mouseDrag: function(/* event */) {},
+	_mouseStop: function(/* event */) {},
+	_mouseCapture: function(/* event */) { return true; }
+});
+
+})(jQuery);
+(function( $, undefined ) {
+
+$.widget("ui.draggable", $.ui.mouse, {
+	version: "1.10.4",
+	widgetEventPrefix: "drag",
+	options: {
+		addClasses: true,
+		appendTo: "parent",
+		axis: false,
+		connectToSortable: false,
+		containment: false,
+		cursor: "auto",
+		cursorAt: false,
+		grid: false,
+		handle: false,
+		helper: "original",
+		iframeFix: false,
+		opacity: false,
+		refreshPositions: false,
+		revert: false,
+		revertDuration: 500,
+		scope: "default",
+		scroll: true,
+		scrollSensitivity: 20,
+		scrollSpeed: 20,
+		snap: false,
+		snapMode: "both",
+		snapTolerance: 20,
+		stack: false,
+		zIndex: false,
+
+		// callbacks
+		drag: null,
+		start: null,
+		stop: null
+	},
+	_create: function() {
+
+		if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
+			this.element[0].style.position = "relative";
+		}
+		if (this.options.addClasses){
+			this.element.addClass("ui-draggable");
+		}
+		if (this.options.disabled){
+			this.element.addClass("ui-draggable-disabled");
+		}
+
+		this._mouseInit();
+
+	},
+
+	_destroy: function() {
+		this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
+		this._mouseDestroy();
+	},
+
+	_mouseCapture: function(event) {
+
+		var o = this.options;
+
+		// among others, prevent a drag on a resizable-handle
+		if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
+			return false;
+		}
+
+		//Quit if we're not on a valid handle
+		this.handle = this._getHandle(event);
+		if (!this.handle) {
+			return false;
+		}
+
+		$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
+			$("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
+			.css({
+				width: this.offsetWidth+"px", height: this.offsetHeight+"px",
+				position: "absolute", opacity: "0.001", zIndex: 1000
+			})
+			.css($(this).offset())
+			.appendTo("body");
+		});
+
+		return true;
+
+	},
+
+	_mouseStart: function(event) {
+
+		var o = this.options;
+
+		//Create and append the visible helper
+		this.helper = this._createHelper(event);
+
+		this.helper.addClass("ui-draggable-dragging");
+
+		//Cache the helper size
+		this._cacheHelperProportions();
+
+		//If ddmanager is used for droppables, set the global draggable
+		if($.ui.ddmanager) {
+			$.ui.ddmanager.current = this;
+		}
+
+		/*
+		 * - Position generation -
+		 * This block generates everything position related - it's the core of draggables.
+		 */
+
+		//Cache the margins of the original element
+		this._cacheMargins();
+
+		//Store the helper's css position
+		this.cssPosition = this.helper.css( "position" );
+		this.scrollParent = this.helper.scrollParent();
+		this.offsetParent = this.helper.offsetParent();
+		this.offsetParentCssPosition = this.offsetParent.css( "position" );
+
+		//The element's absolute position on the page minus margins
+		this.offset = this.positionAbs = this.element.offset();
+		this.offset = {
+			top: this.offset.top - this.margins.top,
+			left: this.offset.left - this.margins.left
+		};
+
+		//Reset scroll cache
+		this.offset.scroll = false;
+
+		$.extend(this.offset, {
+			click: { //Where the click happened, relative to the element
+				left: event.pageX - this.offset.left,
+				top: event.pageY - this.offset.top
+			},
+			parent: this._getParentOffset(),
+			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+		});
+
+		//Generate the original position
+		this.originalPosition = this.position = this._generatePosition(event);
+		this.originalPageX = event.pageX;
+		this.originalPageY = event.pageY;
+
+		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
+		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+		//Set a containment if given in the options
+		this._setContainment();
+
+		//Trigger event + callbacks
+		if(this._trigger("start", event) === false) {
+			this._clear();
+			return false;
+		}
+
+		//Recache the helper size
+		this._cacheHelperProportions();
+
+		//Prepare the droppable offsets
+		if ($.ui.ddmanager && !o.dropBehaviour) {
+			$.ui.ddmanager.prepareOffsets(this, event);
+		}
+
+
+		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+
+		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.dragStart(this, event);
+		}
+
+		return true;
+	},
+
+	_mouseDrag: function(event, noPropagation) {
+		// reset any necessary cached properties (see #5009)
+		if ( this.offsetParentCssPosition === "fixed" ) {
+			this.offset.parent = this._getParentOffset();
+		}
+
+		//Compute the helpers position
+		this.position = this._generatePosition(event);
+		this.positionAbs = this._convertPositionTo("absolute");
+
+		//Call plugins and callbacks and use the resulting position if something is returned
+		if (!noPropagation) {
+			var ui = this._uiHash();
+			if(this._trigger("drag", event, ui) === false) {
+				this._mouseUp({});
+				return false;
+			}
+			this.position = ui.position;
+		}
+
+		if(!this.options.axis || this.options.axis !== "y") {
+			this.helper[0].style.left = this.position.left+"px";
+		}
+		if(!this.options.axis || this.options.axis !== "x") {
+			this.helper[0].style.top = this.position.top+"px";
+		}
+		if($.ui.ddmanager) {
+			$.ui.ddmanager.drag(this, event);
+		}
+
+		return false;
+	},
+
+	_mouseStop: function(event) {
+
+		//If we are using droppables, inform the manager about the drop
+		var that = this,
+			dropped = false;
+		if ($.ui.ddmanager && !this.options.dropBehaviour) {
+			dropped = $.ui.ddmanager.drop(this, event);
+		}
+
+		//if a drop comes from outside (a sortable)
+		if(this.dropped) {
+			dropped = this.dropped;
+			this.dropped = false;
+		}
+
+		//if the original element is no longer in the DOM don't bother to continue (see #8269)
+		if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) {
+			return false;
+		}
+
+		if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
+			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
+				if(that._trigger("stop", event) !== false) {
+					that._clear();
+				}
+			});
+		} else {
+			if(this._trigger("stop", event) !== false) {
+				this._clear();
+			}
+		}
+
+		return false;
+	},
+
+	_mouseUp: function(event) {
+		//Remove frame helpers
+		$("div.ui-draggable-iframeFix").each(function() {
+			this.parentNode.removeChild(this);
+		});
+
+		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
+		if( $.ui.ddmanager ) {
+			$.ui.ddmanager.dragStop(this, event);
+		}
+
+		return $.ui.mouse.prototype._mouseUp.call(this, event);
+	},
+
+	cancel: function() {
+
+		if(this.helper.is(".ui-draggable-dragging")) {
+			this._mouseUp({});
+		} else {
+			this._clear();
+		}
+
+		return this;
+
+	},
+
+	_getHandle: function(event) {
+		return this.options.handle ?
+			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
+			true;
+	},
+
+	_createHelper: function(event) {
+
+		var o = this.options,
+			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element);
+
+		if(!helper.parents("body").length) {
+			helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
+		}
+
+		if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
+			helper.css("position", "absolute");
+		}
+
+		return helper;
+
+	},
+
+	_adjustOffsetFromHelper: function(obj) {
+		if (typeof obj === "string") {
+			obj = obj.split(" ");
+		}
+		if ($.isArray(obj)) {
+			obj = {left: +obj[0], top: +obj[1] || 0};
+		}
+		if ("left" in obj) {
+			this.offset.click.left = obj.left + this.margins.left;
+		}
+		if ("right" in obj) {
+			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+		}
+		if ("top" in obj) {
+			this.offset.click.top = obj.top + this.margins.top;
+		}
+		if ("bottom" in obj) {
+			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+		}
+	},
+
+	_getParentOffset: function() {
+
+		//Get the offsetParent and cache its position
+		var po = this.offsetParent.offset();
+
+		// This is a special case where we need to modify a offset calculated on start, since the following happened:
+		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+		if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
+			po.left += this.scrollParent.scrollLeft();
+			po.top += this.scrollParent.scrollTop();
+		}
+
+		//This needs to be actually done for all browsers, since pageX/pageY includes this information
+		//Ugly IE fix
+		if((this.offsetParent[0] === document.body) ||
+			(this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
+			po = { top: 0, left: 0 };
+		}
+
+		return {
+			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+		};
+
+	},
+
+	_getRelativeOffset: function() {
+
+		if(this.cssPosition === "relative") {
+			var p = this.element.position();
+			return {
+				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+			};
+		} else {
+			return { top: 0, left: 0 };
+		}
+
+	},
+
+	_cacheMargins: function() {
+		this.margins = {
+			left: (parseInt(this.element.css("marginLeft"),10) || 0),
+			top: (parseInt(this.element.css("marginTop"),10) || 0),
+			right: (parseInt(this.element.css("marginRight"),10) || 0),
+			bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
+		};
+	},
+
+	_cacheHelperProportions: function() {
+		this.helperProportions = {
+			width: this.helper.outerWidth(),
+			height: this.helper.outerHeight()
+		};
+	},
+
+	_setContainment: function() {
+
+		var over, c, ce,
+			o = this.options;
+
+		if ( !o.containment ) {
+			this.containment = null;
+			return;
+		}
+
+		if ( o.containment === "window" ) {
+			this.containment = [
+				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
+				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
+				$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
+				$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
+			];
+			return;
+		}
+
+		if ( o.containment === "document") {
+			this.containment = [
+				0,
+				0,
+				$( document ).width() - this.helperProportions.width - this.margins.left,
+				( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
+			];
+			return;
+		}
+
+		if ( o.containment.constructor === Array ) {
+			this.containment = o.containment;
+			return;
+		}
+
+		if ( o.containment === "parent" ) {
+			o.containment = this.helper[ 0 ].parentNode;
+		}
+
+		c = $( o.containment );
+		ce = c[ 0 ];
+
+		if( !ce ) {
+			return;
+		}
+
+		over = c.css( "overflow" ) !== "hidden";
+
+		this.containment = [
+			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
+			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) ,
+			( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right,
+			( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top  - this.margins.bottom
+		];
+		this.relative_container = c;
+	},
+
+	_convertPositionTo: function(d, pos) {
+
+		if(!pos) {
+			pos = this.position;
+		}
+
+		var mod = d === "absolute" ? 1 : -1,
+			scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent;
+
+		//Cache the scroll
+		if (!this.offset.scroll) {
+			this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
+		}
+
+		return {
+			top: (
+				pos.top	+																// The absolute mouse position
+				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.top * mod -										// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * mod )
+			),
+			left: (
+				pos.left +																// The absolute mouse position
+				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod )
+			)
+		};
+
+	},
+
+	_generatePosition: function(event) {
+
+		var containment, co, top, left,
+			o = this.options,
+			scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent,
+			pageX = event.pageX,
+			pageY = event.pageY;
+
+		//Cache the scroll
+		if (!this.offset.scroll) {
+			this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
+		}
+
+		/*
+		 * - Position constraining -
+		 * Constrain the position to a mix of grid, containment.
+		 */
+
+		// If we are not dragging yet, we won't check for options
+		if ( this.originalPosition ) {
+			if ( this.containment ) {
+				if ( this.relative_container ){
+					co = this.relative_container.offset();
+					containment = [
+						this.containment[ 0 ] + co.left,
+						this.containment[ 1 ] + co.top,
+						this.containment[ 2 ] + co.left,
+						this.containment[ 3 ] + co.top
+					];
+				}
+				else {
+					containment = this.containment;
+				}
+
+				if(event.pageX - this.offset.click.left < containment[0]) {
+					pageX = containment[0] + this.offset.click.left;
+				}
+				if(event.pageY - this.offset.click.top < containment[1]) {
+					pageY = containment[1] + this.offset.click.top;
+				}
+				if(event.pageX - this.offset.click.left > containment[2]) {
+					pageX = containment[2] + this.offset.click.left;
+				}
+				if(event.pageY - this.offset.click.top > containment[3]) {
+					pageY = containment[3] + this.offset.click.top;
+				}
+			}
+
+			if(o.grid) {
+				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
+				top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
+				pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+				left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
+				pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+			}
+
+		}
+
+		return {
+			top: (
+				pageY -																	// The absolute mouse position
+				this.offset.click.top	-												// Click offset (relative to the element)
+				this.offset.relative.top -												// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
+				( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top )
+			),
+			left: (
+				pageX -																	// The absolute mouse position
+				this.offset.click.left -												// Click offset (relative to the element)
+				this.offset.relative.left -												// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
+				( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left )
+			)
+		};
+
+	},
+
+	_clear: function() {
+		this.helper.removeClass("ui-draggable-dragging");
+		if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
+			this.helper.remove();
+		}
+		this.helper = null;
+		this.cancelHelperRemoval = false;
+	},
+
+	// From now on bulk stuff - mainly helpers
+
+	_trigger: function(type, event, ui) {
+		ui = ui || this._uiHash();
+		$.ui.plugin.call(this, type, [event, ui]);
+		//The absolute position has to be recalculated after plugins
+		if(type === "drag") {
+			this.positionAbs = this._convertPositionTo("absolute");
+		}
+		return $.Widget.prototype._trigger.call(this, type, event, ui);
+	},
+
+	plugins: {},
+
+	_uiHash: function() {
+		return {
+			helper: this.helper,
+			position: this.position,
+			originalPosition: this.originalPosition,
+			offset: this.positionAbs
+		};
+	}
+
+});
+
+$.ui.plugin.add("draggable", "connectToSortable", {
+	start: function(event, ui) {
+
+		var inst = $(this).data("ui-draggable"), o = inst.options,
+			uiSortable = $.extend({}, ui, { item: inst.element });
+		inst.sortables = [];
+		$(o.connectToSortable).each(function() {
+			var sortable = $.data(this, "ui-sortable");
+			if (sortable && !sortable.options.disabled) {
+				inst.sortables.push({
+					instance: sortable,
+					shouldRevert: sortable.options.revert
+				});
+				sortable.refreshPositions();	// Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
+				sortable._trigger("activate", event, uiSortable);
+			}
+		});
+
+	},
+	stop: function(event, ui) {
+
+		//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
+		var inst = $(this).data("ui-draggable"),
+			uiSortable = $.extend({}, ui, { item: inst.element });
+
+		$.each(inst.sortables, function() {
+			if(this.instance.isOver) {
+
+				this.instance.isOver = 0;
+
+				inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
+				this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
+
+				//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
+				if(this.shouldRevert) {
+					this.instance.options.revert = this.shouldRevert;
+				}
+
+				//Trigger the stop of the sortable
+				this.instance._mouseStop(event);
+
+				this.instance.options.helper = this.instance.options._helper;
+
+				//If the helper has been the original item, restore properties in the sortable
+				if(inst.options.helper === "original") {
+					this.instance.currentItem.css({ top: "auto", left: "auto" });
+				}
+
+			} else {
+				this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
+				this.instance._trigger("deactivate", event, uiSortable);
+			}
+
+		});
+
+	},
+	drag: function(event, ui) {
+
+		var inst = $(this).data("ui-draggable"), that = this;
+
+		$.each(inst.sortables, function() {
+
+			var innermostIntersecting = false,
+				thisSortable = this;
+
+			//Copy over some variables to allow calling the sortable's native _intersectsWith
+			this.instance.positionAbs = inst.positionAbs;
+			this.instance.helperProportions = inst.helperProportions;
+			this.instance.offset.click = inst.offset.click;
+
+			if(this.instance._intersectsWith(this.instance.containerCache)) {
+				innermostIntersecting = true;
+				$.each(inst.sortables, function () {
+					this.instance.positionAbs = inst.positionAbs;
+					this.instance.helperProportions = inst.helperProportions;
+					this.instance.offset.click = inst.offset.click;
+					if (this !== thisSortable &&
+						this.instance._intersectsWith(this.instance.containerCache) &&
+						$.contains(thisSortable.instance.element[0], this.instance.element[0])
+					) {
+						innermostIntersecting = false;
+					}
+					return innermostIntersecting;
+				});
+			}
+
+
+			if(innermostIntersecting) {
+				//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
+				if(!this.instance.isOver) {
+
+					this.instance.isOver = 1;
+					//Now we fake the start of dragging for the sortable instance,
+					//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
+					//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
+					this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
+					this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
+					this.instance.options.helper = function() { return ui.helper[0]; };
+
+					event.target = this.instance.currentItem[0];
+					this.instance._mouseCapture(event, true);
+					this.instance._mouseStart(event, true, true);
+
+					//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
+					this.instance.offset.click.top = inst.offset.click.top;
+					this.instance.offset.click.left = inst.offset.click.left;
+					this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
+					this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
+
+					inst._trigger("toSortable", event);
+					inst.dropped = this.instance.element; //draggable revert needs that
+					//hack so receive/update callbacks work (mostly)
+					inst.currentItem = inst.element;
+					this.instance.fromOutside = inst;
+
+				}
+
+				//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
+				if(this.instance.currentItem) {
+					this.instance._mouseDrag(event);
+				}
+
+			} else {
+
+				//If it doesn't intersect with the sortable, and it intersected before,
+				//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
+				if(this.instance.isOver) {
+
+					this.instance.isOver = 0;
+					this.instance.cancelHelperRemoval = true;
+
+					//Prevent reverting on this forced stop
+					this.instance.options.revert = false;
+
+					// The out event needs to be triggered independently
+					this.instance._trigger("out", event, this.instance._uiHash(this.instance));
+
+					this.instance._mouseStop(event, true);
+					this.instance.options.helper = this.instance.options._helper;
+
+					//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
+					this.instance.currentItem.remove();
+					if(this.instance.placeholder) {
+						this.instance.placeholder.remove();
+					}
+
+					inst._trigger("fromSortable", event);
+					inst.dropped = false; //draggable revert needs that
+				}
+
+			}
+
+		});
+
+	}
+});
+
+$.ui.plugin.add("draggable", "cursor", {
+	start: function() {
+		var t = $("body"), o = $(this).data("ui-draggable").options;
+		if (t.css("cursor")) {
+			o._cursor = t.css("cursor");
+		}
+		t.css("cursor", o.cursor);
+	},
+	stop: function() {
+		var o = $(this).data("ui-draggable").options;
+		if (o._cursor) {
+			$("body").css("cursor", o._cursor);
+		}
+	}
+});
+
+$.ui.plugin.add("draggable", "opacity", {
+	start: function(event, ui) {
+		var t = $(ui.helper), o = $(this).data("ui-draggable").options;
+		if(t.css("opacity")) {
+			o._opacity = t.css("opacity");
+		}
+		t.css("opacity", o.opacity);
+	},
+	stop: function(event, ui) {
+		var o = $(this).data("ui-draggable").options;
+		if(o._opacity) {
+			$(ui.helper).css("opacity", o._opacity);
+		}
+	}
+});
+
+$.ui.plugin.add("draggable", "scroll", {
+	start: function() {
+		var i = $(this).data("ui-draggable");
+		if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
+			i.overflowOffset = i.scrollParent.offset();
+		}
+	},
+	drag: function( event ) {
+
+		var i = $(this).data("ui-draggable"), o = i.options, scrolled = false;
+
+		if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
+
+			if(!o.axis || o.axis !== "x") {
+				if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
+					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
+				} else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) {
+					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
+				}
+			}
+
+			if(!o.axis || o.axis !== "y") {
+				if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
+					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
+				} else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) {
+					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
+				}
+			}
+
+		} else {
+
+			if(!o.axis || o.axis !== "x") {
+				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
+					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+				} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
+					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+				}
+			}
+
+			if(!o.axis || o.axis !== "y") {
+				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
+					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+				} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
+					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+				}
+			}
+
+		}
+
+		if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
+			$.ui.ddmanager.prepareOffsets(i, event);
+		}
+
+	}
+});
+
+$.ui.plugin.add("draggable", "snap", {
+	start: function() {
+
+		var i = $(this).data("ui-draggable"),
+			o = i.options;
+
+		i.snapElements = [];
+
+		$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
+			var $t = $(this),
+				$o = $t.offset();
+			if(this !== i.element[0]) {
+				i.snapElements.push({
+					item: this,
+					width: $t.outerWidth(), height: $t.outerHeight(),
+					top: $o.top, left: $o.left
+				});
+			}
+		});
+
+	},
+	drag: function(event, ui) {
+
+		var ts, bs, ls, rs, l, r, t, b, i, first,
+			inst = $(this).data("ui-draggable"),
+			o = inst.options,
+			d = o.snapTolerance,
+			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
+			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
+
+		for (i = inst.snapElements.length - 1; i >= 0; i--){
+
+			l = inst.snapElements[i].left;
+			r = l + inst.snapElements[i].width;
+			t = inst.snapElements[i].top;
+			b = t + inst.snapElements[i].height;
+
+			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
+				if(inst.snapElements[i].snapping) {
+					(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+				}
+				inst.snapElements[i].snapping = false;
+				continue;
+			}
+
+			if(o.snapMode !== "inner") {
+				ts = Math.abs(t - y2) <= d;
+				bs = Math.abs(b - y1) <= d;
+				ls = Math.abs(l - x2) <= d;
+				rs = Math.abs(r - x1) <= d;
+				if(ts) {
+					ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+				}
+				if(bs) {
+					ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
+				}
+				if(ls) {
+					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
+				}
+				if(rs) {
+					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
+				}
+			}
+
+			first = (ts || bs || ls || rs);
+
+			if(o.snapMode !== "outer") {
+				ts = Math.abs(t - y1) <= d;
+				bs = Math.abs(b - y2) <= d;
+				ls = Math.abs(l - x1) <= d;
+				rs = Math.abs(r - x2) <= d;
+				if(ts) {
+					ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
+				}
+				if(bs) {
+					ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+				}
+				if(ls) {
+					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
+				}
+				if(rs) {
+					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
+				}
+			}
+
+			if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
+				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+			}
+			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
+
+		}
+
+	}
+});
+
+$.ui.plugin.add("draggable", "stack", {
+	start: function() {
+		var min,
+			o = this.data("ui-draggable").options,
+			group = $.makeArray($(o.stack)).sort(function(a,b) {
+				return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
+			});
+
+		if (!group.length) { return; }
+
+		min = parseInt($(group[0]).css("zIndex"), 10) || 0;
+		$(group).each(function(i) {
+			$(this).css("zIndex", min + i);
+		});
+		this.css("zIndex", (min + group.length));
+	}
+});
+
+$.ui.plugin.add("draggable", "zIndex", {
+	start: function(event, ui) {
+		var t = $(ui.helper), o = $(this).data("ui-draggable").options;
+		if(t.css("zIndex")) {
+			o._zIndex = t.css("zIndex");
+		}
+		t.css("zIndex", o.zIndex);
+	},
+	stop: function(event, ui) {
+		var o = $(this).data("ui-draggable").options;
+		if(o._zIndex) {
+			$(ui.helper).css("zIndex", o._zIndex);
+		}
+	}
+});
+
+})(jQuery);
+(function( $, undefined ) {
+
+function isOverAxis( x, reference, size ) {
+	return ( x > reference ) && ( x < ( reference + size ) );
+}
+
+$.widget("ui.droppable", {
+	version: "1.10.4",
+	widgetEventPrefix: "drop",
+	options: {
+		accept: "*",
+		activeClass: false,
+		addClasses: true,
+		greedy: false,
+		hoverClass: false,
+		scope: "default",
+		tolerance: "intersect",
+
+		// callbacks
+		activate: null,
+		deactivate: null,
+		drop: null,
+		out: null,
+		over: null
+	},
+	_create: function() {
+
+		var proportions,
+			o = this.options,
+			accept = o.accept;
+
+		this.isover = false;
+		this.isout = true;
+
+		this.accept = $.isFunction(accept) ? accept : function(d) {
+			return d.is(accept);
+		};
+
+		this.proportions = function( /* valueToWrite */ ) {
+			if ( arguments.length ) {
+				// Store the droppable's proportions
+				proportions = arguments[ 0 ];
+			} else {
+				// Retrieve or derive the droppable's proportions
+				return proportions ?
+					proportions :
+					proportions = {
+						width: this.element[ 0 ].offsetWidth,
+						height: this.element[ 0 ].offsetHeight
+					};
+			}
+		};
+
+		// Add the reference and positions to the manager
+		$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
+		$.ui.ddmanager.droppables[o.scope].push(this);
+
+		(o.addClasses && this.element.addClass("ui-droppable"));
+
+	},
+
+	_destroy: function() {
+		var i = 0,
+			drop = $.ui.ddmanager.droppables[this.options.scope];
+
+		for ( ; i < drop.length; i++ ) {
+			if ( drop[i] === this ) {
+				drop.splice(i, 1);
+			}
+		}
+
+		this.element.removeClass("ui-droppable ui-droppable-disabled");
+	},
+
+	_setOption: function(key, value) {
+
+		if(key === "accept") {
+			this.accept = $.isFunction(value) ? value : function(d) {
+				return d.is(value);
+			};
+		}
+		$.Widget.prototype._setOption.apply(this, arguments);
+	},
+
+	_activate: function(event) {
+		var draggable = $.ui.ddmanager.current;
+		if(this.options.activeClass) {
+			this.element.addClass(this.options.activeClass);
+		}
+		if(draggable){
+			this._trigger("activate", event, this.ui(draggable));
+		}
+	},
+
+	_deactivate: function(event) {
+		var draggable = $.ui.ddmanager.current;
+		if(this.options.activeClass) {
+			this.element.removeClass(this.options.activeClass);
+		}
+		if(draggable){
+			this._trigger("deactivate", event, this.ui(draggable));
+		}
+	},
+
+	_over: function(event) {
+
+		var draggable = $.ui.ddmanager.current;
+
+		// Bail if draggable and droppable are same element
+		if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
+			return;
+		}
+
+		if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+			if(this.options.hoverClass) {
+				this.element.addClass(this.options.hoverClass);
+			}
+			this._trigger("over", event, this.ui(draggable));
+		}
+
+	},
+
+	_out: function(event) {
+
+		var draggable = $.ui.ddmanager.current;
+
+		// Bail if draggable and droppable are same element
+		if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
+			return;
+		}
+
+		if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+			if(this.options.hoverClass) {
+				this.element.removeClass(this.options.hoverClass);
+			}
+			this._trigger("out", event, this.ui(draggable));
+		}
+
+	},
+
+	_drop: function(event,custom) {
+
+		var draggable = custom || $.ui.ddmanager.current,
+			childrenIntersection = false;
+
+		// Bail if draggable and droppable are same element
+		if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
+			return false;
+		}
+
+		this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() {
+			var inst = $.data(this, "ui-droppable");
+			if(
+				inst.options.greedy &&
+				!inst.options.disabled &&
+				inst.options.scope === draggable.options.scope &&
+				inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) &&
+				$.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
+			) { childrenIntersection = true; return false; }
+		});
+		if(childrenIntersection) {
+			return false;
+		}
+
+		if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+			if(this.options.activeClass) {
+				this.element.removeClass(this.options.activeClass);
+			}
+			if(this.options.hoverClass) {
+				this.element.removeClass(this.options.hoverClass);
+			}
+			this._trigger("drop", event, this.ui(draggable));
+			return this.element;
+		}
+
+		return false;
+
+	},
+
+	ui: function(c) {
+		return {
+			draggable: (c.currentItem || c.element),
+			helper: c.helper,
+			position: c.position,
+			offset: c.positionAbs
+		};
+	}
+
+});
+
+$.ui.intersect = function(draggable, droppable, toleranceMode) {
+
+	if (!droppable.offset) {
+		return false;
+	}
+
+	var draggableLeft, draggableTop,
+		x1 = (draggable.positionAbs || draggable.position.absolute).left,
+		y1 = (draggable.positionAbs || draggable.position.absolute).top,
+		x2 = x1 + draggable.helperProportions.width,
+		y2 = y1 + draggable.helperProportions.height,
+		l = droppable.offset.left,
+		t = droppable.offset.top,
+		r = l + droppable.proportions().width,
+		b = t + droppable.proportions().height;
+
+	switch (toleranceMode) {
+		case "fit":
+			return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
+		case "intersect":
+			return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
+				x2 - (draggable.helperProportions.width / 2) < r && // Left Half
+				t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
+				y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
+		case "pointer":
+			draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left);
+			draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top);
+			return isOverAxis( draggableTop, t, droppable.proportions().height ) && isOverAxis( draggableLeft, l, droppable.proportions().width );
+		case "touch":
+			return (
+				(y1 >= t && y1 <= b) ||	// Top edge touching
+				(y2 >= t && y2 <= b) ||	// Bottom edge touching
+				(y1 < t && y2 > b)		// Surrounded vertically
+			) && (
+				(x1 >= l && x1 <= r) ||	// Left edge touching
+				(x2 >= l && x2 <= r) ||	// Right edge touching
+				(x1 < l && x2 > r)		// Surrounded horizontally
+			);
+		default:
+			return false;
+		}
+
+};
+
+/*
+	This manager tracks offsets of draggables and droppables
+*/
+$.ui.ddmanager = {
+	current: null,
+	droppables: { "default": [] },
+	prepareOffsets: function(t, event) {
+
+		var i, j,
+			m = $.ui.ddmanager.droppables[t.options.scope] || [],
+			type = event ? event.type : null, // workaround for #2317
+			list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack();
+
+		droppablesLoop: for (i = 0; i < m.length; i++) {
+
+			//No disabled and non-accepted
+			if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) {
+				continue;
+			}
+
+			// Filter out elements in the current dragged item
+			for (j=0; j < list.length; j++) {
+				if(list[j] === m[i].element[0]) {
+					m[i].proportions().height = 0;
+					continue droppablesLoop;
+				}
+			}
+
+			m[i].visible = m[i].element.css("display") !== "none";
+			if(!m[i].visible) {
+				continue;
+			}
+
+			//Activate the droppable if used directly from draggables
+			if(type === "mousedown") {
+				m[i]._activate.call(m[i], event);
+			}
+
+			m[ i ].offset = m[ i ].element.offset();
+			m[ i ].proportions({ width: m[ i ].element[ 0 ].offsetWidth, height: m[ i ].element[ 0 ].offsetHeight });
+
+		}
+
+	},
+	drop: function(draggable, event) {
+
+		var dropped = false;
+		// Create a copy of the droppables in case the list changes during the drop (#9116)
+		$.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() {
+
+			if(!this.options) {
+				return;
+			}
+			if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) {
+				dropped = this._drop.call(this, event) || dropped;
+			}
+
+			if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+				this.isout = true;
+				this.isover = false;
+				this._deactivate.call(this, event);
+			}
+
+		});
+		return dropped;
+
+	},
+	dragStart: function( draggable, event ) {
+		//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
+		draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
+			if( !draggable.options.refreshPositions ) {
+				$.ui.ddmanager.prepareOffsets( draggable, event );
+			}
+		});
+	},
+	drag: function(draggable, event) {
+
+		//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
+		if(draggable.options.refreshPositions) {
+			$.ui.ddmanager.prepareOffsets(draggable, event);
+		}
+
+		//Run through all droppables and check their positions based on specific tolerance options
+		$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
+
+			if(this.options.disabled || this.greedyChild || !this.visible) {
+				return;
+			}
+
+			var parentInstance, scope, parent,
+				intersects = $.ui.intersect(draggable, this, this.options.tolerance),
+				c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
+			if(!c) {
+				return;
+			}
+
+			if (this.options.greedy) {
+				// find droppable parents with same scope
+				scope = this.options.scope;
+				parent = this.element.parents(":data(ui-droppable)").filter(function () {
+					return $.data(this, "ui-droppable").options.scope === scope;
+				});
+
+				if (parent.length) {
+					parentInstance = $.data(parent[0], "ui-droppable");
+					parentInstance.greedyChild = (c === "isover");
+				}
+			}
+
+			// we just moved into a greedy child
+			if (parentInstance && c === "isover") {
+				parentInstance.isover = false;
+				parentInstance.isout = true;
+				parentInstance._out.call(parentInstance, event);
+			}
+
+			this[c] = true;
+			this[c === "isout" ? "isover" : "isout"] = false;
+			this[c === "isover" ? "_over" : "_out"].call(this, event);
+
+			// we just moved out of a greedy child
+			if (parentInstance && c === "isout") {
+				parentInstance.isout = false;
+				parentInstance.isover = true;
+				parentInstance._over.call(parentInstance, event);
+			}
+		});
+
+	},
+	dragStop: function( draggable, event ) {
+		draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
+		//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
+		if( !draggable.options.refreshPositions ) {
+			$.ui.ddmanager.prepareOffsets( draggable, event );
+		}
+	}
+};
+
+})(jQuery);
+(function( $, undefined ) {
+
+function isOverAxis( x, reference, size ) {
+	return ( x > reference ) && ( x < ( reference + size ) );
+}
+
+function isFloating(item) {
+	return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
+}
+
+$.widget("ui.sortable", $.ui.mouse, {
+	version: "1.10.4",
+	widgetEventPrefix: "sort",
+	ready: false,
+	options: {
+		appendTo: "parent",
+		axis: false,
+		connectWith: false,
+		containment: false,
+		cursor: "auto",
+		cursorAt: false,
+		dropOnEmpty: true,
+		forcePlaceholderSize: false,
+		forceHelperSize: false,
+		grid: false,
+		handle: false,
+		helper: "original",
+		items: "> *",
+		opacity: false,
+		placeholder: false,
+		revert: false,
+		scroll: true,
+		scrollSensitivity: 20,
+		scrollSpeed: 20,
+		scope: "default",
+		tolerance: "intersect",
+		zIndex: 1000,
+
+		// callbacks
+		activate: null,
+		beforeStop: null,
+		change: null,
+		deactivate: null,
+		out: null,
+		over: null,
+		receive: null,
+		remove: null,
+		sort: null,
+		start: null,
+		stop: null,
+		update: null
+	},
+	_create: function() {
+
+		var o = this.options;
+		this.containerCache = {};
+		this.element.addClass("ui-sortable");
+
+		//Get the items
+		this.refresh();
+
+		//Let's determine if the items are being displayed horizontally
+		this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false;
+
+		//Let's determine the parent's offset
+		this.offset = this.element.offset();
+
+		//Initialize mouse events for interaction
+		this._mouseInit();
+
+		//We're ready to go
+		this.ready = true;
+
+	},
+
+	_destroy: function() {
+		this.element
+			.removeClass("ui-sortable ui-sortable-disabled");
+		this._mouseDestroy();
+
+		for ( var i = this.items.length - 1; i >= 0; i-- ) {
+			this.items[i].item.removeData(this.widgetName + "-item");
+		}
+
+		return this;
+	},
+
+	_setOption: function(key, value){
+		if ( key === "disabled" ) {
+			this.options[ key ] = value;
+
+			this.widget().toggleClass( "ui-sortable-disabled", !!value );
+		} else {
+			// Don't call widget base _setOption for disable as it adds ui-state-disabled class
+			$.Widget.prototype._setOption.apply(this, arguments);
+		}
+	},
+
+	_mouseCapture: function(event, overrideHandle) {
+		var currentItem = null,
+			validHandle = false,
+			that = this;
+
+		if (this.reverting) {
+			return false;
+		}
+
+		if(this.options.disabled || this.options.type === "static") {
+			return false;
+		}
+
+		//We have to refresh the items data once first
+		this._refreshItems(event);
+
+		//Find out if the clicked node (or one of its parents) is a actual item in this.items
+		$(event.target).parents().each(function() {
+			if($.data(this, that.widgetName + "-item") === that) {
+				currentItem = $(this);
+				return false;
+			}
+		});
+		if($.data(event.target, that.widgetName + "-item") === that) {
+			currentItem = $(event.target);
+		}
+
+		if(!currentItem) {
+			return false;
+		}
+		if(this.options.handle && !overrideHandle) {
+			$(this.options.handle, currentItem).find("*").addBack().each(function() {
+				if(this === event.target) {
+					validHandle = true;
+				}
+			});
+			if(!validHandle) {
+				return false;
+			}
+		}
+
+		this.currentItem = currentItem;
+		this._removeCurrentsFromItems();
+		return true;
+
+	},
+
+	_mouseStart: function(event, overrideHandle, noActivation) {
+
+		var i, body,
+			o = this.options;
+
+		this.currentContainer = this;
+
+		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
+		this.refreshPositions();
+
+		//Create and append the visible helper
+		this.helper = this._createHelper(event);
+
+		//Cache the helper size
+		this._cacheHelperProportions();
+
+		/*
+		 * - Position generation -
+		 * This block generates everything position related - it's the core of draggables.
+		 */
+
+		//Cache the margins of the original element
+		this._cacheMargins();
+
+		//Get the next scrolling parent
+		this.scrollParent = this.helper.scrollParent();
+
+		//The element's absolute position on the page minus margins
+		this.offset = this.currentItem.offset();
+		this.offset = {
+			top: this.offset.top - this.margins.top,
+			left: this.offset.left - this.margins.left
+		};
+
+		$.extend(this.offset, {
+			click: { //Where the click happened, relative to the element
+				left: event.pageX - this.offset.left,
+				top: event.pageY - this.offset.top
+			},
+			parent: this._getParentOffset(),
+			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+		});
+
+		// Only after we got the offset, we can change the helper's position to absolute
+		// TODO: Still need to figure out a way to make relative sorting possible
+		this.helper.css("position", "absolute");
+		this.cssPosition = this.helper.css("position");
+
+		//Generate the original position
+		this.originalPosition = this._generatePosition(event);
+		this.originalPageX = event.pageX;
+		this.originalPageY = event.pageY;
+
+		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
+		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+		//Cache the former DOM position
+		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
+
+		//If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
+		if(this.helper[0] !== this.currentItem[0]) {
+			this.currentItem.hide();
+		}
+
+		//Create the placeholder
+		this._createPlaceholder();
+
+		//Set a containment if given in the options
+		if(o.containment) {
+			this._setContainment();
+		}
+
+		if( o.cursor && o.cursor !== "auto" ) { // cursor option
+			body = this.document.find( "body" );
+
+			// support: IE
+			this.storedCursor = body.css( "cursor" );
+			body.css( "cursor", o.cursor );
+
+			this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
+		}
+
+		if(o.opacity) { // opacity option
+			if (this.helper.css("opacity")) {
+				this._storedOpacity = this.helper.css("opacity");
+			}
+			this.helper.css("opacity", o.opacity);
+		}
+
+		if(o.zIndex) { // zIndex option
+			if (this.helper.css("zIndex")) {
+				this._storedZIndex = this.helper.css("zIndex");
+			}
+			this.helper.css("zIndex", o.zIndex);
+		}
+
+		//Prepare scrolling
+		if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
+			this.overflowOffset = this.scrollParent.offset();
+		}
+
+		//Call callbacks
+		this._trigger("start", event, this._uiHash());
+
+		//Recache the helper size
+		if(!this._preserveHelperProportions) {
+			this._cacheHelperProportions();
+		}
+
+
+		//Post "activate" events to possible containers
+		if( !noActivation ) {
+			for ( i = this.containers.length - 1; i >= 0; i-- ) {
+				this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
+			}
+		}
+
+		//Prepare possible droppables
+		if($.ui.ddmanager) {
+			$.ui.ddmanager.current = this;
+		}
+
+		if ($.ui.ddmanager && !o.dropBehaviour) {
+			$.ui.ddmanager.prepareOffsets(this, event);
+		}
+
+		this.dragging = true;
+
+		this.helper.addClass("ui-sortable-helper");
+		this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+		return true;
+
+	},
+
+	_mouseDrag: function(event) {
+		var i, item, itemElement, intersection,
+			o = this.options,
+			scrolled = false;
+
+		//Compute the helpers position
+		this.position = this._generatePosition(event);
+		this.positionAbs = this._convertPositionTo("absolute");
+
+		if (!this.lastPositionAbs) {
+			this.lastPositionAbs = this.positionAbs;
+		}
+
+		//Do scrolling
+		if(this.options.scroll) {
+			if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
+
+				if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
+					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
+				} else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
+					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
+				}
+
+				if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
+					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
+				} else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
+					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
+				}
+
+			} else {
+
+				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
+					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+				} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
+					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+				}
+
+				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
+					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+				} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
+					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+				}
+
+			}
+
+			if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
+				$.ui.ddmanager.prepareOffsets(this, event);
+			}
+		}
+
+		//Regenerate the absolute position used for position checks
+		this.positionAbs = this._convertPositionTo("absolute");
+
+		//Set the helper position
+		if(!this.options.axis || this.options.axis !== "y") {
+			this.helper[0].style.left = this.position.left+"px";
+		}
+		if(!this.options.axis || this.options.axis !== "x") {
+			this.helper[0].style.top = this.position.top+"px";
+		}
+
+		//Rearrange
+		for (i = this.items.length - 1; i >= 0; i--) {
+
+			//Cache variables and intersection, continue if no intersection
+			item = this.items[i];
+			itemElement = item.item[0];
+			intersection = this._intersectsWithPointer(item);
+			if (!intersection) {
+				continue;
+			}
+
+			// Only put the placeholder inside the current Container, skip all
+			// items from other containers. This works because when moving
+			// an item from one container to another the
+			// currentContainer is switched before the placeholder is moved.
+			//
+			// Without this, moving items in "sub-sortables" can cause
+			// the placeholder to jitter beetween the outer and inner container.
+			if (item.instance !== this.currentContainer) {
+				continue;
+			}
+
+			// cannot intersect with itself
+			// no useless actions that have been done before
+			// no action if the item moved is the parent of the item checked
+			if (itemElement !== this.currentItem[0] &&
+				this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
+				!$.contains(this.placeholder[0], itemElement) &&
+				(this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
+			) {
+
+				this.direction = intersection === 1 ? "down" : "up";
+
+				if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
+					this._rearrange(event, item);
+				} else {
+					break;
+				}
+
+				this._trigger("change", event, this._uiHash());
+				break;
+			}
+		}
+
+		//Post events to containers
+		this._contactContainers(event);
+
+		//Interconnect with droppables
+		if($.ui.ddmanager) {
+			$.ui.ddmanager.drag(this, event);
+		}
+
+		//Call callbacks
+		this._trigger("sort", event, this._uiHash());
+
+		this.lastPositionAbs = this.positionAbs;
+		return false;
+
+	},
+
+	_mouseStop: function(event, noPropagation) {
+
+		if(!event) {
+			return;
+		}
+
+		//If we are using droppables, inform the manager about the drop
+		if ($.ui.ddmanager && !this.options.dropBehaviour) {
+			$.ui.ddmanager.drop(this, event);
+		}
+
+		if(this.options.revert) {
+			var that = this,
+				cur = this.placeholder.offset(),
+				axis = this.options.axis,
+				animation = {};
+
+			if ( !axis || axis === "x" ) {
+				animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft);
+			}
+			if ( !axis || axis === "y" ) {
+				animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop);
+			}
+			this.reverting = true;
+			$(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
+				that._clear(event);
+			});
+		} else {
+			this._clear(event, noPropagation);
+		}
+
+		return false;
+
+	},
+
+	cancel: function() {
+
+		if(this.dragging) {
+
+			this._mouseUp({ target: null });
+
+			if(this.options.helper === "original") {
+				this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+			} else {
+				this.currentItem.show();
+			}
+
+			//Post deactivating events to containers
+			for (var i = this.containers.length - 1; i >= 0; i--){
+				this.containers[i]._trigger("deactivate", null, this._uiHash(this));
+				if(this.containers[i].containerCache.over) {
+					this.containers[i]._trigger("out", null, this._uiHash(this));
+					this.containers[i].containerCache.over = 0;
+				}
+			}
+
+		}
+
+		if (this.placeholder) {
+			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+			if(this.placeholder[0].parentNode) {
+				this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+			}
+			if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
+				this.helper.remove();
+			}
+
+			$.extend(this, {
+				helper: null,
+				dragging: false,
+				reverting: false,
+				_noFinalSort: null
+			});
+
+			if(this.domPosition.prev) {
+				$(this.domPosition.prev).after(this.currentItem);
+			} else {
+				$(this.domPosition.parent).prepend(this.currentItem);
+			}
+		}
+
+		return this;
+
+	},
+
+	serialize: function(o) {
+
+		var items = this._getItemsAsjQuery(o && o.connected),
+			str = [];
+		o = o || {};
+
+		$(items).each(function() {
+			var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
+			if (res) {
+				str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
+			}
+		});
+
+		if(!str.length && o.key) {
+			str.push(o.key + "=");
+		}
+
+		return str.join("&");
+
+	},
+
+	toArray: function(o) {
+
+		var items = this._getItemsAsjQuery(o && o.connected),
+			ret = [];
+
+		o = o || {};
+
+		items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
+		return ret;
+
+	},
+
+	/* Be careful with the following core functions */
+	_intersectsWith: function(item) {
+
+		var x1 = this.positionAbs.left,
+			x2 = x1 + this.helperProportions.width,
+			y1 = this.positionAbs.top,
+			y2 = y1 + this.helperProportions.height,
+			l = item.left,
+			r = l + item.width,
+			t = item.top,
+			b = t + item.height,
+			dyClick = this.offset.click.top,
+			dxClick = this.offset.click.left,
+			isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
+			isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
+			isOverElement = isOverElementHeight && isOverElementWidth;
+
+		if ( this.options.tolerance === "pointer" ||
+			this.options.forcePointerForContainers ||
+			(this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
+		) {
+			return isOverElement;
+		} else {
+
+			return (l < x1 + (this.helperProportions.width / 2) && // Right Half
+				x2 - (this.helperProportions.width / 2) < r && // Left Half
+				t < y1 + (this.helperProportions.height / 2) && // Bottom Half
+				y2 - (this.helperProportions.height / 2) < b ); // Top Half
+
+		}
+	},
+
+	_intersectsWithPointer: function(item) {
+
+		var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
+			isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
+			isOverElement = isOverElementHeight && isOverElementWidth,
+			verticalDirection = this._getDragVerticalDirection(),
+			horizontalDirection = this._getDragHorizontalDirection();
+
+		if (!isOverElement) {
+			return false;
+		}
+
+		return this.floating ?
+			( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
+			: ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );
+
+	},
+
+	_intersectsWithSides: function(item) {
+
+		var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
+			isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
+			verticalDirection = this._getDragVerticalDirection(),
+			horizontalDirection = this._getDragHorizontalDirection();
+
+		if (this.floating && horizontalDirection) {
+			return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
+		} else {
+			return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
+		}
+
+	},
+
+	_getDragVerticalDirection: function() {
+		var delta = this.positionAbs.top - this.lastPositionAbs.top;
+		return delta !== 0 && (delta > 0 ? "down" : "up");
+	},
+
+	_getDragHorizontalDirection: function() {
+		var delta = this.positionAbs.left - this.lastPositionAbs.left;
+		return delta !== 0 && (delta > 0 ? "right" : "left");
+	},
+
+	refresh: function(event) {
+		this._refreshItems(event);
+		this.refreshPositions();
+		return this;
+	},
+
+	_connectWith: function() {
+		var options = this.options;
+		return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
+	},
+
+	_getItemsAsjQuery: function(connected) {
+
+		var i, j, cur, inst,
+			items = [],
+			queries = [],
+			connectWith = this._connectWith();
+
+		if(connectWith && connected) {
+			for (i = connectWith.length - 1; i >= 0; i--){
+				cur = $(connectWith[i]);
+				for ( j = cur.length - 1; j >= 0; j--){
+					inst = $.data(cur[j], this.widgetFullName);
+					if(inst && inst !== this && !inst.options.disabled) {
+						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
+					}
+				}
+			}
+		}
+
+		queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);
+
+		function addItems() {
+			items.push( this );
+		}
+		for (i = queries.length - 1; i >= 0; i--){
+			queries[i][0].each( addItems );
+		}
+
+		return $(items);
+
+	},
+
+	_removeCurrentsFromItems: function() {
+
+		var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
+
+		this.items = $.grep(this.items, function (item) {
+			for (var j=0; j < list.length; j++) {
+				if(list[j] === item.item[0]) {
+					return false;
+				}
+			}
+			return true;
+		});
+
+	},
+
+	_refreshItems: function(event) {
+
+		this.items = [];
+		this.containers = [this];
+
+		var i, j, cur, inst, targetData, _queries, item, queriesLength,
+			items = this.items,
+			queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
+			connectWith = this._connectWith();
+
+		if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
+			for (i = connectWith.length - 1; i >= 0; i--){
+				cur = $(connectWith[i]);
+				for (j = cur.length - 1; j >= 0; j--){
+					inst = $.data(cur[j], this.widgetFullName);
+					if(inst && inst !== this && !inst.options.disabled) {
+						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
+						this.containers.push(inst);
+					}
+				}
+			}
+		}
+
+		for (i = queries.length - 1; i >= 0; i--) {
+			targetData = queries[i][1];
+			_queries = queries[i][0];
+
+			for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
+				item = $(_queries[j]);
+
+				item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)
+
+				items.push({
+					item: item,
+					instance: targetData,
+					width: 0, height: 0,
+					left: 0, top: 0
+				});
+			}
+		}
+
+	},
+
+	refreshPositions: function(fast) {
+
+		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
+		if(this.offsetParent && this.helper) {
+			this.offset.parent = this._getParentOffset();
+		}
+
+		var i, item, t, p;
+
+		for (i = this.items.length - 1; i >= 0; i--){
+			item = this.items[i];
+
+			//We ignore calculating positions of all connected containers when we're not over them
+			if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
+				continue;
+			}
+
+			t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
+
+			if (!fast) {
+				item.width = t.outerWidth();
+				item.height = t.outerHeight();
+			}
+
+			p = t.offset();
+			item.left = p.left;
+			item.top = p.top;
+		}
+
+		if(this.options.custom && this.options.custom.refreshContainers) {
+			this.options.custom.refreshContainers.call(this);
+		} else {
+			for (i = this.containers.length - 1; i >= 0; i--){
+				p = this.containers[i].element.offset();
+				this.containers[i].containerCache.left = p.left;
+				this.containers[i].containerCache.top = p.top;
+				this.containers[i].containerCache.width	= this.containers[i].element.outerWidth();
+				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
+			}
+		}
+
+		return this;
+	},
+
+	_createPlaceholder: function(that) {
+		that = that || this;
+		var className,
+			o = that.options;
+
+		if(!o.placeholder || o.placeholder.constructor === String) {
+			className = o.placeholder;
+			o.placeholder = {
+				element: function() {
+
+					var nodeName = that.currentItem[0].nodeName.toLowerCase(),
+						element = $( "<" + nodeName + ">", that.document[0] )
+							.addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
+							.removeClass("ui-sortable-helper");
+
+					if ( nodeName === "tr" ) {
+						that.currentItem.children().each(function() {
+							$( "<td>&#160;</td>", that.document[0] )
+								.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
+								.appendTo( element );
+						});
+					} else if ( nodeName === "img" ) {
+						element.attr( "src", that.currentItem.attr( "src" ) );
+					}
+
+					if ( !className ) {
+						element.css( "visibility", "hidden" );
+					}
+
+					return element;
+				},
+				update: function(container, p) {
+
+					// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
+					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
+					if(className && !o.forcePlaceholderSize) {
+						return;
+					}
+
+					//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
+					if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
+					if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
+				}
+			};
+		}
+
+		//Create the placeholder
+		that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
+
+		//Append it after the actual current item
+		that.currentItem.after(that.placeholder);
+
+		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
+		o.placeholder.update(that, that.placeholder);
+
+	},
+
+	_contactContainers: function(event) {
+		var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating,
+			innermostContainer = null,
+			innermostIndex = null;
+
+		// get innermost container that intersects with item
+		for (i = this.containers.length - 1; i >= 0; i--) {
+
+			// never consider a container that's located within the item itself
+			if($.contains(this.currentItem[0], this.containers[i].element[0])) {
+				continue;
+			}
+
+			if(this._intersectsWith(this.containers[i].containerCache)) {
+
+				// if we've already found a container and it's more "inner" than this, then continue
+				if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
+					continue;
+				}
+
+				innermostContainer = this.containers[i];
+				innermostIndex = i;
+
+			} else {
+				// container doesn't intersect. trigger "out" event if necessary
+				if(this.containers[i].containerCache.over) {
+					this.containers[i]._trigger("out", event, this._uiHash(this));
+					this.containers[i].containerCache.over = 0;
+				}
+			}
+
+		}
+
+		// if no intersecting containers found, return
+		if(!innermostContainer) {
+			return;
+		}
+
+		// move the item into the container if it's not there already
+		if(this.containers.length === 1) {
+			if (!this.containers[innermostIndex].containerCache.over) {
+				this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+				this.containers[innermostIndex].containerCache.over = 1;
+			}
+		} else {
+
+			//When entering a new container, we will find the item with the least distance and append our item near it
+			dist = 10000;
+			itemWithLeastDistance = null;
+			floating = innermostContainer.floating || isFloating(this.currentItem);
+			posProperty = floating ? "left" : "top";
+			sizeProperty = floating ? "width" : "height";
+			base = this.positionAbs[posProperty] + this.offset.click[posProperty];
+			for (j = this.items.length - 1; j >= 0; j--) {
+				if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
+					continue;
+				}
+				if(this.items[j].item[0] === this.currentItem[0]) {
+					continue;
+				}
+				if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) {
+					continue;
+				}
+				cur = this.items[j].item.offset()[posProperty];
+				nearBottom = false;
+				if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
+					nearBottom = true;
+					cur += this.items[j][sizeProperty];
+				}
+
+				if(Math.abs(cur - base) < dist) {
+					dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
+					this.direction = nearBottom ? "up": "down";
+				}
+			}
+
+			//Check if dropOnEmpty is enabled
+			if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
+				return;
+			}
+
+			if(this.currentContainer === this.containers[innermostIndex]) {
+				return;
+			}
+
+			itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
+			this._trigger("change", event, this._uiHash());
+			this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
+			this.currentContainer = this.containers[innermostIndex];
+
+			//Update the placeholder
+			this.options.placeholder.update(this.currentContainer, this.placeholder);
+
+			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+			this.containers[innermostIndex].containerCache.over = 1;
+		}
+
+
+	},
+
+	_createHelper: function(event) {
+
+		var o = this.options,
+			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);
+
+		//Add the helper to the DOM if that didn't happen already
+		if(!helper.parents("body").length) {
+			$(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
+		}
+
+		if(helper[0] === this.currentItem[0]) {
+			this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
+		}
+
+		if(!helper[0].style.width || o.forceHelperSize) {
+			helper.width(this.currentItem.width());
+		}
+		if(!helper[0].style.height || o.forceHelperSize) {
+			helper.height(this.currentItem.height());
+		}
+
+		return helper;
+
+	},
+
+	_adjustOffsetFromHelper: function(obj) {
+		if (typeof obj === "string") {
+			obj = obj.split(" ");
+		}
+		if ($.isArray(obj)) {
+			obj = {left: +obj[0], top: +obj[1] || 0};
+		}
+		if ("left" in obj) {
+			this.offset.click.left = obj.left + this.margins.left;
+		}
+		if ("right" in obj) {
+			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+		}
+		if ("top" in obj) {
+			this.offset.click.top = obj.top + this.margins.top;
+		}
+		if ("bottom" in obj) {
+			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+		}
+	},
+
+	_getParentOffset: function() {
+
+
+		//Get the offsetParent and cache its position
+		this.offsetParent = this.helper.offsetParent();
+		var po = this.offsetParent.offset();
+
+		// This is a special case where we need to modify a offset calculated on start, since the following happened:
+		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+		if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
+			po.left += this.scrollParent.scrollLeft();
+			po.top += this.scrollParent.scrollTop();
+		}
+
+		// This needs to be actually done for all browsers, since pageX/pageY includes this information
+		// with an ugly IE fix
+		if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
+			po = { top: 0, left: 0 };
+		}
+
+		return {
+			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+		};
+
+	},
+
+	_getRelativeOffset: function() {
+
+		if(this.cssPosition === "relative") {
+			var p = this.currentItem.position();
+			return {
+				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+			};
+		} else {
+			return { top: 0, left: 0 };
+		}
+
+	},
+
+	_cacheMargins: function() {
+		this.margins = {
+			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
+			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
+		};
+	},
+
+	_cacheHelperProportions: function() {
+		this.helperProportions = {
+			width: this.helper.outerWidth(),
+			height: this.helper.outerHeight()
+		};
+	},
+
+	_setContainment: function() {
+
+		var ce, co, over,
+			o = this.options;
+		if(o.containment === "parent") {
+			o.containment = this.helper[0].parentNode;
+		}
+		if(o.containment === "document" || o.containment === "window") {
+			this.containment = [
+				0 - this.offset.relative.left - this.offset.parent.left,
+				0 - this.offset.relative.top - this.offset.parent.top,
+				$(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left,
+				($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+			];
+		}
+
+		if(!(/^(document|window|parent)$/).test(o.containment)) {
+			ce = $(o.containment)[0];
+			co = $(o.containment).offset();
+			over = ($(ce).css("overflow") !== "hidden");
+
+			this.containment = [
+				co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
+				co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
+				co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
+				co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
+			];
+		}
+
+	},
+
+	_convertPositionTo: function(d, pos) {
+
+		if(!pos) {
+			pos = this.position;
+		}
+		var mod = d === "absolute" ? 1 : -1,
+			scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
+			scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+		return {
+			top: (
+				pos.top	+																// The absolute mouse position
+				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.top * mod -											// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+			),
+			left: (
+				pos.left +																// The absolute mouse position
+				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+			)
+		};
+
+	},
+
+	_generatePosition: function(event) {
+
+		var top, left,
+			o = this.options,
+			pageX = event.pageX,
+			pageY = event.pageY,
+			scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+		// This is another very weird special case that only happens for relative elements:
+		// 1. If the css position is relative
+		// 2. and the scroll parent is the document or similar to the offset parent
+		// we have to refresh the relative offset during the scroll so there are no jumps
+		if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) {
+			this.offset.relative = this._getRelativeOffset();
+		}
+
+		/*
+		 * - Position constraining -
+		 * Constrain the position to a mix of grid, containment.
+		 */
+
+		if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+
+			if(this.containment) {
+				if(event.pageX - this.offset.click.left < this.containment[0]) {
+					pageX = this.containment[0] + this.offset.click.left;
+				}
+				if(event.pageY - this.offset.click.top < this.containment[1]) {
+					pageY = this.containment[1] + this.offset.click.top;
+				}
+				if(event.pageX - this.offset.click.left > this.containment[2]) {
+					pageX = this.containment[2] + this.offset.click.left;
+				}
+				if(event.pageY - this.offset.click.top > this.containment[3]) {
+					pageY = this.containment[3] + this.offset.click.top;
+				}
+			}
+
+			if(o.grid) {
+				top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
+				pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+				left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
+				pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+			}
+
+		}
+
+		return {
+			top: (
+				pageY -																// The absolute mouse position
+				this.offset.click.top -													// Click offset (relative to the element)
+				this.offset.relative.top	-											// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+			),
+			left: (
+				pageX -																// The absolute mouse position
+				this.offset.click.left -												// Click offset (relative to the element)
+				this.offset.relative.left	-											// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+			)
+		};
+
+	},
+
+	_rearrange: function(event, i, a, hardRefresh) {
+
+		a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
+
+		//Various things done here to improve the performance:
+		// 1. we create a setTimeout, that calls refreshPositions
+		// 2. on the instance, we have a counter variable, that get's higher after every append
+		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
+		// 4. this lets only the last addition to the timeout stack through
+		this.counter = this.counter ? ++this.counter : 1;
+		var counter = this.counter;
+
+		this._delay(function() {
+			if(counter === this.counter) {
+				this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
+			}
+		});
+
+	},
+
+	_clear: function(event, noPropagation) {
+
+		this.reverting = false;
+		// We delay all events that have to be triggered to after the point where the placeholder has been removed and
+		// everything else normalized again
+		var i,
+			delayedTriggers = [];
+
+		// We first have to update the dom position of the actual currentItem
+		// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
+		if(!this._noFinalSort && this.currentItem.parent().length) {
+			this.placeholder.before(this.currentItem);
+		}
+		this._noFinalSort = null;
+
+		if(this.helper[0] === this.currentItem[0]) {
+			for(i in this._storedCSS) {
+				if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
+					this._storedCSS[i] = "";
+				}
+			}
+			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+		} else {
+			this.currentItem.show();
+		}
+
+		if(this.fromOutside && !noPropagation) {
+			delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
+		}
+		if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
+			delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
+		}
+
+		// Check if the items Container has Changed and trigger appropriate
+		// events.
+		if (this !== this.currentContainer) {
+			if(!noPropagation) {
+				delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
+				delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.currentContainer));
+				delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.currentContainer));
+			}
+		}
+
+
+		//Post events to containers
+		function delayEvent( type, instance, container ) {
+			return function( event ) {
+				container._trigger( type, event, instance._uiHash( instance ) );
+			};
+		}
+		for (i = this.containers.length - 1; i >= 0; i--){
+			if (!noPropagation) {
+				delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
+			}
+			if(this.containers[i].containerCache.over) {
+				delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
+				this.containers[i].containerCache.over = 0;
+			}
+		}
+
+		//Do what was originally in plugins
+		if ( this.storedCursor ) {
+			this.document.find( "body" ).css( "cursor", this.storedCursor );
+			this.storedStylesheet.remove();
+		}
+		if(this._storedOpacity) {
+			this.helper.css("opacity", this._storedOpacity);
+		}
+		if(this._storedZIndex) {
+			this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
+		}
+
+		this.dragging = false;
+		if(this.cancelHelperRemoval) {
+			if(!noPropagation) {
+				this._trigger("beforeStop", event, this._uiHash());
+				for (i=0; i < delayedTriggers.length; i++) {
+					delayedTriggers[i].call(this, event);
+				} //Trigger all delayed events
+				this._trigger("stop", event, this._uiHash());
+			}
+
+			this.fromOutside = false;
+			return false;
+		}
+
+		if(!noPropagation) {
+			this._trigger("beforeStop", event, this._uiHash());
+		}
+
+		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+		this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+
+		if(this.helper[0] !== this.currentItem[0]) {
+			this.helper.remove();
+		}
+		this.helper = null;
+
+		if(!noPropagation) {
+			for (i=0; i < delayedTriggers.length; i++) {
+				delayedTriggers[i].call(this, event);
+			} //Trigger all delayed events
+			this._trigger("stop", event, this._uiHash());
+		}
+
+		this.fromOutside = false;
+		return true;
+
+	},
+
+	_trigger: function() {
+		if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
+			this.cancel();
+		}
+	},
+
+	_uiHash: function(_inst) {
+		var inst = _inst || this;
+		return {
+			helper: inst.helper,
+			placeholder: inst.placeholder || $([]),
+			position: inst.position,
+			originalPosition: inst.originalPosition,
+			offset: inst.positionAbs,
+			item: inst.currentItem,
+			sender: _inst ? _inst.element : null
+		};
+	}
+
+});
+
+})(jQuery);
+(function( $, undefined ) {
+
+// number of pages in a slider
+// (how many times can you page up/down to go through the whole range)
+var numPages = 5;
+
+$.widget( "ui.slider", $.ui.mouse, {
+	version: "1.10.4",
+	widgetEventPrefix: "slide",
+
+	options: {
+		animate: false,
+		distance: 0,
+		max: 100,
+		min: 0,
+		orientation: "horizontal",
+		range: false,
+		step: 1,
+		value: 0,
+		values: null,
+
+		// callbacks
+		change: null,
+		slide: null,
+		start: null,
+		stop: null
+	},
+
+	_create: function() {
+		this._keySliding = false;
+		this._mouseSliding = false;
+		this._animateOff = true;
+		this._handleIndex = null;
+		this._detectOrientation();
+		this._mouseInit();
+
+		this.element
+			.addClass( "ui-slider" +
+				" ui-slider-" + this.orientation +
+				" ui-widget" +
+				" ui-widget-content" +
+				" ui-corner-all");
+
+		this._refresh();
+		this._setOption( "disabled", this.options.disabled );
+
+		this._animateOff = false;
+	},
+
+	_refresh: function() {
+		this._createRange();
+		this._createHandles();
+		this._setupEvents();
+		this._refreshValue();
+	},
+
+	_createHandles: function() {
+		var i, handleCount,
+			options = this.options,
+			existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
+			handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
+			handles = [];
+
+		handleCount = ( options.values && options.values.length ) || 1;
+
+		if ( existingHandles.length > handleCount ) {
+			existingHandles.slice( handleCount ).remove();
+			existingHandles = existingHandles.slice( 0, handleCount );
+		}
+
+		for ( i = existingHandles.length; i < handleCount; i++ ) {
+			handles.push( handle );
+		}
+
+		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
+
+		this.handle = this.handles.eq( 0 );
+
+		this.handles.each(function( i ) {
+			$( this ).data( "ui-slider-handle-index", i );
+		});
+	},
+
+	_createRange: function() {
+		var options = this.options,
+			classes = "";
+
+		if ( options.range ) {
+			if ( options.range === true ) {
+				if ( !options.values ) {
+					options.values = [ this._valueMin(), this._valueMin() ];
+				} else if ( options.values.length && options.values.length !== 2 ) {
+					options.values = [ options.values[0], options.values[0] ];
+				} else if ( $.isArray( options.values ) ) {
+					options.values = options.values.slice(0);
+				}
+			}
+
+			if ( !this.range || !this.range.length ) {
+				this.range = $( "<div></div>" )
+					.appendTo( this.element );
+
+				classes = "ui-slider-range" +
+				// note: this isn't the most fittingly semantic framework class for this element,
+				// but worked best visually with a variety of themes
+				" ui-widget-header ui-corner-all";
+			} else {
+				this.range.removeClass( "ui-slider-range-min ui-slider-range-max" )
+					// Handle range switching from true to min/max
+					.css({
+						"left": "",
+						"bottom": ""
+					});
+			}
+
+			this.range.addClass( classes +
+				( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) );
+		} else {
+			if ( this.range ) {
+				this.range.remove();
+			}
+			this.range = null;
+		}
+	},
+
+	_setupEvents: function() {
+		var elements = this.handles.add( this.range ).filter( "a" );
+		this._off( elements );
+		this._on( elements, this._handleEvents );
+		this._hoverable( elements );
+		this._focusable( elements );
+	},
+
+	_destroy: function() {
+		this.handles.remove();
+		if ( this.range ) {
+			this.range.remove();
+		}
+
+		this.element
+			.removeClass( "ui-slider" +
+				" ui-slider-horizontal" +
+				" ui-slider-vertical" +
+				" ui-widget" +
+				" ui-widget-content" +
+				" ui-corner-all" );
+
+		this._mouseDestroy();
+	},
+
+	_mouseCapture: function( event ) {
+		var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
+			that = this,
+			o = this.options;
+
+		if ( o.disabled ) {
+			return false;
+		}
+
+		this.elementSize = {
+			width: this.element.outerWidth(),
+			height: this.element.outerHeight()
+		};
+		this.elementOffset = this.element.offset();
+
+		position = { x: event.pageX, y: event.pageY };
+		normValue = this._normValueFromMouse( position );
+		distance = this._valueMax() - this._valueMin() + 1;
+		this.handles.each(function( i ) {
+			var thisDistance = Math.abs( normValue - that.values(i) );
+			if (( distance > thisDistance ) ||
+				( distance === thisDistance &&
+					(i === that._lastChangedValue || that.values(i) === o.min ))) {
+				distance = thisDistance;
+				closestHandle = $( this );
+				index = i;
+			}
+		});
+
+		allowed = this._start( event, index );
+		if ( allowed === false ) {
+			return false;
+		}
+		this._mouseSliding = true;
+
+		this._handleIndex = index;
+
+		closestHandle
+			.addClass( "ui-state-active" )
+			.focus();
+
+		offset = closestHandle.offset();
+		mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" );
+		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
+			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
+			top: event.pageY - offset.top -
+				( closestHandle.height() / 2 ) -
+				( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
+				( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
+				( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
+		};
+
+		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
+			this._slide( event, index, normValue );
+		}
+		this._animateOff = true;
+		return true;
+	},
+
+	_mouseStart: function() {
+		return true;
+	},
+
+	_mouseDrag: function( event ) {
+		var position = { x: event.pageX, y: event.pageY },
+			normValue = this._normValueFromMouse( position );
+
+		this._slide( event, this._handleIndex, normValue );
+
+		return false;
+	},
+
+	_mouseStop: function( event ) {
+		this.handles.removeClass( "ui-state-active" );
+		this._mouseSliding = false;
+
+		this._stop( event, this._handleIndex );
+		this._change( event, this._handleIndex );
+
+		this._handleIndex = null;
+		this._clickOffset = null;
+		this._animateOff = false;
+
+		return false;
+	},
+
+	_detectOrientation: function() {
+		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
+	},
+
+	_normValueFromMouse: function( position ) {
+		var pixelTotal,
+			pixelMouse,
+			percentMouse,
+			valueTotal,
+			valueMouse;
+
+		if ( this.orientation === "horizontal" ) {
+			pixelTotal = this.elementSize.width;
+			pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
+		} else {
+			pixelTotal = this.elementSize.height;
+			pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
+		}
+
+		percentMouse = ( pixelMouse / pixelTotal );
+		if ( percentMouse > 1 ) {
+			percentMouse = 1;
+		}
+		if ( percentMouse < 0 ) {
+			percentMouse = 0;
+		}
+		if ( this.orientation === "vertical" ) {
+			percentMouse = 1 - percentMouse;
+		}
+
+		valueTotal = this._valueMax() - this._valueMin();
+		valueMouse = this._valueMin() + percentMouse * valueTotal;
+
+		return this._trimAlignValue( valueMouse );
+	},
+
+	_start: function( event, index ) {
+		var uiHash = {
+			handle: this.handles[ index ],
+			value: this.value()
+		};
+		if ( this.options.values && this.options.values.length ) {
+			uiHash.value = this.values( index );
+			uiHash.values = this.values();
+		}
+		return this._trigger( "start", event, uiHash );
+	},
+
+	_slide: function( event, index, newVal ) {
+		var otherVal,
+			newValues,
+			allowed;
+
+		if ( this.options.values && this.options.values.length ) {
+			otherVal = this.values( index ? 0 : 1 );
+
+			if ( ( this.options.values.length === 2 && this.options.range === true ) &&
+					( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
+				) {
+				newVal = otherVal;
+			}
+
+			if ( newVal !== this.values( index ) ) {
+				newValues = this.values();
+				newValues[ index ] = newVal;
+				// A slide can be canceled by returning false from the slide callback
+				allowed = this._trigger( "slide", event, {
+					handle: this.handles[ index ],
+					value: newVal,
+					values: newValues
+				} );
+				otherVal = this.values( index ? 0 : 1 );
+				if ( allowed !== false ) {
+					this.values( index, newVal );
+				}
+			}
+		} else {
+			if ( newVal !== this.value() ) {
+				// A slide can be canceled by returning false from the slide callback
+				allowed = this._trigger( "slide", event, {
+					handle: this.handles[ index ],
+					value: newVal
+				} );
+				if ( allowed !== false ) {
+					this.value( newVal );
+				}
+			}
+		}
+	},
+
+	_stop: function( event, index ) {
+		var uiHash = {
+			handle: this.handles[ index ],
+			value: this.value()
+		};
+		if ( this.options.values && this.options.values.length ) {
+			uiHash.value = this.values( index );
+			uiHash.values = this.values();
+		}
+
+		this._trigger( "stop", event, uiHash );
+	},
+
+	_change: function( event, index ) {
+		if ( !this._keySliding && !this._mouseSliding ) {
+			var uiHash = {
+				handle: this.handles[ index ],
+				value: this.value()
+			};
+			if ( this.options.values && this.options.values.length ) {
+				uiHash.value = this.values( index );
+				uiHash.values = this.values();
+			}
+
+			//store the last changed value index for reference when handles overlap
+			this._lastChangedValue = index;
+
+			this._trigger( "change", event, uiHash );
+		}
+	},
+
+	value: function( newValue ) {
+		if ( arguments.length ) {
+			this.options.value = this._trimAlignValue( newValue );
+			this._refreshValue();
+			this._change( null, 0 );
+			return;
+		}
+
+		return this._value();
+	},
+
+	values: function( index, newValue ) {
+		var vals,
+			newValues,
+			i;
+
+		if ( arguments.length > 1 ) {
+			this.options.values[ index ] = this._trimAlignValue( newValue );
+			this._refreshValue();
+			this._change( null, index );
+			return;
+		}
+
+		if ( arguments.length ) {
+			if ( $.isArray( arguments[ 0 ] ) ) {
+				vals = this.options.values;
+				newValues = arguments[ 0 ];
+				for ( i = 0; i < vals.length; i += 1 ) {
+					vals[ i ] = this._trimAlignValue( newValues[ i ] );
+					this._change( null, i );
+				}
+				this._refreshValue();
+			} else {
+				if ( this.options.values && this.options.values.length ) {
+					return this._values( index );
+				} else {
+					return this.value();
+				}
+			}
+		} else {
+			return this._values();
+		}
+	},
+
+	_setOption: function( key, value ) {
+		var i,
+			valsLength = 0;
+
+		if ( key === "range" && this.options.range === true ) {
+			if ( value === "min" ) {
+				this.options.value = this._values( 0 );
+				this.options.values = null;
+			} else if ( value === "max" ) {
+				this.options.value = this._values( this.options.values.length-1 );
+				this.options.values = null;
+			}
+		}
+
+		if ( $.isArray( this.options.values ) ) {
+			valsLength = this.options.values.length;
+		}
+
+		$.Widget.prototype._setOption.apply( this, arguments );
+
+		switch ( key ) {
+			case "orientation":
+				this._detectOrientation();
+				this.element
+					.removeClass( "ui-slider-horizontal ui-slider-vertical" )
+					.addClass( "ui-slider-" + this.orientation );
+				this._refreshValue();
+				break;
+			case "value":
+				this._animateOff = true;
+				this._refreshValue();
+				this._change( null, 0 );
+				this._animateOff = false;
+				break;
+			case "values":
+				this._animateOff = true;
+				this._refreshValue();
+				for ( i = 0; i < valsLength; i += 1 ) {
+					this._change( null, i );
+				}
+				this._animateOff = false;
+				break;
+			case "min":
+			case "max":
+				this._animateOff = true;
+				this._refreshValue();
+				this._animateOff = false;
+				break;
+			case "range":
+				this._animateOff = true;
+				this._refresh();
+				this._animateOff = false;
+				break;
+		}
+	},
+
+	//internal value getter
+	// _value() returns value trimmed by min and max, aligned by step
+	_value: function() {
+		var val = this.options.value;
+		val = this._trimAlignValue( val );
+
+		return val;
+	},
+
+	//internal values getter
+	// _values() returns array of values trimmed by min and max, aligned by step
+	// _values( index ) returns single value trimmed by min and max, aligned by step
+	_values: function( index ) {
+		var val,
+			vals,
+			i;
+
+		if ( arguments.length ) {
+			val = this.options.values[ index ];
+			val = this._trimAlignValue( val );
+
+			return val;
+		} else if ( this.options.values && this.options.values.length ) {
+			// .slice() creates a copy of the array
+			// this copy gets trimmed by min and max and then returned
+			vals = this.options.values.slice();
+			for ( i = 0; i < vals.length; i+= 1) {
+				vals[ i ] = this._trimAlignValue( vals[ i ] );
+			}
+
+			return vals;
+		} else {
+			return [];
+		}
+	},
+
+	// returns the step-aligned value that val is closest to, between (inclusive) min and max
+	_trimAlignValue: function( val ) {
+		if ( val <= this._valueMin() ) {
+			return this._valueMin();
+		}
+		if ( val >= this._valueMax() ) {
+			return this._valueMax();
+		}
+		var step = ( this.options.step > 0 ) ? this.options.step : 1,
+			valModStep = (val - this._valueMin()) % step,
+			alignValue = val - valModStep;
+
+		if ( Math.abs(valModStep) * 2 >= step ) {
+			alignValue += ( valModStep > 0 ) ? step : ( -step );
+		}
+
+		// Since JavaScript has problems with large floats, round
+		// the final value to 5 digits after the decimal point (see #4124)
+		return parseFloat( alignValue.toFixed(5) );
+	},
+
+	_valueMin: function() {
+		return this.options.min;
+	},
+
+	_valueMax: function() {
+		return this.options.max;
+	},
+
+	_refreshValue: function() {
+		var lastValPercent, valPercent, value, valueMin, valueMax,
+			oRange = this.options.range,
+			o = this.options,
+			that = this,
+			animate = ( !this._animateOff ) ? o.animate : false,
+			_set = {};
+
+		if ( this.options.values && this.options.values.length ) {
+			this.handles.each(function( i ) {
+				valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
+				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+				if ( that.options.range === true ) {
+					if ( that.orientation === "horizontal" ) {
+						if ( i === 0 ) {
+							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
+						}
+						if ( i === 1 ) {
+							that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+						}
+					} else {
+						if ( i === 0 ) {
+							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
+						}
+						if ( i === 1 ) {
+							that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+						}
+					}
+				}
+				lastValPercent = valPercent;
+			});
+		} else {
+			value = this.value();
+			valueMin = this._valueMin();
+			valueMax = this._valueMax();
+			valPercent = ( valueMax !== valueMin ) ?
+					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
+					0;
+			_set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+
+			if ( oRange === "min" && this.orientation === "horizontal" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
+			}
+			if ( oRange === "max" && this.orientation === "horizontal" ) {
+				this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+			}
+			if ( oRange === "min" && this.orientation === "vertical" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
+			}
+			if ( oRange === "max" && this.orientation === "vertical" ) {
+				this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+			}
+		}
+	},
+
+	_handleEvents: {
+		keydown: function( event ) {
+			var allowed, curVal, newVal, step,
+				index = $( event.target ).data( "ui-slider-handle-index" );
+
+			switch ( event.keyCode ) {
+				case $.ui.keyCode.HOME:
+				case $.ui.keyCode.END:
+				case $.ui.keyCode.PAGE_UP:
+				case $.ui.keyCode.PAGE_DOWN:
+				case $.ui.keyCode.UP:
+				case $.ui.keyCode.RIGHT:
+				case $.ui.keyCode.DOWN:
+				case $.ui.keyCode.LEFT:
+					event.preventDefault();
+					if ( !this._keySliding ) {
+						this._keySliding = true;
+						$( event.target ).addClass( "ui-state-active" );
+						allowed = this._start( event, index );
+						if ( allowed === false ) {
+							return;
+						}
+					}
+					break;
+			}
+
+			step = this.options.step;
+			if ( this.options.values && this.options.values.length ) {
+				curVal = newVal = this.values( index );
+			} else {
+				curVal = newVal = this.value();
+			}
+
+			switch ( event.keyCode ) {
+				case $.ui.keyCode.HOME:
+					newVal = this._valueMin();
+					break;
+				case $.ui.keyCode.END:
+					newVal = this._valueMax();
+					break;
+				case $.ui.keyCode.PAGE_UP:
+					newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
+					break;
+				case $.ui.keyCode.PAGE_DOWN:
+					newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
+					break;
+				case $.ui.keyCode.UP:
+				case $.ui.keyCode.RIGHT:
+					if ( curVal === this._valueMax() ) {
+						return;
+					}
+					newVal = this._trimAlignValue( curVal + step );
+					break;
+				case $.ui.keyCode.DOWN:
+				case $.ui.keyCode.LEFT:
+					if ( curVal === this._valueMin() ) {
+						return;
+					}
+					newVal = this._trimAlignValue( curVal - step );
+					break;
+			}
+
+			this._slide( event, index, newVal );
+		},
+		click: function( event ) {
+			event.preventDefault();
+		},
+		keyup: function( event ) {
+			var index = $( event.target ).data( "ui-slider-handle-index" );
+
+			if ( this._keySliding ) {
+				this._keySliding = false;
+				this._stop( event, index );
+				this._change( event, index );
+				$( event.target ).removeClass( "ui-state-active" );
+			}
+		}
+	}
+
+});
+
+}(jQuery));
diff --git a/src/UI/Mixins/AsFilteredCollection.js b/src/UI/Mixins/AsFilteredCollection.js
index 447f60edb..2a0e17991 100644
--- a/src/UI/Mixins/AsFilteredCollection.js
+++ b/src/UI/Mixins/AsFilteredCollection.js
@@ -32,7 +32,7 @@ define(
             this.prototype._makeFullCollection = function (models, options) {
                 var self = this;
 
-                self.shadowCollection = originalMakeFullCollection.apply(this, [models, options]);
+                self.shadowCollection = originalMakeFullCollection.call(this, models, options);
                 
                 var filterModel = function(model) {
                     if (!self.state.filterKey || !self.state.filterValue)
@@ -46,12 +46,10 @@ define(
                 };
 
                 var filteredModels = self.shadowCollection.filtered();
-                
-                var fullCollection = originalMakeFullCollection.apply(this, [filteredModels, options]);
-
+                var fullCollection = originalMakeFullCollection.call(this, filteredModels, options);
 
                 fullCollection.resetFiltered = function(options) {
-                    Backbone.Collection.prototype.reset.apply(this, [self.shadowCollection.filtered(), options]);
+                    Backbone.Collection.prototype.reset.call(this, self.shadowCollection.filtered(), options);
                 };
                 
                 fullCollection.reset = function (models, options) {
diff --git a/src/UI/Mixins/backbone.signalr.mixin.js b/src/UI/Mixins/backbone.signalr.mixin.js
index 04b1dfd7f..a29ed4882 100644
--- a/src/UI/Mixins/backbone.signalr.mixin.js
+++ b/src/UI/Mixins/backbone.signalr.mixin.js
@@ -22,6 +22,12 @@ define(
                         return;
                     }
 
+                    if (options.action === 'deleted') {
+                        collection.remove(new collection.model(options.resource, {parse: true}));
+
+                        return;
+                    }
+
                     var model = new collection.model(options.resource, {parse: true});
 
                     //updateOnly will prevent the collection from adding a new item
diff --git a/src/UI/Quality/QualityDefinitionCollection.js b/src/UI/Quality/QualityDefinitionCollection.js
new file mode 100644
index 000000000..8da78717b
--- /dev/null
+++ b/src/UI/Quality/QualityDefinitionCollection.js
@@ -0,0 +1,11 @@
+'use strict';
+define(
+    [
+        'backbone',
+        'Quality/QualityDefinitionModel'
+    ], function (Backbone, QualityDefinitionModel) {
+        return Backbone.Collection.extend({
+            model: QualityDefinitionModel,
+            url  : window.NzbDrone.ApiRoot + '/qualitydefinition'
+        });
+    });
diff --git a/src/UI/Quality/QualitySizeModel.js b/src/UI/Quality/QualityDefinitionModel.js
similarity index 71%
rename from src/UI/Quality/QualitySizeModel.js
rename to src/UI/Quality/QualityDefinitionModel.js
index d305969b3..e95c87a95 100644
--- a/src/UI/Quality/QualitySizeModel.js
+++ b/src/UI/Quality/QualityDefinitionModel.js
@@ -9,10 +9,10 @@ define(
             baseInitialize: ModelBase.prototype.initialize,
 
             initialize: function () {
-                var name = this.get('name');
+                var name = this.get('quality').name;
 
-                this.successMessage = 'Saved ' + name + ' size settings';
-                this.errorMessage = 'Couldn\'t save ' + name + ' size settings';
+                this.successMessage = 'Saved ' + name + ' quality settings';
+                this.errorMessage = 'Couldn\'t save ' + name + ' quality settings';
 
                 this.baseInitialize.call(this);
             }
diff --git a/src/UI/Quality/QualitySizeCollection.js b/src/UI/Quality/QualitySizeCollection.js
deleted file mode 100644
index 92c580647..000000000
--- a/src/UI/Quality/QualitySizeCollection.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-define(
-    [
-        'backbone',
-        'Quality/QualitySizeModel'
-    ], function (Backbone, QualitySizeModel) {
-        return Backbone.Collection.extend({
-            model: QualitySizeModel,
-            url  : window.NzbDrone.ApiRoot + '/qualitysize'
-        });
-    });
diff --git a/src/UI/Series/Editor/SeriesEditorFooterView.js b/src/UI/Series/Editor/SeriesEditorFooterView.js
index 28a27bca4..3d8993ece 100644
--- a/src/UI/Series/Editor/SeriesEditorFooterView.js
+++ b/src/UI/Series/Editor/SeriesEditorFooterView.js
@@ -7,9 +7,9 @@ define(
         'vent',
         'Series/SeriesCollection',
         'Quality/QualityProfileCollection',
-        'AddSeries/RootFolders/Collection',
+        'AddSeries/RootFolders/RootFolderCollection',
         'Shared/Toolbar/ToolbarLayout',
-        'AddSeries/RootFolders/Layout',
+        'AddSeries/RootFolders/RootFolderLayout',
         'Config'
     ], function (_,
                  Marionette,
diff --git a/src/UI/Series/EpisodeCollection.js b/src/UI/Series/EpisodeCollection.js
index 8c72b2440..570a72de7 100644
--- a/src/UI/Series/EpisodeCollection.js
+++ b/src/UI/Series/EpisodeCollection.js
@@ -5,7 +5,7 @@ define(
         'Series/EpisodeModel'
     ], function (Backbone, EpisodeModel) {
         return Backbone.Collection.extend({
-            url  : window.NzbDrone.ApiRoot + '/episodes',
+            url  : window.NzbDrone.ApiRoot + '/episode',
             model: EpisodeModel,
 
             state: {
diff --git a/src/UI/Series/EpisodeModel.js b/src/UI/Series/EpisodeModel.js
index 9a040715d..8f6459d47 100644
--- a/src/UI/Series/EpisodeModel.js
+++ b/src/UI/Series/EpisodeModel.js
@@ -11,7 +11,7 @@ define(
             },
 
             methodUrls: {
-                'update': window.NzbDrone.ApiRoot + '/episodes'
+                'update': window.NzbDrone.ApiRoot + '/episode'
             },
 
             sync: function(method, model, options) {
diff --git a/src/UI/Series/Index/List/CollectionView.js b/src/UI/Series/Index/Overview/SeriesOverviewCollectionView.js
similarity index 64%
rename from src/UI/Series/Index/List/CollectionView.js
rename to src/UI/Series/Index/Overview/SeriesOverviewCollectionView.js
index 8f6b5ae7f..986edd574 100644
--- a/src/UI/Series/Index/List/CollectionView.js
+++ b/src/UI/Series/Index/Overview/SeriesOverviewCollectionView.js
@@ -3,12 +3,12 @@
 define(
     [
         'marionette',
-        'Series/Index/List/ItemView'
+        'Series/Index/Overview/SeriesOverviewItemView'
     ], function (Marionette, ListItemView) {
 
         return Marionette.CompositeView.extend({
             itemView         : ListItemView,
             itemViewContainer: '#x-series-list',
-            template         : 'Series/Index/List/CollectionTemplate'
+            template         : 'Series/Index/Overview/SeriesOverviewCollectionViewTemplate'
         });
     });
diff --git a/src/UI/Series/Index/List/CollectionTemplate.html b/src/UI/Series/Index/Overview/SeriesOverviewCollectionViewTemplate.html
similarity index 100%
rename from src/UI/Series/Index/List/CollectionTemplate.html
rename to src/UI/Series/Index/Overview/SeriesOverviewCollectionViewTemplate.html
diff --git a/src/UI/Series/Index/List/ItemView.js b/src/UI/Series/Index/Overview/SeriesOverviewItemView.js
similarity index 89%
rename from src/UI/Series/Index/List/ItemView.js
rename to src/UI/Series/Index/Overview/SeriesOverviewItemView.js
index e6da6377b..1c4970a65 100644
--- a/src/UI/Series/Index/List/ItemView.js
+++ b/src/UI/Series/Index/Overview/SeriesOverviewItemView.js
@@ -6,7 +6,7 @@ define(
         'marionette'
     ], function (vent, Marionette) {
         return Marionette.ItemView.extend({
-            template: 'Series/Index/List/ItemTemplate',
+            template: 'Series/Index/Overview/SeriesOverviewItemViewTemplate',
 
             ui: {
                 'progressbar': '.progress .bar'
diff --git a/src/UI/Series/Index/List/ItemTemplate.html b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.html
similarity index 100%
rename from src/UI/Series/Index/List/ItemTemplate.html
rename to src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.html
diff --git a/src/UI/Series/Index/Posters/CollectionView.js b/src/UI/Series/Index/Posters/SeriesPostersCollectionView.js
similarity index 65%
rename from src/UI/Series/Index/Posters/CollectionView.js
rename to src/UI/Series/Index/Posters/SeriesPostersCollectionView.js
index 06600e8fb..ead7a910a 100644
--- a/src/UI/Series/Index/Posters/CollectionView.js
+++ b/src/UI/Series/Index/Posters/SeriesPostersCollectionView.js
@@ -3,12 +3,12 @@
 define(
     [
         'marionette',
-        'Series/Index/Posters/ItemView'
+        'Series/Index/Posters/SeriesPostersItemView'
     ], function (Marionette, PosterItemView) {
 
         return Marionette.CompositeView.extend({
             itemView         : PosterItemView,
             itemViewContainer: '#x-series-posters',
-            template         : 'Series/Index/Posters/CollectionTemplate'
+            template         : 'Series/Index/Posters/SeriesPostersCollectionViewTemplate'
         });
     });
diff --git a/src/UI/Series/Index/Posters/CollectionTemplate.html b/src/UI/Series/Index/Posters/SeriesPostersCollectionViewTemplate.html
similarity index 100%
rename from src/UI/Series/Index/Posters/CollectionTemplate.html
rename to src/UI/Series/Index/Posters/SeriesPostersCollectionViewTemplate.html
diff --git a/src/UI/Series/Index/Posters/ItemView.js b/src/UI/Series/Index/Posters/SeriesPostersItemView.js
similarity index 93%
rename from src/UI/Series/Index/Posters/ItemView.js
rename to src/UI/Series/Index/Posters/SeriesPostersItemView.js
index 02d4ba713..997c1c9e1 100644
--- a/src/UI/Series/Index/Posters/ItemView.js
+++ b/src/UI/Series/Index/Posters/SeriesPostersItemView.js
@@ -8,7 +8,7 @@ define(
 
         return Marionette.ItemView.extend({
             tagName : 'li',
-            template: 'Series/Index/Posters/ItemTemplate',
+            template: 'Series/Index/Posters/SeriesPostersItemViewTemplate',
 
 
             ui: {
diff --git a/src/UI/Series/Index/Posters/ItemTemplate.html b/src/UI/Series/Index/Posters/SeriesPostersItemViewTemplate.html
similarity index 89%
rename from src/UI/Series/Index/Posters/ItemTemplate.html
rename to src/UI/Series/Index/Posters/SeriesPostersItemViewTemplate.html
index 36bb4ff0f..7ef1c9c4e 100644
--- a/src/UI/Series/Index/Posters/ItemTemplate.html
+++ b/src/UI/Series/Index/Posters/SeriesPostersItemViewTemplate.html
@@ -11,15 +11,12 @@
                         <div class="ended-banner">Ended</div>
                     {{/unless_eq}}
                     <a href="{{route}}">
-                        <img class="series-poster" src="{{poster}}" alt="{{title}}">
+                        <img class="series-poster" src="{{poster}}" {{defaultImg}}>
+                        <div class="center title">{{title}}</div>
                     </a>
                 </div>
             </div>
 
-            <div class="title-container">
-                <div class="center title">{{title}}</div>
-            </div>
-
             <div class="center">
                 <div class="labels">
                     {{#if_eq status compare="continuing"}}
diff --git a/src/UI/Series/Index/SeriesIndexLayout.js b/src/UI/Series/Index/SeriesIndexLayout.js
index 3313bca83..bfd37e8e7 100644
--- a/src/UI/Series/Index/SeriesIndexLayout.js
+++ b/src/UI/Series/Index/SeriesIndexLayout.js
@@ -1,10 +1,11 @@
 'use strict';
 define(
     [
+        'underscore',
         'marionette',
         'backgrid',
-        'Series/Index/Posters/CollectionView',
-        'Series/Index/List/CollectionView',
+        'Series/Index/Posters/SeriesPostersCollectionView',
+        'Series/Index/Overview/SeriesOverviewCollectionView',
         'Series/Index/EmptyView',
         'Series/SeriesCollection',
         'Cells/RelativeDateCell',
@@ -17,8 +18,9 @@ define(
         'Series/Index/FooterView',
         'Series/Index/FooterModel',
         'Shared/Toolbar/ToolbarLayout',
-        'underscore'
-    ], function (Marionette,
+        'Mixins/backbone.signalr.mixin'
+    ], function (_,
+                 Marionette,
                  Backgrid,
                  PosterCollectionView,
                  ListCollectionView,
@@ -33,8 +35,7 @@ define(
                  SeriesStatusCell,
                  FooterView,
                  FooterModel,
-                 ToolbarLayout,
-                 _) {
+                 ToolbarLayout) {
         return Marionette.Layout.extend({
             template: 'Series/Index/SeriesIndexLayoutTemplate',
 
@@ -130,9 +131,22 @@ define(
 
             initialize: function () {
                 this.seriesCollection = SeriesCollection.clone();
+                this.seriesCollection.shadowCollection.bindSignalR();
 
-                this.listenTo(SeriesCollection, 'sync', this._renderView);
-                this.listenTo(SeriesCollection, 'remove', this._renderView);
+                this.listenTo(this.seriesCollection.shadowCollection, 'sync', function (model, collection, options) {
+                    this.seriesCollection.fullCollection.resetFiltered();
+                    this._renderView();
+                });
+
+                this.listenTo(this.seriesCollection.shadowCollection, 'add', function (model, collection, options) {
+                    this.seriesCollection.fullCollection.resetFiltered();
+                    this._renderView();
+                });
+
+                this.listenTo(this.seriesCollection.shadowCollection, 'remove', function (model, collection, options) {
+                    this.seriesCollection.fullCollection.resetFiltered();
+                    this._renderView();
+                });
 
                 this.sortingOptions = {
                     type          : 'sorting',
@@ -240,7 +254,6 @@ define(
 
             onShow: function () {
                 this._showToolbar();
-                this._renderView();
                 this._fetchCollection();
             },
 
@@ -255,7 +268,7 @@ define(
             },
 
             _showList: function () {
-                this.currentView = new ListCollectionView({ 
+                this.currentView = new ListCollectionView({
                     collection: this.seriesCollection
                 });
 
@@ -269,11 +282,12 @@ define(
 
                 this._renderView();
             },
-            
+
             _renderView: function () {
 
                 if (SeriesCollection.length === 0) {
                     this.seriesRegion.show(new EmptyView());
+
                     this.toolbar.close();
                     this.toolbar2.close();
                 }
@@ -301,6 +315,14 @@ define(
                     return;
                 }
 
+                this.toolbar2.show(new ToolbarLayout({
+                    right  :
+                        [
+                            this.filteringOptions
+                        ],
+                    context: this
+                }));
+
                 this.toolbar.show(new ToolbarLayout({
                     right  :
                         [
@@ -313,14 +335,6 @@ define(
                         ],
                     context: this
                 }));
-
-                this.toolbar2.show(new ToolbarLayout({
-                    right  :
-                        [
-                            this.filteringOptions
-                        ],
-                    context: this
-                }));
             },
 
             _showFooter: function () {
diff --git a/src/UI/Series/series.less b/src/UI/Series/series.less
index 474c61d99..2e23c92a5 100644
--- a/src/UI/Series/series.less
+++ b/src/UI/Series/series.less
@@ -95,16 +95,6 @@
       }
     }
 
-    .title-container {
-      position : relative;
-
-      .title {
-        position : absolute;
-        top      : -100px;
-        opacity  : 0.0;
-      }
-    }
-
     .labels {
       display : inline-block;
       .opacity(0.75);
@@ -130,6 +120,21 @@
     overflow : hidden;
     display  : inline-block;
 
+    .placeholder-image ~ .title {
+      opacity: 1.0;
+    }
+
+    .title {
+      position   : absolute;
+      top        : 25px;
+      color      : #f5f5f5;
+      width      : 100%;
+      font-size  : 22px;
+      line-height: 24px;
+      opacity    : 0.0;
+      font-weight: 100;
+    }
+
     .ended-banner {
       color                    : #eeeeee;
       background-color         : #b94a48;
@@ -137,8 +142,8 @@
       -moz-transform-origin    : 50% 50%;
       -webkit-transform-origin : 50% 50%;
       position                 : absolute;
-      width                    : 300px;
-      top                      : 175px;
+      width                    : 320px;
+      top                      : 200px;
       left                     : -122px;
       text-align               : center;
       .opacity(0.9);
diff --git a/src/UI/Settings/Notifications/AddItemView.js b/src/UI/Settings/Notifications/AddItemView.js
index c1264354b..2c031b3e0 100644
--- a/src/UI/Settings/Notifications/AddItemView.js
+++ b/src/UI/Settings/Notifications/AddItemView.js
@@ -24,10 +24,11 @@ define([
             }
 
             this.model.set({
-                id: undefined,
-                name: this.model.get('implementationName'),
-                onGrab: true,
-                onDownload: true
+                id         : undefined,
+                name       : this.model.get('implementationName'),
+                onGrab     : true,
+                onDownload : true,
+                onUpgrade  : true
             });
 
             var editView = new EditView({ model: this.model, notificationCollection: this.notificationCollection });
diff --git a/src/UI/Settings/Notifications/NotificationEditView.js b/src/UI/Settings/Notifications/NotificationEditView.js
index 92a6f8af1..03de47197 100644
--- a/src/UI/Settings/Notifications/NotificationEditView.js
+++ b/src/UI/Settings/Notifications/NotificationEditView.js
@@ -16,18 +16,28 @@ define(
         var model = Marionette.ItemView.extend({
             template: 'Settings/Notifications/NotificationEditViewTemplate',
 
+            ui: {
+                onDownloadToggle: '.x-on-download',
+                onUpgradeSection: '.x-on-upgrade'
+            },
+
             events: {
                 'click .x-save'        : '_saveNotification',
                 'click .x-save-and-add': '_saveAndAddNotification',
                 'click .x-delete'      : '_deleteNotification',
                 'click .x-back'        : '_back',
-                'click .x-test'        : '_test'
+                'click .x-test'        : '_test',
+                'change .x-on-download': '_onDownloadChanged'
             },
 
             initialize: function (options) {
                 this.notificationCollection = options.notificationCollection;
             },
 
+            onRender: function () {
+                this._onDownloadChanged();
+            },
+
             _saveNotification: function () {
                 var self = this;
                 var promise = this.model.saveSettings();
@@ -71,6 +81,18 @@ define(
                 });
 
                 CommandController.Execute(testCommand, properties);
+            },
+
+            _onDownloadChanged: function () {
+                var checked = this.ui.onDownloadToggle.prop('checked');
+
+                if (checked) {
+                    this.ui.onUpgradeSection.show();
+                }
+
+                else {
+                    this.ui.onUpgradeSection.hide();
+                }
             }
         });
 
diff --git a/src/UI/Settings/Notifications/NotificationEditViewTemplate.html b/src/UI/Settings/Notifications/NotificationEditViewTemplate.html
index c785fe9c1..e6abfa829 100644
--- a/src/UI/Settings/Notifications/NotificationEditViewTemplate.html
+++ b/src/UI/Settings/Notifications/NotificationEditViewTemplate.html
@@ -30,9 +30,9 @@
                     <div class="btn btn-primary slide-button"/>
                 </label>
 
-            <span class="help-inline-checkbox">
-                <i class="icon-nd-form-info" title="Do you want to get notifications when episodes are grabbed?"/>
-            </span>
+                <span class="help-inline-checkbox">
+                    <i class="icon-nd-form-info" title="Do you want to get notifications when episodes are grabbed?"/>
+                </span>
             </div>
         </div>
 
@@ -41,7 +41,7 @@
 
             <div class="controls">
                 <label class="checkbox toggle well">
-                    <input type="checkbox" name="onDownload"/>
+                    <input type="checkbox" name="onDownload" class="x-on-download"/>
                     <p>
                         <span>Yes</span>
                         <span>No</span>
@@ -50,9 +50,29 @@
                     <div class="btn btn-primary slide-button"/>
                 </label>
 
-            <span class="help-inline-checkbox">
-                <i class="icon-nd-form-info" title="Do you want to get notifications when episodes are downloaded?"/>
-            </span>
+                <span class="help-inline-checkbox">
+                    <i class="icon-nd-form-info" title="Do you want to get notifications when episodes are downloaded?"/>
+                </span>
+            </div>
+        </div>
+
+        <div class="control-group x-on-upgrade">
+            <label class="control-label">On Upgrade</label>
+
+            <div class="controls">
+                <label class="checkbox toggle well">
+                    <input type="checkbox" name="onUpgrade"/>
+                    <p>
+                        <span>Yes</span>
+                        <span>No</span>
+                    </p>
+
+                    <div class="btn btn-primary slide-button"/>
+                </label>
+
+                <span class="help-inline-checkbox">
+                    <i class="icon-nd-form-info" title="Do you want to get notifications when episodes are upgraded to a better quality?"/>
+                </span>
             </div>
         </div>
 
diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionTemplate.html b/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionTemplate.html
new file mode 100644
index 000000000..d3287100b
--- /dev/null
+++ b/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionTemplate.html
@@ -0,0 +1,16 @@
+<fieldset>
+    <legend>Quality Definitions</legend>
+    <div class="span11">
+        <div id="quality-definition-list">
+            <div class="quality-header x-header">
+                <div class="row">
+                    <span class="span2">Quality</span>
+                    <span class="span2">Title</span>
+                    <span class="offset1 span4">Size Limit</span>
+                </div>
+            </div>
+            <div class="rows x-rows">
+            </div>
+        </div>
+    </div>
+</fieldset>
diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionView.js b/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionView.js
new file mode 100644
index 000000000..48588bcbf
--- /dev/null
+++ b/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionView.js
@@ -0,0 +1,17 @@
+'use strict';
+
+define(
+    [
+        'marionette',
+        'backgrid',
+        'Settings/Quality/Definition/QualityDefinitionView'
+    ], function (Marionette, Backgrid, QualityDefinitionView) {
+    
+        return Marionette.CompositeView.extend({
+            template: 'Settings/Quality/Definition/QualityDefinitionCollectionTemplate',
+            
+            itemViewContainer: ".x-rows",
+            
+            itemView: QualityDefinitionView
+        });
+    });
diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionTemplate.html b/src/UI/Settings/Quality/Definition/QualityDefinitionTemplate.html
new file mode 100644
index 000000000..e61558bfc
--- /dev/null
+++ b/src/UI/Settings/Quality/Definition/QualityDefinitionTemplate.html
@@ -0,0 +1,31 @@
+    <span class="span2">
+        {{quality.name}}
+    </span>
+    <span class="span2">
+        <input type="text" class="x-title input-block-level" value="{{title}}">
+    </span>
+    <span class="offset1 span4">
+        <div class="x-slider"></div>
+        <div class="size-label-wrapper">
+            <div class="pull-left">
+                <span class="label label-warning x-min-thirty"
+                      name="thirtyMinuteMinSize"
+                      title="Minimum size for a 30 minute episode">
+                </span>
+                <span class="label label-info x-min-sixty"
+                      name="sixtyMinuteMinSize"
+                      title="Minimum size for a 60 minute episode">
+                </span>
+            </div>
+            <div class="pull-right">
+                <span class="label label-warning x-max-thirty"
+                      name="thirtyMinuteMaxSize"
+                      title="Maximum size for a 30 minute episode">
+                </span>
+                <span class="label label-info x-max-sixty"
+                      name="sixtyMinuteMaxSize"
+                      title="Maximum size for a 60 minute episode">
+                </span>            
+            </div>
+        </div>
+    </span>
\ No newline at end of file
diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionView.js b/src/UI/Settings/Quality/Definition/QualityDefinitionView.js
new file mode 100644
index 000000000..9b0465fe9
--- /dev/null
+++ b/src/UI/Settings/Quality/Definition/QualityDefinitionView.js
@@ -0,0 +1,86 @@
+'use strict';
+
+define(
+    [
+        'marionette',
+        'Mixins/AsModelBoundView',
+        'filesize',
+        'jquery-ui'
+    ], function (Marionette, AsModelBoundView, fileSize) {
+
+        var view = Marionette.ItemView.extend({
+            template: 'Settings/Quality/Definition/QualityDefinitionTemplate',
+            className: 'row',
+
+            ui: {
+                title              : '.x-title',
+                sizeSlider         : '.x-slider',
+                thirtyMinuteMinSize: '.x-min-thirty',
+                sixtyMinuteMinSize : '.x-min-sixty',
+                thirtyMinuteMaxSize: '.x-max-thirty',
+                sixtyMinuteMaxSize : '.x-max-sixty'
+            },
+
+            events: {
+                'change .x-title': '_updateTitle',
+                'slide .x-slider': '_updateSize'
+            },
+
+            initialize: function (options) {
+                this.qualityProfileCollection = options.qualityProfiles;
+                this.filesize = fileSize;
+            },
+
+            onRender: function () {
+                this.ui.sizeSlider.slider({
+                    range       : true,
+                    min         : 0,
+                    max         : 200,
+                    values      : [ this.model.get('minSize'), this.model.get('maxSize') ],
+                });     
+                
+                this._changeSize();     
+            },
+            
+            _updateTitle: function() {
+                this.model.set('title', this.ui.title.val());
+            },
+
+            _updateSize: function (event, ui) {
+                this.model.set('minSize', ui.values[0]);
+                this.model.set('maxSize', ui.values[1]);
+                
+                this._changeSize();
+            },
+            
+            _changeSize: function () {
+                var minSize = this.model.get('minSize');
+                var maxSize = this.model.get('maxSize');
+
+                {
+                    var minBytes = minSize * 1024 * 1024;
+                    var minThirty = fileSize(minBytes * 30, 1, false);
+                    var minSixty = fileSize(minBytes * 60, 1, false);
+                    
+                    this.ui.thirtyMinuteMinSize.html(minThirty);
+                    this.ui.sixtyMinuteMinSize.html(minSixty);
+                }
+                
+                {
+                    var maxBytes = maxSize * 1024 * 1024;
+                    var maxThirty = fileSize(maxBytes * 30, 1, false);
+                    var maxSixty = fileSize(maxBytes * 60, 1, false);
+                    
+                    this.ui.thirtyMinuteMaxSize.html(maxThirty);
+                    this.ui.sixtyMinuteMaxSize.html(maxSixty);
+                }
+                
+                /*if (parseInt(maxSize, 10) === 0) {
+                    thirty = 'No Limit';
+                    sixty = 'No Limit';
+                }*/
+            }
+        });
+
+        return AsModelBoundView.call(view);
+    });
diff --git a/src/UI/Settings/Quality/Profile/AllowedLabeler.js b/src/UI/Settings/Quality/Profile/AllowedLabeler.js
index fed162e17..ab09626e9 100644
--- a/src/UI/Settings/Quality/Profile/AllowedLabeler.js
+++ b/src/UI/Settings/Quality/Profile/AllowedLabeler.js
@@ -7,13 +7,14 @@ define(
         Handlebars.registerHelper('allowedLabeler', function () {
             var ret = '';
             var cutoff = this.cutoff;
-            _.each(this.allowed, function (allowed) {
-                if (allowed.id === cutoff.id) {
-                    ret += '<span class="label label-info" title="Cutoff">' + allowed.name + '</span> ';
-                }
-
-                else {
-                    ret += '<span class="label">' + allowed.name + '</span> ';
+            _.each(this.items, function (item) {
+                if (item.allowed) {
+                    if (item.quality.id === cutoff.id) {
+                        ret += '<span class="label label-info" title="Cutoff">' + item.quality.name + '</span>&nbsp;';
+                    }
+                    else {
+                        ret += '<span class="label">' + item.quality.name + '</span>&nbsp;';
+                    }
                 }
             });
 
diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js
new file mode 100644
index 000000000..dbd101a84
--- /dev/null
+++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js
@@ -0,0 +1,9 @@
+'use strict';
+define(
+    [
+        'marionette'
+    ], function (Marionette) {
+        return Marionette.ItemView.extend({
+            template : 'Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate'
+        });
+    });
diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate.html b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate.html
new file mode 100644
index 000000000..33cc7d2f5
--- /dev/null
+++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate.html
@@ -0,0 +1,3 @@
+<i class="select-handle pull-left x-select" />
+<span class="quality-label">{{quality.name}}</span>
+<i class="drag-handle pull-right icon-reorder advanced-setting x-drag-handle" />
diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js
new file mode 100644
index 000000000..1ef9825d5
--- /dev/null
+++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js
@@ -0,0 +1,108 @@
+'use strict';
+define(
+    [
+        'underscore',
+        'vent',
+        'marionette',
+        'backbone',
+        'Settings/Quality/Profile/Edit/EditQualityProfileItemView',
+        'Settings/Quality/Profile/Edit/QualitySortableCollectionView',
+        'Settings/Quality/Profile/Edit/EditQualityProfileView',
+        'Config'
+    ], function (_, vent, Marionette, Backbone, EditQualityProfileItemView, QualitySortableCollectionView, EditQualityProfileView, Config) {
+
+        return Marionette.Layout.extend({
+            template: 'Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate',
+
+            regions: {
+                fields   : '#x-fields',
+                qualities: '#x-qualities'
+            },
+
+            events: {
+                'click .x-save'  : '_saveQualityProfile',
+                'click .x-cancel': '_cancelQualityProfile'
+            },
+
+            initialize: function (options) {
+                this.profileCollection = options.profileCollection;
+                this.itemsCollection = new Backbone.Collection(_.toArray(this.model.get('items')).reverse());
+            },
+
+            onShow: function () {
+                this.fieldsView = new EditQualityProfileView({ model: this.model });
+                this._showFieldsView();
+
+                this.sortableListView = new QualitySortableCollectionView({
+                    selectable      : true,
+                    selectMultiple  : true,
+                    clickToSelect   : true,
+                    clickToToggle   : true,
+                    sortable        : Config.getValueBoolean(Config.Keys.AdvancedSettings, false),
+
+                    sortableOptions : {
+                        handle: '.x-drag-handle'
+                    },
+
+                    collection: this.itemsCollection,
+                    model     : this.model
+                });
+
+                this.sortableListView.setSelectedModels(this.itemsCollection.filter(function(item) { return item.get('allowed') === true; }));
+                this.qualities.show(this.sortableListView);
+
+                this.listenTo(this.sortableListView, 'selectionChanged', this._selectionChanged);
+                this.listenTo(this.sortableListView, 'sortStop', this._updateModel);
+            },
+            
+            _selectionChanged: function(newSelectedModels, oldSelectedModels) {
+                var addedModels = _.difference(newSelectedModels, oldSelectedModels);
+                var removeModels = _.difference(oldSelectedModels, newSelectedModels);
+                
+                _.each(removeModels, function(item) { item.set('allowed', false); });
+                _.each(addedModels, function(item) { item.set('allowed', true); });
+                
+                this._updateModel();
+            },
+            
+            _updateModel: function() {            
+                this.model.set('items', this.itemsCollection.toJSON().reverse());
+
+                this._showFieldsView();
+            },
+            
+            _saveQualityProfile: function () {
+                var self = this;
+                var cutoff = this.fieldsView.getCutoff();
+                this.model.set('cutoff', cutoff);
+
+                var promise = this.model.save();
+
+                if (promise) {
+                    promise.done(function () {
+                        self.profileCollection.add(self.model, { merge: true });
+                        vent.trigger(vent.Commands.CloseModalCommand);
+                    });
+                }
+            },
+
+            _cancelQualityProfile: function () {
+                if (!this.model.has('id')) {
+                    vent.trigger(vent.Commands.CloseModalCommand);
+                    return;
+                }
+
+                var promise = this.model.fetch();
+
+                if (promise) {
+                    promise.done(function () {
+                        vent.trigger(vent.Commands.CloseModalCommand);
+                    });
+                }
+            },
+
+            _showFieldsView: function () {
+                this.fields.show(this.fieldsView);
+            }
+        });
+    });
diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html
new file mode 100644
index 000000000..7f6f1359f
--- /dev/null
+++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html
@@ -0,0 +1,29 @@
+<div class="modal-header">
+    <button type="button" class="close x-cancel" aria-hidden="true">&times;</button>
+    {{#if id}}
+        <h3>Edit</h3>
+    {{else}}
+        <h3>Add</h3>
+    {{/if}}
+</div>
+<div class="modal-body">
+    <div class="form-horizontal">
+        <div id="x-fields"></div>
+        <div class="control-group">
+            <label class="control-label">Qualities</label>
+            <div class="controls qualities-controls">
+                <span id="x-qualities"></span>
+                <span class="help-inline">
+                    <i class="icon-nd-form-info" title="Qualities higher in the list are more preferred. Only checked qualities will be wanted."/>
+                </span>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="modal-footer">
+    {{#if id}}
+        <button class="btn btn-danger pull-left x-delete">delete</button>
+    {{/if}}
+    <button class="btn x-cancel">cancel</button>
+    <button class="btn btn-primary x-save">save</button>
+</div>
diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js
new file mode 100644
index 000000000..1365de61c
--- /dev/null
+++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js
@@ -0,0 +1,26 @@
+'use strict';
+define(
+    [
+        'underscore',
+        'marionette',
+        'Mixins/AsModelBoundView',
+        'Mixins/AsValidatedView'
+    ], function (_, Marionette, AsModelBoundView, AsValidatedView) {
+
+        var view = Marionette.ItemView.extend({
+            template: 'Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate',
+
+            ui: {
+                cutoff   : '.x-cutoff'
+            },
+
+            getCutoff: function () {
+                var self = this;
+
+                return _.findWhere(_.pluck(this.model.get('items'), 'quality'), { id: parseInt(self.ui.cutoff.val(), 10)});
+            }
+        });
+
+        AsValidatedView.call(view);
+        return AsModelBoundView.call(view);
+    });
diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html
new file mode 100644
index 000000000..87ba4deef
--- /dev/null
+++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html
@@ -0,0 +1,21 @@
+<div class="control-group">
+    <label class="control-label">Name</label>
+    <div class="controls">
+        <input type="text" name="name">
+    </div>
+</div>
+<div class="control-group">
+    <label class="control-label">Cutoff</label>
+    <div class="controls">
+        <select class="x-cutoff" name="cutoff.id" validation-name="cutoff">
+            {{#eachReverse items}}
+            {{#if allowed}}
+            <option value="{{quality.id}}">{{quality.name}}</option>
+            {{/if}}
+            {{/eachReverse}}
+        </select>
+        <span class="help-inline">
+            <i class="icon-nd-form-info" title="Once this quality is reached NzbDrone will no longer download episodes"/>
+        </span>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js b/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js
new file mode 100644
index 000000000..03693ec29
--- /dev/null
+++ b/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js
@@ -0,0 +1,22 @@
+'use strict';
+define(
+    [
+        'backbone.collectionview',
+        'Settings/Quality/Profile/Edit/EditQualityProfileItemView'
+    ], function (BackboneSortableCollectionView, EditQualityProfileItemView) {
+        return BackboneSortableCollectionView.extend({
+
+            className: 'qualities',
+            modelView: EditQualityProfileItemView,
+
+            attributes: {
+                'validation-name': 'items'
+            },
+
+            events: {
+                'click li, td' : '_listItem_onMousedown',
+                'dblclick li, td'  : '_listItem_onDoubleClick',
+                'keydown'          : '_onKeydown'
+            }
+        });
+    });
diff --git a/src/UI/Settings/Quality/Profile/EditQualityProfileTemplate.html b/src/UI/Settings/Quality/Profile/EditQualityProfileTemplate.html
deleted file mode 100644
index 5f65e44b5..000000000
--- a/src/UI/Settings/Quality/Profile/EditQualityProfileTemplate.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<div class="modal-header">
-    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-    {{#if id}}
-        <h3>Edit</h3>
-    {{else}}
-        <h3>Add</h3>
-    {{/if}}
-</div>
-<div class="modal-body">
-    <div class="form-horizontal">
-        <div class="control-group">
-            <label class="control-label">Name</label>
-            <div class="controls">
-                <input type="text" name="name">
-            </div>
-        </div>
-        <div class="control-group">
-            <label class="control-label">Cutoff</label>
-            <div class="controls">
-                <select class="x-cutoff" name="cutoff.id" validation-name="cutoff">
-                    {{#each allowed}}
-                    <option value="{{id}}">{{name}}</option>
-                    {{/each}}
-                </select>
-                <span class="help-inline">
-                    <i class="icon-nd-form-info" title="Once this quality is reached NzbDrone will no longer download episodes"/>
-                </span>
-            </div>
-        </div>
-    </div>
-    <div>
-        <div class="offset1 span3">
-            <h3>Available</h3>
-            <select multiple="multiple" class="x-available-list">
-                {{#each available}}
-                <option value="{{id}}">{{name}}</option>
-                {{/each}}
-            </select>
-        </div>
-        <div class="span3">
-            <div class="control-group">
-                <div class="controls">
-                    <h3>Allowed</h3>
-                    <select multiple="multiple" class="x-allowed-list" validation-name="allowed">
-                        {{#each allowed}}
-                        <option value="{{id}}">{{name}}</option>
-                        {{/each}}
-                    </select>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>
-<div class="modal-footer">
-    {{#if id}}
-        <button class="btn btn-danger pull-left x-delete">delete</button>
-    {{/if}}
-    <button class="btn" data-dismiss="modal">cancel</button>
-    <button class="btn btn-primary x-save">save</button>
-</div>
diff --git a/src/UI/Settings/Quality/Profile/EditQualityProfileView.js b/src/UI/Settings/Quality/Profile/EditQualityProfileView.js
deleted file mode 100644
index 503d820fc..000000000
--- a/src/UI/Settings/Quality/Profile/EditQualityProfileView.js
+++ /dev/null
@@ -1,83 +0,0 @@
-'use strict';
-define(
-    [
-        'vent',
-        'marionette',
-        'backbone',
-        'Mixins/AsModelBoundView',
-        'Mixins/AsValidatedView',
-        'underscore'
-    ], function (vent, Marionette, Backbone, AsModelBoundView, AsValidatedView, _) {
-
-        var view = Marionette.ItemView.extend({
-            template: 'Settings/Quality/Profile/EditQualityProfileTemplate',
-
-            ui: {
-                cutoff: '.x-cutoff'
-            },
-
-            events: {
-                'click .x-save'             : '_saveQualityProfile',
-                'dblclick .x-available-list': '_moveQuality',
-                'dblclick .x-allowed-list'  : '_moveQuality'
-            },
-
-            initialize: function (options) {
-                this.profileCollection = options.profileCollection;
-            },
-
-            _moveQuality: function (event) {
-
-                var quality;
-                var qualityId = event.target.value;
-                var availableCollection = new Backbone.Collection(this.model.get('available'));
-                availableCollection.comparator = function (model) {
-                    return model.get('weight');
-                };
-
-                var allowedCollection = new Backbone.Collection(this.model.get('allowed'));
-                allowedCollection.comparator = function (model) {
-                    return model.get('weight');
-                };
-
-                if (availableCollection.get(qualityId)) {
-                    quality = availableCollection.get(qualityId);
-                    availableCollection.remove(quality);
-                    allowedCollection.add(quality);
-                }
-                else if (allowedCollection.get(qualityId)) {
-                    quality = allowedCollection.get(qualityId);
-
-                    allowedCollection.remove(quality);
-                    availableCollection.add(quality);
-                }
-                else {
-                    throw 'couldnt find quality id ' + qualityId;
-                }
-
-                this.model.set('available', availableCollection.toJSON());
-                this.model.set('allowed', allowedCollection.toJSON());
-
-                this.render();
-            },
-
-            _saveQualityProfile: function () {
-                var self = this;
-                var cutoff = _.findWhere(this.model.get('allowed'), { id: parseInt(this.ui.cutoff.val(), 10)});
-                this.model.set('cutoff', cutoff);
-
-                var promise = this.model.save();
-
-                if (promise) {
-                    promise.done(function () {
-                        self.profileCollection.add(self.model, { merge: true });
-                        vent.trigger(vent.Commands.CloseModalCommand);
-                    });
-                }
-            }
-        });
-
-        AsValidatedView.call(view);
-        return AsModelBoundView.call(view);
-
-    });
diff --git a/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js b/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js
index 066bf986e..b3f5e12af 100644
--- a/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js
+++ b/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js
@@ -3,7 +3,7 @@
 define(['AppLayout',
         'marionette',
         'Settings/Quality/Profile/QualityProfileView',
-        'Settings/Quality/Profile/EditQualityProfileView',
+        'Settings/Quality/Profile/Edit/EditQualityProfileLayout',
         'Settings/Quality/Profile/QualityProfileSchemaCollection',
         'underscore'
 ], function (AppLayout, Marionette, QualityProfileView, EditProfileView, ProfileCollection, _) {
diff --git a/src/UI/Settings/Quality/Profile/QualityProfileView.js b/src/UI/Settings/Quality/Profile/QualityProfileView.js
index 439ad62f4..4bf9e466d 100644
--- a/src/UI/Settings/Quality/Profile/QualityProfileView.js
+++ b/src/UI/Settings/Quality/Profile/QualityProfileView.js
@@ -4,7 +4,7 @@ define(
     [
         'AppLayout',
         'marionette',
-        'Settings/Quality/Profile/EditQualityProfileView',
+        'Settings/Quality/Profile/Edit/EditQualityProfileLayout',
         'Settings/Quality/Profile/DeleteView',
         'Series/SeriesCollection',
         'Mixins/AsModelBoundView',
@@ -13,7 +13,7 @@ define(
     ], function (AppLayout, Marionette, EditProfileView, DeleteProfileView, SeriesCollection, AsModelBoundView) {
 
         var view = Marionette.ItemView.extend({
-            template: 'Settings/Quality/Profile/QualityProfileTemplate',
+            template: 'Settings/Quality/Profile/QualityProfileViewTemplate',
             tagName : 'li',
 
             ui: {
diff --git a/src/UI/Settings/Quality/Profile/QualityProfileTemplate.html b/src/UI/Settings/Quality/Profile/QualityProfileViewTemplate.html
similarity index 100%
rename from src/UI/Settings/Quality/Profile/QualityProfileTemplate.html
rename to src/UI/Settings/Quality/Profile/QualityProfileViewTemplate.html
diff --git a/src/UI/Settings/Quality/QualityLayout.js b/src/UI/Settings/Quality/QualityLayout.js
index 09d4aca44..c80486209 100644
--- a/src/UI/Settings/Quality/QualityLayout.js
+++ b/src/UI/Settings/Quality/QualityLayout.js
@@ -5,27 +5,27 @@ define(
         'marionette',
         'Quality/QualityProfileCollection',
         'Settings/Quality/Profile/QualityProfileCollectionView',
-        'Quality/QualitySizeCollection',
-        'Settings/Quality/Size/QualitySizeCollectionView'
-    ], function (Marionette, QualityProfileCollection, QualityProfileCollectionView, QualitySizeCollection, QualitySizeCollectionView) {
+        'Quality/QualityDefinitionCollection',
+        'Settings/Quality/Definition/QualityDefinitionCollectionView'
+    ], function (Marionette, QualityProfileCollection, QualityProfileCollectionView, QualityDefinitionCollection, QualityDefinitionCollectionView) {
         return Marionette.Layout.extend({
             template: 'Settings/Quality/QualityLayoutTemplate',
 
             regions: {
-                qualityProfile : '#quality-profile',
-                qualitySize    : '#quality-size'
+                qualityProfile    : '#quality-profile',
+                qualityDefinition : '#quality-definition'
             },
 
             initialize: function (options) {
                 this.settings = options.settings;
                 QualityProfileCollection.fetch();
-                this.qualitySizeCollection = new QualitySizeCollection();
-                this.qualitySizeCollection.fetch();
+                this.qualityDefinitionCollection = new QualityDefinitionCollection();
+                this.qualityDefinitionCollection.fetch();
             },
 
             onShow: function () {
                 this.qualityProfile.show(new QualityProfileCollectionView({collection: QualityProfileCollection}));
-                this.qualitySize.show(new QualitySizeCollectionView({collection: this.qualitySizeCollection}));
+                this.qualityDefinition.show(new QualityDefinitionCollectionView({collection: this.qualityDefinitionCollection}));
             }
         });
     });
diff --git a/src/UI/Settings/Quality/QualityLayoutTemplate.html b/src/UI/Settings/Quality/QualityLayoutTemplate.html
index 5954e5eb4..9bd521d37 100644
--- a/src/UI/Settings/Quality/QualityLayoutTemplate.html
+++ b/src/UI/Settings/Quality/QualityLayoutTemplate.html
@@ -5,5 +5,5 @@
 <br/>
 
 <div class="row advanced-setting">
-    <div class="span12" id="quality-size"/>
+    <div class="span12" id="quality-definition"/>
 </div>
diff --git a/src/UI/Settings/Quality/Size/QualitySizeCollectionTemplate.html b/src/UI/Settings/Quality/Size/QualitySizeCollectionTemplate.html
deleted file mode 100644
index 6d406bdc0..000000000
--- a/src/UI/Settings/Quality/Size/QualitySizeCollectionTemplate.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<fieldset>
-    <legend>Quality Size Limits</legend>
-    <ul class="quality-sizes"/>
-</fieldset>
diff --git a/src/UI/Settings/Quality/Size/QualitySizeCollectionView.js b/src/UI/Settings/Quality/Size/QualitySizeCollectionView.js
deleted file mode 100644
index e27e01a41..000000000
--- a/src/UI/Settings/Quality/Size/QualitySizeCollectionView.js
+++ /dev/null
@@ -1,9 +0,0 @@
-'use strict';
-
-define(['marionette', 'Settings/Quality/Size/QualitySizeView'], function (Marionette, QualitySizeView) {
-    return Marionette.CompositeView.extend({
-        itemView         : QualitySizeView,
-        itemViewContainer: '.quality-sizes',
-        template         : 'Settings/Quality/Size/QualitySizeCollectionTemplate'
-    });
-});
diff --git a/src/UI/Settings/Quality/Size/QualitySizeTemplate.html b/src/UI/Settings/Quality/Size/QualitySizeTemplate.html
deleted file mode 100644
index 657922e73..000000000
--- a/src/UI/Settings/Quality/Size/QualitySizeTemplate.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<div class="quality-size-item">
-    <h3 class="center-block">{{name}}</h3>
-    <div class="size">
-        <div class="size-value-wrapper">
-            <div>
-                <span class="label label-large label-warning x-size-thirty"
-                      name="thirtyMinuteSize"
-                      title="Maximum size for a 30 minute episode">
-                </span>
-            </div>
-            <div>
-                <span class="label label-large label-info x-size-sixty"
-                      name="sixtyMinuteSize"
-                      title="Maximum size for a 60 minute episode">
-                </span>
-            </div>
-        </div>
-        <input type="text" name="maxSize" class="knob x-knob" />
-    </div>
-</div>
\ No newline at end of file
diff --git a/src/UI/Settings/Quality/Size/QualitySizeView.js b/src/UI/Settings/Quality/Size/QualitySizeView.js
deleted file mode 100644
index 215304ba0..000000000
--- a/src/UI/Settings/Quality/Size/QualitySizeView.js
+++ /dev/null
@@ -1,61 +0,0 @@
-'use strict';
-
-define(
-    [
-        'marionette',
-        'Mixins/AsModelBoundView',
-        'filesize',
-        'jquery.knob'
-    ], function (Marionette, AsModelBoundView, fileSize) {
-
-        var view = Marionette.ItemView.extend({
-            template: 'Settings/Quality/Size/QualitySizeTemplate',
-            tagName : 'li',
-
-            ui: {
-                knob            : '.x-knob',
-                thirtyMinuteSize: '.x-size-thirty',
-                sixtyMinuteSize : '.x-size-sixty'
-            },
-
-            events: {
-                'change .x-knob': '_changeMaxSize'
-            },
-
-            initialize: function (options) {
-                this.qualityProfileCollection = options.qualityProfiles;
-                this.filesize = fileSize;
-            },
-
-            onRender: function () {
-                this.ui.knob.knob({
-                    min         : 0,
-                    max         : 200,
-                    step        : 1,
-                    cursor      : 25,
-                    width       : 150,
-                    stopper     : true,
-                    displayInput: false
-                });
-
-                this._changeMaxSize();
-            },
-
-            _changeMaxSize: function () {
-                var maxSize = this.model.get('maxSize');
-                var bytes = maxSize * 1024 * 1024;
-                var thirty = fileSize(bytes * 30, 1, false);
-                var sixty = fileSize(bytes * 60, 1, false);
-
-                if (parseInt(maxSize, 10) === 0) {
-                    thirty = 'No Limit';
-                    sixty = 'No Limit';
-                }
-
-                this.ui.thirtyMinuteSize.html(thirty);
-                this.ui.sixtyMinuteSize.html(sixty);
-            }
-        });
-
-        return AsModelBoundView.call(view);
-    });
diff --git a/src/UI/Settings/Quality/quality.less b/src/UI/Settings/Quality/quality.less
index 95d9f3a4f..199f87201 100644
--- a/src/UI/Settings/Quality/quality.less
+++ b/src/UI/Settings/Quality/quality.less
@@ -1,6 +1,8 @@
 @import "../../Shared/Styles/card";
+@import "../../Content/Bootstrap/mixins";
+@import "../../Content/FontAwesome/font-awesome";
 
-.quality-profiles, .quality-sizes {
+.quality-profiles {
   li {
     display: inline-block;
     vertical-align: top;
@@ -35,41 +37,130 @@
   }
 }
 
-.quality-size-item {
+ul.qualities {
+  .user-select(none);
 
-  .card;
-  text-align: center;
+  min-height: 100px;
+  margin: 0;
+  padding: 0;
+  list-style-type: none;
+  outline: none;
+  width: 220px;
+  display: inline-block;
+  
+  li {
+    margin: 2px;
+    padding: 2px 4px;
+    line-height: 20px;
+    border: 1px solid #aaaaaa;
+    border-radius: 4px;   /* may need vendor varients */
+    background: #fafafa;
+    cursor: pointer;
 
-  width: 200px;
-  height: 210px;
-  padding: 10px 15px;
+    &.selected {
+      .select-handle {
+        opacity: 1.0;
+        cursor: pointer;
+      }
 
-  h3 {
-    margin-top: 0px;
-  }
+      .quality-label {
+        color: #444444;
+      }
 
-  .size {
-    position: relative;
-    height: 100px;
-    margin: 10px;
-    text-align: center;
-  }
+      .select-handle:before {
+        .icon(@check);
+      }
+    }
 
-  .knob {
-    box-shadow: none;
-  }
+    &:hover {
+      border-color: #888888;
+      background: #eeeeee;
 
-  .size-value-wrapper {
-    position: absolute;
-    top: 50px;
-    width: 100%;
+      .drag-handle {
+        opacity: 1.0;
+        cursor: move;
+      }
+    }
 
-    div {
-      margin-top: 2px;
+    .quality-label {
+      color: #c6c6c6;
+    }
+    .drag-handle, .select-handle {
+      opacity: 0.2;
+      line-height: 20px;
+      cursor: pointer;
+    }
+
+    .select-handle:before {
+      .icon(@check-empty);
+      display: inline-block;
+      width: 16px;
+      margin-top: 1px;
     }
   }
 }
 
-#quality-size {
-  overflow: hidden;
+.qualities-controls {
+  .help-inline {
+    vertical-align: top;
+    margin-top: 5px;
+  }
+}
+
+#quality-definition-list {
+
+  .quality-header .row {
+    font-weight: bold;
+    line-height: 40px;
+  }
+
+  .rows .row {
+    line-height: 30px;
+    border-top: 1px solid #ddd;
+    vertical-align: middle;
+    padding: 5px;
+
+    input {
+      margin-bottom: 0px;
+    }
+
+    .size-label-wrapper {
+      line-height: 20px;
+    }
+
+    .label {
+      min-width: 70px;
+      text-align: center;
+      margin: 0px 1px;
+      padding: 1px 4px;
+    }
+
+    .ui-slider {
+      position: relative;
+      text-align: left;
+      background-color: #f5f5f5;
+      border-radius: 3px;
+      border: 1px solid #ccc;
+      height: 8px;
+
+      .ui-slider-range {
+        position: absolute;
+        display: block;
+        background-color: #ddd;
+        height: 100%;
+      }
+
+      .ui-slider-handle {
+        position: absolute;
+        z-index: 2;
+        width: 6px;
+        height: 12px;
+        cursor: default;
+        background-color: #ccc;
+        border: 1px solid #aaa;
+        border-radius: 3px;
+        top: -3px;
+      }
+    }
+  }
 }
diff --git a/src/UI/Settings/SettingsLayout.js b/src/UI/Settings/SettingsLayout.js
index 88d10205c..2770feee5 100644
--- a/src/UI/Settings/SettingsLayout.js
+++ b/src/UI/Settings/SettingsLayout.js
@@ -1,6 +1,7 @@
 'use strict';
 define(
     [
+        'jquery',
         'vent',
         'marionette',
         'backbone',
@@ -17,7 +18,8 @@ define(
         'Settings/General/GeneralView',
         'Shared/LoadingView',
         'Config'
-    ], function (vent,
+    ], function ($,
+                 vent,
                  Marionette,
                  Backbone,
                  SettingsModel,
@@ -64,7 +66,7 @@ define(
                 'click .x-notifications-tab'    : '_showNotifications',
                 'click .x-general-tab'          : '_showGeneral',
                 'click .x-save-settings'        : '_save',
-                'change .x-advanced-settings'     : '_toggleAdvancedSettings'
+                'change .x-advanced-settings'   : '_toggleAdvancedSettings'
             },
 
             initialize: function (options) {
@@ -192,24 +194,24 @@ define(
             },
 
             _setAdvancedSettingsState: function () {
-                var checked = Config.getValueBoolean('advancedSettings');
+                var checked = Config.getValueBoolean(Config.Keys.AdvancedSettings);
                 this.ui.advancedSettings.prop('checked', checked);
 
                 if (checked) {
-                    this.$el.addClass('show-advanced-settings');
+                    $('body').addClass('show-advanced-settings');
                 }
             },
 
             _toggleAdvancedSettings: function () {
                 var checked = this.ui.advancedSettings.prop('checked');
-                Config.setValue('advancedSettings', checked);
+                Config.setValue(Config.Keys.AdvancedSettings, checked);
 
                 if (checked) {
-                    this.$el.addClass('show-advanced-settings');
+                    $('body').addClass('show-advanced-settings');
                 }
 
                 else {
-                    this.$el.removeClass('show-advanced-settings');
+                    $('body').removeClass('show-advanced-settings');
                 }
             }
         });
diff --git a/src/UI/Shared/Toolbar/Radio/RadioButtonView.js b/src/UI/Shared/Toolbar/Radio/RadioButtonView.js
index baa67a5da..af2f0c903 100644
--- a/src/UI/Shared/Toolbar/Radio/RadioButtonView.js
+++ b/src/UI/Shared/Toolbar/Radio/RadioButtonView.js
@@ -50,7 +50,6 @@ define(
                     throw 'ownerContext must be set.';
                 }
 
-
                 var callback = this.model.get('callback');
                 if (callback) {
                     callback.call(this.model.ownerContext, this);
diff --git a/src/UI/System/SystemLayout.js b/src/UI/System/SystemLayout.js
index d4a34ae12..7a5d51368 100644
--- a/src/UI/System/SystemLayout.js
+++ b/src/UI/System/SystemLayout.js
@@ -5,12 +5,14 @@ define(
         'marionette',
         'System/Info/SystemInfoLayout',
         'System/Logs/LogsLayout',
-        'System/Update/UpdateLayout'
+        'System/Update/UpdateLayout',
+        'Commands/CommandController'
     ], function (Backbone,
                  Marionette,
                  SystemInfoLayout,
                  LogsLayout,
-                 UpdateLayout) {
+                 UpdateLayout,
+                 CommandController) {
         return Marionette.Layout.extend({
             template: 'System/SystemLayoutTemplate',
 
@@ -27,9 +29,11 @@ define(
             },
 
             events: {
-                'click .x-info-tab'  : '_showInfo',
+                'click .x-info-tab'   : '_showInfo',
                 'click .x-logs-tab'   : '_showLogs',
-                'click .x-updates-tab': '_showUpdates'
+                'click .x-updates-tab': '_showUpdates',
+                'click .x-shutdown'   : '_shutdown',
+                'click .x-restart'    : '_restart'
             },
 
             initialize: function (options) {
@@ -83,6 +87,18 @@ define(
                 this.updates.show(new UpdateLayout());
                 this.ui.updatesTab.tab('show');
                 this._navigate('system/updates');
+            },
+
+            _shutdown: function () {
+                CommandController.Execute('shutdown', {
+                    name : 'shutdown'
+                });
+            },
+
+            _restart: function () {
+                CommandController.Execute('restart', {
+                    name : 'restart'
+                });
             }
         });
     });
diff --git a/src/UI/System/SystemLayoutTemplate.html b/src/UI/System/SystemLayoutTemplate.html
index d95a3996b..1ee3a2349 100644
--- a/src/UI/System/SystemLayoutTemplate.html
+++ b/src/UI/System/SystemLayoutTemplate.html
@@ -2,6 +2,16 @@
     <li><a href="#info" class="x-info-tab no-router">Info</a></li>
     <li><a href="#logs" class="x-logs-tab no-router">Logs</a></li>
     <li><a href="#updates" class="x-updates-tab no-router">Updates</a></li>
+    <li class="lifecycle-controls pull-right">
+        <div class="btn-group">
+            <button class="btn btn-icon-only x-shutdown" title="Shutdown" data-container="body">
+                <i class="icon-nd-shutdown"></i>
+            </button>
+            <button class="btn btn-icon-only x-restart" title="Restart" data-container="body">
+                <i class="icon-nd-restart"></i>
+            </button>
+        </div>
+    </li>
 </ul>
 
 <div class="tab-content">
diff --git a/src/UI/app.js b/src/UI/app.js
index d8dde2b5a..c3cb3bb57 100644
--- a/src/UI/app.js
+++ b/src/UI/app.js
@@ -2,30 +2,32 @@
 require.config({
 
     paths: {
-        'backbone'            : 'JsLibraries/backbone',
-        'moment'              : 'JsLibraries/moment',
-        'filesize'            : 'JsLibraries/filesize',
-        'handlebars'          : 'JsLibraries/handlebars.runtime',
-        'handlebars.helpers'  : 'JsLibraries/handlebars.helpers',
-        'bootstrap'           : 'JsLibraries/bootstrap',
-        'backbone.deepmodel'  : 'JsLibraries/backbone.deep.model',
-        'backbone.pageable'   : 'JsLibraries/backbone.pageable',
-        'backbone.validation' : 'JsLibraries/backbone.validation',
-        'backbone.modelbinder': 'JsLibraries/backbone.modelbinder',
-        'backgrid'            : 'JsLibraries/backbone.backgrid',
-        'backgrid.paginator'  : 'JsLibraries/backbone.backgrid.paginator',
-        'backgrid.selectall'  : 'JsLibraries/backbone.backgrid.selectall',
-        'fullcalendar'        : 'JsLibraries/fullcalendar',
-        'backstrech'          : 'JsLibraries/jquery.backstretch',
-        'underscore'          : 'JsLibraries/lodash.underscore',
-        'marionette'          : 'JsLibraries/backbone.marionette',
-        'signalR'             : 'JsLibraries/jquery.signalR',
-        'jquery.knob'         : 'JsLibraries/jquery.knob',
+        'backbone'                : 'JsLibraries/backbone',
+        'moment'                  : 'JsLibraries/moment',
+        'filesize'                : 'JsLibraries/filesize',
+        'handlebars'              : 'JsLibraries/handlebars.runtime',
+        'handlebars.helpers'      : 'JsLibraries/handlebars.helpers',
+        'bootstrap'               : 'JsLibraries/bootstrap',
+        'backbone.deepmodel'      : 'JsLibraries/backbone.deep.model',
+        'backbone.pageable'       : 'JsLibraries/backbone.pageable',
+        'backbone.validation'     : 'JsLibraries/backbone.validation',
+        'backbone.modelbinder'    : 'JsLibraries/backbone.modelbinder',
+        'backbone.collectionview' : 'JsLibraries/backbone.collectionview',
+        'backgrid'                : 'JsLibraries/backbone.backgrid',
+        'backgrid.paginator'      : 'JsLibraries/backbone.backgrid.paginator',
+        'backgrid.selectall'      : 'JsLibraries/backbone.backgrid.selectall',
+        'fullcalendar'            : 'JsLibraries/fullcalendar',
+        'backstrech'              : 'JsLibraries/jquery.backstretch',
+        'underscore'              : 'JsLibraries/lodash.underscore',
+        'marionette'              : 'JsLibraries/backbone.marionette',
+        'signalR'                 : 'JsLibraries/jquery.signalR',
+        'jquery-ui'               : 'JsLibraries/jquery-ui',
+        'jquery.knob'             : 'JsLibraries/jquery.knob',
         'jquery.easypiechart' : 'JsLibraries/jquery.easypiechart',
-        'jquery.dotdotdot'    : 'JsLibraries/jquery.dotdotdot',
-        'messenger'           : 'JsLibraries/messenger',
-        'jquery'              : 'JsLibraries/jquery',
-        'libs'                : 'JsLibraries/',
+        'jquery.dotdotdot'        : 'JsLibraries/jquery.dotdotdot',
+        'messenger'               : 'JsLibraries/messenger',
+        'jquery'                  : 'JsLibraries/jquery',
+        'libs'                    : 'JsLibraries/',
 
         'api': 'Require/require.api'
     },
@@ -106,6 +108,12 @@ require.config({
 
             }
         },
+        'jquery-ui'           : {
+            deps:
+                [
+                    'jquery'
+                ]
+        },
         'jquery.knob'         : {
             deps:
                 [
@@ -150,6 +158,14 @@ require.config({
                     'backbone'
                 ]
         },
+        'backbone.collectionview': {
+            deps:
+                [
+                    'backbone',
+                    'jquery-ui'
+                ],                
+            exports: 'Backbone.CollectionView'
+        },
         backgrid              : {
             deps:
                 [