diff --git a/NzbDrone.Core.Test/DownloadProviderTest.cs b/NzbDrone.Core.Test/DownloadProviderTest.cs index e0d956236..353f42c71 100644 --- a/NzbDrone.Core.Test/DownloadProviderTest.cs +++ b/NzbDrone.Core.Test/DownloadProviderTest.cs @@ -55,10 +55,12 @@ namespace NzbDrone.Core.Test mocker.GetMock() .Setup(c => c.MarkEpisodeAsFetched(12)); - mocker.GetMock() .Setup(c => c.MarkEpisodeAsFetched(99)); + mocker.GetMock() + .Setup(c => c.OnGrab(It.IsAny())); + mocker.Resolve().DownloadReport(parseResult); mocker.VerifyAllMocks(); diff --git a/NzbDrone.Core.Test/EpisodeProviderTest.cs b/NzbDrone.Core.Test/EpisodeProviderTest.cs index 03e12b013..a4f078c5f 100644 --- a/NzbDrone.Core.Test/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/EpisodeProviderTest.cs @@ -991,5 +991,246 @@ namespace NzbDrone.Core.Test mocker.VerifyAllMocks(); } + + [Test] + public void IgnoreEpisode_Ignore() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var episodes = Builder.CreateListOfSize(4) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Have(c => c.Ignored = false) + .Build().ToList(); + + episodes.ForEach(c => db.Insert(c)); + + //Act + mocker.Resolve().SetEpisodeIgnore(1, true); + + //Assert + var episodesInDb = db.Fetch(@"SELECT * FROM Episodes"); + + episodesInDb.Should().HaveCount(4); + episodesInDb.Where(e => e.Ignored).Should().HaveCount(1); + + mocker.VerifyAllMocks(); + } + + [Test] + public void IgnoreEpisode_RemoveIgnore() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var episodes = Builder.CreateListOfSize(4) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Have(c => c.Ignored = true) + .Build().ToList(); + + episodes.ForEach(c => db.Insert(c)); + + //Act + mocker.Resolve().SetEpisodeIgnore(1, false); + + //Assert + var episodesInDb = db.Fetch(@"SELECT * FROM Episodes"); + + episodesInDb.Should().HaveCount(4); + episodesInDb.Where(e => !e.Ignored).Should().HaveCount(1); + + mocker.VerifyAllMocks(); + } + + [Test] + public void IgnoreSeason_Ignore() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var episodes = Builder.CreateListOfSize(4) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Have(c => c.Ignored = false) + .Build().ToList(); + + episodes.ForEach(c => db.Insert(c)); + + //Act + mocker.Resolve().SetSeasonIgnore(10, 1, true); + + //Assert + var episodesInDb = db.Fetch(@"SELECT * FROM Episodes"); + + episodesInDb.Should().HaveCount(4); + episodesInDb.Where(e => e.Ignored).Should().HaveCount(4); + + mocker.VerifyAllMocks(); + } + + [Test] + public void IgnoreSeason_RemoveIgnore() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var episodes = Builder.CreateListOfSize(4) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Have(c => c.Ignored = true) + .Build().ToList(); + + episodes.ForEach(c => db.Insert(c)); + + //Act + mocker.Resolve().SetSeasonIgnore(10, 1, false); + + //Assert + var episodesInDb = db.Fetch(@"SELECT * FROM Episodes"); + + episodesInDb.Should().HaveCount(4); + episodesInDb.Where(e => !e.Ignored).Should().HaveCount(4); + + mocker.VerifyAllMocks(); + } + + [Test] + public void IgnoreSeason_Ignore_Half() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var episodes = Builder.CreateListOfSize(4) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .WhereTheFirst(2) + .Have(c => c.Ignored = false) + .AndTheRemaining() + .Have(c => c.Ignored = true) + .Build().ToList(); + + episodes.ForEach(c => db.Insert(c)); + + //Act + mocker.Resolve().SetSeasonIgnore(10, 1, true); + + //Assert + var episodesInDb = db.Fetch(@"SELECT * FROM Episodes"); + + episodesInDb.Should().HaveCount(4); + episodesInDb.Where(e => e.Ignored).Should().HaveCount(4); + + mocker.VerifyAllMocks(); + } + + [Test] + public void EpisodesWithoutFiles_no_specials() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var series = Builder.CreateNew() + .With(s => s.SeriesId = 10) + .Build(); + + var episodes = Builder.CreateListOfSize(4) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Have(c => c.AirDate = DateTime.Today.AddDays(-4)) + .Have(c => c.Ignored = true) + .WhereTheFirst(2) + .Have(c => c.EpisodeFileId = 0) + .WhereSection(1, 2) + .Have(c => c.Ignored = false) + .Build().ToList(); + + var specials = Builder.CreateListOfSize(2) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 0) + .Have(c => c.AirDate = DateTime.Today.AddDays(-4)) + .Have(c => c.EpisodeFileId = 0) + .WhereTheFirst(1) + .Have(c => c.Ignored = true) + .AndTheRemaining() + .Have(c => c.Ignored = false) + .Build().ToList(); + + db.Insert(series); + db.InsertMany(episodes); + db.InsertMany(specials); + + //Act + var missingFiles= mocker.Resolve().EpisodesWithoutFiles(false); + + //Assert + missingFiles.Should().HaveCount(1); + missingFiles.Where(e => e.EpisodeFileId == 0).Should().HaveCount(1); + + mocker.VerifyAllMocks(); + } + + [Test] + public void EpisodesWithoutFiles_with_specials() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var series = Builder.CreateNew() + .With(s => s.SeriesId = 10) + .Build(); + + var episodes = Builder.CreateListOfSize(4) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Have(c => c.AirDate = DateTime.Today.AddDays(-4)) + .Have(c => c.Ignored = true) + .WhereTheFirst(2) + .Have(c => c.EpisodeFileId = 0) + .WhereSection(1,2) + .Have(c => c.Ignored = false) + .Build().ToList(); + + var specials = Builder.CreateListOfSize(2) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 0) + .Have(c => c.AirDate = DateTime.Today.AddDays(-4)) + .Have(c => c.EpisodeFileId = 0) + .WhereTheFirst(1) + .Have(c => c.Ignored = true) + .AndTheRemaining() + .Have(c => c.Ignored = false) + .Build().ToList(); + + db.Insert(series); + db.InsertMany(episodes); + db.InsertMany(specials); + + //Act + var missingFiles = mocker.Resolve().EpisodesWithoutFiles(true); + + //Assert + missingFiles.Should().HaveCount(2); + missingFiles.Where(e => e.EpisodeFileId == 0).Should().HaveCount(2); + + mocker.VerifyAllMocks(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/ParserTest.cs b/NzbDrone.Core.Test/ParserTest.cs index d7073507a..12f82054f 100644 --- a/NzbDrone.Core.Test/ParserTest.cs +++ b/NzbDrone.Core.Test/ParserTest.cs @@ -41,6 +41,7 @@ namespace NzbDrone.Core.Test [TestCase("Adventure.Inc.S03E19.DVDRip.\"XviD\"-OSiTV", "Adventure.Inc", 3, 19)] [TestCase("Hawaii Five-0 (2010) - 1x05 - Nalowale (Forgotten/Missing)", "Hawaii Five-0 (2010)", 1, 5)] [TestCase("Hawaii Five-0 (2010) - 1x05 - Title", "Hawaii Five-0 (2010)", 1, 5)] + [TestCase("House - S06E13 - 5 to 9 [DVD]", "House", 6, 13)] public void ParseTitle_single(string postTitle, string title, int seasonNumber, int episodeNumber) { var result = Parser.ParseTitle(postTitle); @@ -131,7 +132,7 @@ namespace NzbDrone.Core.Test [TestCase("Two.and.a.Half.Men.103.104.720p.HDTV.X264-DIMENSION", "Two.and.a.Half.Men", 1, new[] { 3, 4 }, 2)] [TestCase("Weeds.S03E01.S03E02.720p.HDTV.X264-DIMENSION", "Weeds", 3, new[] { 1, 2 }, 2)] [TestCase("The Borgias S01e01 e02 ShoHD On Demand 1080i DD5 1 ALANiS", "The Borgias", 1, new[] { 1, 2 }, 2)] - [TestCase("Big Time Rush 1x01 to 10 480i DD2 0 Sianto", "Big Time Rush", 1, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 10)] + //[TestCase("Big Time Rush 1x01 to 10 480i DD2 0 Sianto", "Big Time Rush", 1, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 10)] [TestCase("White.Collar.2x04.2x05.720p.BluRay-FUTV", "White.Collar", 2, new[] { 4, 5 }, 2)] [TestCase("Desperate.Housewives.S07E22E23.720p.HDTV.X264-DIMENSION", "Desperate.Housewives", 7, new[] { 22, 23 }, 2)] [TestCase("S07E22 - 7x23 - And Lots of Security.. [HDTV].mkv", "", 7, new[] { 22, 23 }, 2)] @@ -203,6 +204,8 @@ namespace NzbDrone.Core.Test [TestCase("peri.od", "period")] [TestCase("this.^&%^**$%@#$!That", "thisthat")] [TestCase("test/test", "testtest")] + [TestCase("90210", "90210")] + [TestCase("24", "24")] public void Normalize_Title(string dirty, string clean) { var result = Parser.NormalizeTitle(dirty); diff --git a/NzbDrone.Core.Test/SeriesProviderTest.cs b/NzbDrone.Core.Test/SeriesProviderTest.cs index 63796e6ea..d2b271695 100644 --- a/NzbDrone.Core.Test/SeriesProviderTest.cs +++ b/NzbDrone.Core.Test/SeriesProviderTest.cs @@ -10,6 +10,7 @@ using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Repository; using NzbDrone.Core.Repository.Quality; using NzbDrone.Core.Test.Framework; +using PetaPoco; // ReSharper disable InconsistentNaming namespace NzbDrone.Core.Test @@ -350,5 +351,129 @@ namespace NzbDrone.Core.Test series.QualityProfile.Should().NotBeNull(); series.QualityProfileId.Should().Be(fakeQuality.QualityProfileId); } + + [Test] + public void SeriesPathExists_exact_match() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + var path = @"C:\Test\TV\30 Rock"; + + var fakeSeries = Builder.CreateListOfSize(10) + .WhereAll() + .Have(c => c.QualityProfileId = 1) + .WhereTheFirst(1) + .Have(c => c.Path = path) + .Build(); + var fakeQuality = Builder.CreateNew().Build(); + + db.InsertMany(fakeSeries); + db.Insert(fakeQuality); + + //Act + mocker.Resolve(); + //mocker.GetMock().Setup(s => s.Fetch(It.IsAny())).Returns( + //fakeSeries.ToList()); + + var result = mocker.Resolve().SeriesPathExists(path); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void SeriesPathExists_match() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + var path = @"C:\Test\TV\30 Rock"; + + var fakeSeries = Builder.CreateListOfSize(10) + .WhereAll() + .Have(c => c.QualityProfileId = 1) + .WhereTheFirst(1) + .Have(c => c.Path = path) + .Build(); + var fakeQuality = Builder.CreateNew().Build(); + + db.InsertMany(fakeSeries); + db.Insert(fakeQuality); + + //Act + mocker.Resolve(); + //mocker.GetMock().Setup(s => s.Fetch(It.IsAny())).Returns( + //fakeSeries.ToList()); + + var result = mocker.Resolve().SeriesPathExists(path.ToUpper()); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void SeriesPathExists_match_alt() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + var path = @"C:\Test\TV\The Simpsons"; + + var fakeSeries = Builder.CreateListOfSize(10) + .WhereAll() + .Have(c => c.QualityProfileId = 1) + .WhereTheFirst(1) + .Have(c => c.Path = path) + .Build(); + var fakeQuality = Builder.CreateNew().Build(); + + db.InsertMany(fakeSeries); + db.Insert(fakeQuality); + + //Act + mocker.Resolve(); + //mocker.GetMock().Setup(s => s.Fetch(It.IsAny())).Returns( + //fakeSeries.ToList()); + + var result = mocker.Resolve().SeriesPathExists(@"c:\Test\Tv\the sIMpsons"); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void SeriesPathExists_match_false() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + var path = @"C:\Test\TV\30 Rock"; + + var fakeSeries = Builder.CreateListOfSize(10) + .WhereAll() + .Have(c => c.QualityProfileId = 1) + .WhereTheFirst(1) + .Have(c => c.Path = path) + .Build(); + var fakeQuality = Builder.CreateNew().Build(); + + db.InsertMany(fakeSeries); + db.Insert(fakeQuality); + + //Act + mocker.Resolve(); + //mocker.GetMock().Setup(s => s.Fetch(It.IsAny())).Returns( + //fakeSeries.ToList()); + + var result = mocker.Resolve().SeriesPathExists(@"C:\Test\TV\Not A match"); + + //Assert + result.Should().BeFalse(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 102f7f667..993d96d13 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -62,9 +62,9 @@ namespace NzbDrone.Core _kernel.Get().SetupDefaultProfiles(); + BindExternalNotifications(); BindIndexers(); BindJobs(); - BindExternalNotifications(); } private static void BindKernel() @@ -111,8 +111,9 @@ namespace NzbDrone.Core private static void BindExternalNotifications() { - _kernel.Bind().To().InSingletonScope(); - var notifiers = _kernel.GetAll(); + _kernel.Bind().To(); + + var notifiers = _kernel.GetAll(); _kernel.Get().InitializeNotifiers(notifiers.ToList()); } diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20110726.cs b/NzbDrone.Core/Datastore/Migrations/Migration20110726.cs new file mode 100644 index 000000000..c0ed06601 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/Migration20110726.cs @@ -0,0 +1,30 @@ +using System; +using System.Data; +using Migrator.Framework; + +namespace NzbDrone.Core.Datastore.Migrations +{ + + [Migration(20110726)] + public class Migration20110726 : Migration + { + public override void Up() + { + Database.RemoveTable("ExternalNotificationSettings"); + + Database.AddTable("ExternalNotificationDefinitions", new[] + { + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column("Enable", DbType.Boolean, ColumnProperty.NotNull), + new Column("ExternalNotificationProviderType", DbType.String, ColumnProperty.NotNull), + new Column("Name", DbType.String, ColumnProperty.NotNull) + }); + } + + + public override void Down() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/Notification/BasicNotification.cs b/NzbDrone.Core/Model/Notification/BasicNotification.cs index f06172336..225dc97a7 100644 --- a/NzbDrone.Core/Model/Notification/BasicNotification.cs +++ b/NzbDrone.Core/Model/Notification/BasicNotification.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Model.Notification { public BasicNotification() { - Id = Guid.Empty; + Id = Guid.NewGuid(); } /// diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index bc1efb53f..6507a24a8 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -176,6 +176,7 @@ + @@ -203,8 +204,8 @@ - - + + @@ -224,7 +225,7 @@ - + diff --git a/NzbDrone.Core/Parser.cs b/NzbDrone.Core/Parser.cs index dc4a7e441..21e5f736d 100644 --- a/NzbDrone.Core/Parser.cs +++ b/NzbDrone.Core/Parser.cs @@ -19,11 +19,11 @@ namespace NzbDrone.Core RegexOptions.IgnoreCase | RegexOptions.Compiled), //Multi-Part episodes without a title (S01E05.S01E06) - new Regex(@"^(?:\W*S?(?\d{1,2}(?!\d+))(?:(?:\-|\.|[ex]|\s|\sto\s){1,2}(?\d{1,2}(?!\d+)))+){2,}\W?(?!\\)", + new Regex(@"^(?:\W*S?(?\d{1,2}(?!\d+))(?:(?:\-|\.|[ex]|\s){1,2}(?\d{1,2}(?!\d+)))+){2,}\W?(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled), //Single episodes or multi-episode (S01E05E06, S01E05-06, etc) - new Regex(@"^(?.+?)(?:\W+S?(?<season>\d{1,2}(?!\d+))(?:(?:\-|\.|[ex]|\s|\sto\s){1,2}(?<episode>\d{1,2}(?!\d+)))+)+\W?(?!\\)", + new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>\d{1,2}(?!\d+))(?:(?:\-|\.|[ex]|\s){1,2}(?<episode>\d{1,2}(?!\d+)))+)+\W?(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled), //No Title - Single episodes or multi-episode (S01E05E06, S01E05-06, etc) @@ -381,6 +381,9 @@ namespace NzbDrone.Core /// <returns></returns> public static string NormalizeTitle(string title) { + //Todo: Find a better way to do this hack + if (title == "90210" || title == "24") + return title; return NormalizeRegex.Replace(title, String.Empty).ToLower(); } diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs index 7c04d7eb4..49dd9bc8f 100644 --- a/NzbDrone.Core/Providers/DiskScanProvider.cs +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -18,18 +18,22 @@ namespace NzbDrone.Core.Providers private readonly EpisodeProvider _episodeProvider; private readonly MediaFileProvider _mediaFileProvider; private readonly SeriesProvider _seriesProvider; + private readonly ExternalNotificationProvider _externalNotificationProvider; + private readonly SabProvider _sabProvider; [Inject] public DiskScanProvider(DiskProvider diskProvider, EpisodeProvider episodeProvider, - SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider) + SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider, + ExternalNotificationProvider externalNotificationProvider, SabProvider sabProvider) { _diskProvider = diskProvider; _episodeProvider = episodeProvider; _seriesProvider = seriesProvider; _mediaFileProvider = mediaFileProvider; + _externalNotificationProvider = externalNotificationProvider; + _sabProvider = sabProvider; } - public DiskScanProvider() { } @@ -142,7 +146,7 @@ namespace NzbDrone.Core.Providers return episodeFile; } - public virtual bool MoveEpisodeFile(EpisodeFile episodeFile) + public virtual bool MoveEpisodeFile(EpisodeFile episodeFile, bool newDownload = false) { if (episodeFile == null) throw new ArgumentNullException("episodeFile"); @@ -163,6 +167,18 @@ namespace NzbDrone.Core.Providers episodeFile.Path = newFile.FullName; _mediaFileProvider.Update(episodeFile); + //ExternalNotification + var parseResult = Parser.ParsePath(episodeFile.Path); + parseResult.Series = series; + + var message = _sabProvider.GetSabTitle(parseResult); + + if (newDownload) + _externalNotificationProvider.OnDownload(message, series); + + else + _externalNotificationProvider.OnRename(message, series); + return true; } diff --git a/NzbDrone.Core/Providers/DownloadProvider.cs b/NzbDrone.Core/Providers/DownloadProvider.cs index 9a32b609f..e4abe5342 100644 --- a/NzbDrone.Core/Providers/DownloadProvider.cs +++ b/NzbDrone.Core/Providers/DownloadProvider.cs @@ -11,14 +11,18 @@ namespace NzbDrone.Core.Providers private readonly SabProvider _sabProvider; private readonly HistoryProvider _historyProvider; private readonly EpisodeProvider _episodeProvider; + private readonly ExternalNotificationProvider _externalNotificationProvider; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); [Inject] - public DownloadProvider(SabProvider sabProvider, HistoryProvider historyProvider, EpisodeProvider episodeProvider) + public DownloadProvider(SabProvider sabProvider, HistoryProvider historyProvider, + EpisodeProvider episodeProvider, ExternalNotificationProvider externalNotificationProvider) { _sabProvider = sabProvider; _historyProvider = historyProvider; _episodeProvider = episodeProvider; + _externalNotificationProvider = externalNotificationProvider; } public DownloadProvider() @@ -55,6 +59,8 @@ namespace NzbDrone.Core.Providers } } + _externalNotificationProvider.OnGrab(sabTitle); + return addSuccess; } } diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index 416def223..d0cf2858b 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -159,12 +159,15 @@ namespace NzbDrone.Core.Providers public virtual IList<Episode> EpisodesWithoutFiles(bool includeSpecials) { - var episodes = _database.Query<Episode>("WHERE (EpisodeFileId=0 OR EpisodeFileId=NULL) AND AirDate<=@0", + var episodes = _database.Query<Episode, Series>(@"SELECT Episodes.*, Series.Title FROM Episodes + INNER JOIN Series + ON Episodes.SeriesId = Series.SeriesId + WHERE (EpisodeFileId=0 OR EpisodeFileId=NULL) AND Ignored = 0 AND AirDate<=@0", DateTime.Now.Date); - if (includeSpecials) + if (!includeSpecials) return episodes.Where(e => e.SeasonNumber > 0).ToList(); - return AttachSeries(episodes.ToList()); + return episodes.ToList(); } public virtual IList<Episode> GetEpisodesByFileId(int episodeFileId) @@ -296,26 +299,23 @@ namespace NzbDrone.Core.Providers public virtual void SetSeasonIgnore(long seriesId, int seasonNumber, bool isIgnored) { Logger.Info("Setting ignore flag on Series:{0} Season:{1} to {2}", seriesId, seasonNumber, isIgnored); - var episodes = GetEpisodesBySeason(seriesId, seasonNumber); - using (var tran = _database.GetTransaction()) - { - foreach (var episode in episodes) - { - episode.Ignored = isIgnored; - _database.Update(episode); - } - - //Shouldn't run if Database is a mock since transaction will be null - if (_database.GetType().Namespace != "Castle.Proxies" && tran != null) - { - tran.Complete(); - } + _database.Execute(@"UPDATE Episodes SET Ignored = @0 + WHERE SeriesId = @1 AND SeasonNumber = @2 AND Ignored = @3", + isIgnored, seriesId, seasonNumber, !isIgnored); Logger.Info("Ignore flag for Series:{0} Season:{1} successfully set to {2}", seriesId, seasonNumber, isIgnored); - } + } + + public virtual void SetEpisodeIgnore(int episodeId, bool isIgnored) + { + Logger.Info("Setting ignore flag on Episode:{0} to {1}", episodeId, isIgnored); + _database.Execute(@"UPDATE Episodes SET Ignored = @0 + WHERE EpisodeId = @1", + isIgnored, episodeId); + Logger.Info("Ignore flag for Episode:{0} successfully set to {1}", episodeId, isIgnored); } public IList<Episode> AttachSeries(IList<Episode> episodes) diff --git a/NzbDrone.Core/Providers/ExternalNotification/ExternalNotificationProviderBase.cs b/NzbDrone.Core/Providers/ExternalNotification/ExternalNotificationBase.cs similarity index 53% rename from NzbDrone.Core/Providers/ExternalNotification/ExternalNotificationProviderBase.cs rename to NzbDrone.Core/Providers/ExternalNotification/ExternalNotificationBase.cs index d21d8bf35..811713e22 100644 --- a/NzbDrone.Core/Providers/ExternalNotification/ExternalNotificationProviderBase.cs +++ b/NzbDrone.Core/Providers/ExternalNotification/ExternalNotificationBase.cs @@ -6,16 +6,14 @@ using NzbDrone.Core.Repository; namespace NzbDrone.Core.Providers.ExternalNotification { - public abstract class ExternalNotificationProviderBase + public abstract class ExternalNotificationBase { protected readonly Logger _logger; protected readonly ConfigProvider _configProvider; - protected readonly ExternalNotificationProvider _externalNotificationProvider; - protected ExternalNotificationProviderBase(ConfigProvider configProvider, ExternalNotificationProvider externalNotificationProvider) + protected ExternalNotificationBase(ConfigProvider configProvider) { _configProvider = configProvider; - _externalNotificationProvider = externalNotificationProvider; _logger = LogManager.GetLogger(GetType().ToString()); } @@ -24,35 +22,6 @@ namespace NzbDrone.Core.Providers.ExternalNotification /// </summary> public abstract string Name { get; } - public ExternalNotificationSetting Settings - { - get - { - return _externalNotificationProvider.GetSettings(GetType()); - } - } - - public virtual void Notify(ExternalNotificationType type, string message, int seriesId = 0) - { - if (type == ExternalNotificationType.Grab) - OnGrab(message); - - else if (type == ExternalNotificationType.Download) - { - throw new NotImplementedException(); - var series = new Series(); - OnDownload(message, series); - } - - - else if (type == ExternalNotificationType.Rename) - { - throw new NotImplementedException(); - var series = new Series(); - OnRename(message, series); - } - } - /// <summary> /// Performs the on grab action /// </summary> diff --git a/NzbDrone.Core/Providers/ExternalNotification/Xbmc.cs b/NzbDrone.Core/Providers/ExternalNotification/Xbmc.cs new file mode 100644 index 000000000..2533fc54e --- /dev/null +++ b/NzbDrone.Core/Providers/ExternalNotification/Xbmc.cs @@ -0,0 +1,66 @@ +using System; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Providers.ExternalNotification +{ + public class Xbmc : ExternalNotificationBase + { + private readonly XbmcProvider _xbmcProvider; + + public Xbmc(ConfigProvider configProvider, XbmcProvider xbmcProvider) + : base(configProvider) + { + _xbmcProvider = xbmcProvider; + } + + public override string Name + { + get { return "XBMC"; } + } + + public override void OnGrab(string message) + { + const string header = "NzbDrone [TV] - Grabbed"; + + if (_configProvider.XbmcNotifyOnGrab) + { + _logger.Trace("Sending Notification to XBMC"); + _xbmcProvider.Notify(header, message); + } + } + + public override void OnDownload(string message, Series series) + { + const string header = "NzbDrone [TV] - Downloaded"; + + if (_configProvider.XbmcNotifyOnDownload) + { + _logger.Trace("Sending Notification to XBMC"); + _xbmcProvider.Notify(header, message); + } + + UpdateAndClean(series); + } + + public override void OnRename(string message, Series series) + { + UpdateAndClean(series); + } + + private void UpdateAndClean(Series series) + { + if (_configProvider.XbmcUpdateLibrary) + { + _logger.Trace("Sending Update Request to XBMC"); + _xbmcProvider.Update(series); + } + + if (_configProvider.XbmcCleanLibrary) + { + _logger.Trace("Sending Clean DB Request to XBMC"); + _xbmcProvider.Clean(); + } + } + } +} diff --git a/NzbDrone.Core/Providers/ExternalNotification/XbmcNotificationProvider.cs b/NzbDrone.Core/Providers/ExternalNotification/XbmcNotificationProvider.cs deleted file mode 100644 index 458efcec3..000000000 --- a/NzbDrone.Core/Providers/ExternalNotification/XbmcNotificationProvider.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using NzbDrone.Core.Providers.Core; -using NzbDrone.Core.Repository; - -namespace NzbDrone.Core.Providers.ExternalNotification -{ - public class XbmcNotificationProvider : ExternalNotificationProviderBase - { - private readonly XbmcProvider _xbmcProvider; - - public XbmcNotificationProvider(ConfigProvider configProvider, XbmcProvider xbmcProvider, - ExternalNotificationProvider externalNotificationProvider) - : base(configProvider, externalNotificationProvider) - { - _xbmcProvider = xbmcProvider; - } - - public override string Name - { - get { return "XBMC"; } - } - - public override void OnGrab(string message) - { - const string header = "NzbDrone [TV] - Grabbed"; - - if (Convert.ToBoolean(_configProvider.GetValue("XbmcEnabled", false))) - { - if (Convert.ToBoolean(_configProvider.GetValue("XbmcNotifyOnGrab", false))) - { - _logger.Trace("Sending Notification to XBMC"); - _xbmcProvider.Notify(header, message); - return; - } - _logger.Trace("XBMC NotifyOnGrab is not enabled"); - } - - _logger.Trace("XBMC Notifier is not enabled"); - } - - public override void OnDownload(string message, Series series) - { - const string header = "NzbDrone [TV] - Downloaded"; - - if (Convert.ToBoolean(_configProvider.GetValue("XbmcEnabled", false))) - { - if (Convert.ToBoolean(_configProvider.GetValue("XbmcNotifyOnDownload", false))) - { - _logger.Trace("Sending Notification to XBMC"); - _xbmcProvider.Notify(header, message); - } - - if (Convert.ToBoolean(_configProvider.GetValue("XbmcUpdateOnDownload", false))) - { - _logger.Trace("Sending Update Request to XBMC"); - _xbmcProvider.Update(series); - } - - if (Convert.ToBoolean(_configProvider.GetValue("XbmcCleanOnDownload", false))) - { - _logger.Trace("Sending Clean DB Request to XBMC"); - _xbmcProvider.Clean(); - } - } - - _logger.Trace("XBMC Notifier is not enabled"); - } - - public override void OnRename(string message, Series series) - { - const string header = "NzbDrone [TV] - Renamed"; - - if (Convert.ToBoolean(_configProvider.GetValue("XbmcNotifyOnRename", false))) - { - _logger.Trace("Sending Notification to XBMC"); - _xbmcProvider.Notify(header, message); - } - - if (Convert.ToBoolean(_configProvider.GetValue("XbmcUpdateOnRename", false))) - { - _logger.Trace("Sending Update Request to XBMC"); - _xbmcProvider.Update(series); - } - - if (Convert.ToBoolean(_configProvider.GetValue("XbmcCleanOnRename", false))) - { - _logger.Trace("Sending Clean DB Request to XBMC"); - _xbmcProvider.Clean(); - } - } - } -} diff --git a/NzbDrone.Core/Providers/ExternalNotificationProvider.cs b/NzbDrone.Core/Providers/ExternalNotificationProvider.cs index 4cba27051..749c12875 100644 --- a/NzbDrone.Core/Providers/ExternalNotificationProvider.cs +++ b/NzbDrone.Core/Providers/ExternalNotificationProvider.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using Ninject; using NLog; +using NzbDrone.Core.Model; using NzbDrone.Core.Providers.ExternalNotification; using NzbDrone.Core.Repository; using PetaPoco; @@ -13,10 +15,13 @@ namespace NzbDrone.Core.Providers private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly IDatabase _database; - [Inject] - public ExternalNotificationProvider(IDatabase database) + private IEnumerable<ExternalNotificationBase> _notifiers; + + [Inject] + public ExternalNotificationProvider(IDatabase database, IEnumerable<ExternalNotificationBase> notifiers) { _database = database; + _notifiers = notifiers; } public ExternalNotificationProvider() @@ -24,51 +29,54 @@ namespace NzbDrone.Core.Providers } - public virtual List<ExternalNotificationSetting> All() + public virtual List<ExternalNotificationDefinition> All() { - return _database.Fetch<ExternalNotificationSetting>(); + return _database.Fetch<ExternalNotificationDefinition>(); } - public virtual void SaveSettings(ExternalNotificationSetting settings) + public virtual void SaveSettings(ExternalNotificationDefinition settings) { if (settings.Id == 0) { - Logger.Debug("Adding External Notification settings for {0}", settings.Name); + Logger.Debug("Adding External Notification definition for {0}", settings.Name); _database.Insert(settings); } else { - Logger.Debug("Updating External Notification settings for {0}", settings.Name); + Logger.Debug("Updating External Notification definition for {0}", settings.Name); _database.Update(settings); } } - public virtual ExternalNotificationSetting GetSettings(Type type) + public virtual ExternalNotificationDefinition GetSettings(Type type) { - return _database.SingleOrDefault<ExternalNotificationSetting>("WHERE NotifierName = @0", type.ToString()); + return _database.SingleOrDefault<ExternalNotificationDefinition>("WHERE ExternalNotificationProviderType = @0", type.ToString()); } - public virtual ExternalNotificationSetting GetSettings(int id) + public virtual IList<ExternalNotificationBase> GetEnabledExternalNotifiers() { - return _database.SingleOrDefault<ExternalNotificationSetting>(id); + var all = All(); + return _notifiers.Where(i => all.Exists(c => c.ExternalNotificationProviderType == i.GetType().ToString() && c.Enable)).ToList(); } - public virtual void InitializeNotifiers(IList<ExternalNotificationProviderBase> notifiers) + public virtual void InitializeNotifiers(IList<ExternalNotificationBase> notifiers) { Logger.Info("Initializing notifiers. Count {0}", notifiers.Count); + _notifiers = notifiers; + var currentNotifiers = All(); - foreach (var feedProvider in notifiers) + foreach (var notificationProvider in notifiers) { - ExternalNotificationProviderBase externalNotificationProviderLocal = feedProvider; - if (!currentNotifiers.Exists(c => c.NotifierName == externalNotificationProviderLocal.GetType().ToString())) + ExternalNotificationBase externalNotificationProviderLocal = notificationProvider; + if (!currentNotifiers.Exists(c => c.ExternalNotificationProviderType == externalNotificationProviderLocal.GetType().ToString())) { - var settings = new ExternalNotificationSetting() + var settings = new ExternalNotificationDefinition { - Enabled = false, - NotifierName = externalNotificationProviderLocal.GetType().ToString(), + Enable = false, + ExternalNotificationProviderType = externalNotificationProviderLocal.GetType().ToString(), Name = externalNotificationProviderLocal.Name }; @@ -76,5 +84,29 @@ namespace NzbDrone.Core.Providers } } } + + public virtual void OnGrab(string message) + { + foreach (var notifier in _notifiers.Where(i => GetSettings(i.GetType()).Enable)) + { + notifier.OnGrab(message); + } + } + + public virtual void OnDownload(string message, Series series) + { + foreach (var notifier in _notifiers.Where(i => GetSettings(i.GetType()).Enable)) + { + notifier.OnDownload(message, series); + } + } + + public virtual void OnRename(string message, Series series) + { + foreach (var notifier in _notifiers.Where(i => GetSettings(i.GetType()).Enable)) + { + notifier.OnRename(message, series); + } + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs b/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs index dbf84db30..3af0c7557 100644 --- a/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs +++ b/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs @@ -79,12 +79,12 @@ namespace NzbDrone.Core.Providers.Jobs if (series == null) { - Logger.Warn("Unable to Import new download, series doesn't exist in database."); - return; + Logger.Warn("Unable to Import new download [{0}], series doesn't exist in database.", subfolder); + continue; } var importedFiles = _diskScanProvider.Scan(series, subfolder); - importedFiles.ForEach(file => _diskScanProvider.MoveEpisodeFile(file)); + importedFiles.ForEach(file => _diskScanProvider.MoveEpisodeFile(file, true)); //Delete the folder only if folder is small enough if (_diskProvider.GetDirectorySize(subfolder) < 10.Megabytes()) diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 8daeb1c89..038e1c5ab 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -178,7 +178,7 @@ namespace NzbDrone.Core.Providers public virtual bool SeriesPathExists(string cleanPath) { - if (GetAllSeries().Any(s => s.Path == cleanPath)) + if (GetAllSeries().Any(s => s.Path.ToLower() == cleanPath.ToLower())) return true; return false; diff --git a/NzbDrone.Core/Repository/ExternalNotificationDefinition.cs b/NzbDrone.Core/Repository/ExternalNotificationDefinition.cs new file mode 100644 index 000000000..64a3abba1 --- /dev/null +++ b/NzbDrone.Core/Repository/ExternalNotificationDefinition.cs @@ -0,0 +1,17 @@ +using PetaPoco; + +namespace NzbDrone.Core.Repository +{ + [TableName("ExternalNotificationDefinitions")] + [PrimaryKey("Id", autoIncrement = true)] + public class ExternalNotificationDefinition + { + public int Id { get; set; } + + public bool Enable { get; set; } + + public string ExternalNotificationProviderType { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Repository/ExternalNotificationSetting.cs b/NzbDrone.Core/Repository/ExternalNotificationSetting.cs deleted file mode 100644 index b6d946b69..000000000 --- a/NzbDrone.Core/Repository/ExternalNotificationSetting.cs +++ /dev/null @@ -1,17 +0,0 @@ -using PetaPoco; - -namespace NzbDrone.Core.Repository -{ - [TableName("ExternalNotificationSettings")] - [PrimaryKey("Id", autoIncrement = true)] - public class ExternalNotificationSetting - { - public int Id { get; set; } - - public bool Enabled { get; set; } - - public string NotifierName { get; set; } - - public string Name { get; set; } - } -} \ No newline at end of file diff --git a/NzbDrone.Web/Content/Images/ignored.png b/NzbDrone.Web/Content/Images/ignored.png new file mode 100644 index 000000000..41423bad2 Binary files /dev/null and b/NzbDrone.Web/Content/Images/ignored.png differ diff --git a/NzbDrone.Web/Content/Images/ignoredNeutral.png b/NzbDrone.Web/Content/Images/ignoredNeutral.png new file mode 100644 index 000000000..a7181b004 Binary files /dev/null and b/NzbDrone.Web/Content/Images/ignoredNeutral.png differ diff --git a/NzbDrone.Web/Content/Images/notIgnored.png b/NzbDrone.Web/Content/Images/notIgnored.png new file mode 100644 index 000000000..4ead4b480 Binary files /dev/null and b/NzbDrone.Web/Content/Images/notIgnored.png differ diff --git a/NzbDrone.Web/Content/Menu.css b/NzbDrone.Web/Content/Menu.css new file mode 100644 index 000000000..2dc07197c --- /dev/null +++ b/NzbDrone.Web/Content/Menu.css @@ -0,0 +1,20 @@ +#sub-menu +{ + padding-left: 5px; +} + +#sub-menu li +{ + display: inline; + list-style-type: none; + padding-left: 8px; + padding-right: 12px; + padding-top: 6px; + border-right: 1px solid #F0F0F0; +} + +#sub-menu a +{ + text-decoration: none; + color: #105CD6; +} \ No newline at end of file diff --git a/NzbDrone.Web/Content/Settings.css b/NzbDrone.Web/Content/Settings.css index 2169714bf..fc8d817e7 100644 --- a/NzbDrone.Web/Content/Settings.css +++ b/NzbDrone.Web/Content/Settings.css @@ -107,4 +107,12 @@ p, h1, form, button{border:0; margin:0; padding:0;} width: 20px; height: 20px; display: none; +} + +#save_button[disabled="disabled"] +{ + padding: 0px 6px 0px 6px; + border: 2px outset ButtonFace; + color: lightgrey; + cursor: progress; } \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/CommandController.cs b/NzbDrone.Web/Controllers/CommandController.cs new file mode 100644 index 000000000..00c8e7638 --- /dev/null +++ b/NzbDrone.Web/Controllers/CommandController.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using NzbDrone.Core.Providers.Jobs; + +namespace NzbDrone.Web.Controllers +{ + public class CommandController : Controller + { + private readonly JobProvider _jobProvider; + + public CommandController(JobProvider jobProvider) + { + _jobProvider = jobProvider; + } + + public JsonResult RssSync() + { + _jobProvider.QueueJob(typeof(RssSyncJob)); + return new JsonResult { Data = "ok" }; + } + + public JsonResult SyncEpisodesOnDisk(int seriesId) + { + //Syncs the episodes on disk for the specified series + _jobProvider.QueueJob(typeof(DiskScanJob), seriesId); + + return new JsonResult { Data = "ok" }; + } + + public JsonResult UpdateInfo(int seriesId) + { + //Syncs the episodes on disk for the specified series + _jobProvider.QueueJob(typeof(UpdateInfoJob), seriesId); + + return new JsonResult { Data = "ok" }; + } + + public JsonResult RenameSeries(int seriesId) + { + //Syncs the episodes on disk for the specified series + //_jobProvider.QueueJob(typeof(UpdateInfoJob), seriesId); + + return new JsonResult { Data = "ok" }; + } + } +} diff --git a/NzbDrone.Web/Controllers/HistoryController.cs b/NzbDrone.Web/Controllers/HistoryController.cs index e0a1e2e8c..8145614bf 100644 --- a/NzbDrone.Web/Controllers/HistoryController.cs +++ b/NzbDrone.Web/Controllers/HistoryController.cs @@ -26,16 +26,16 @@ namespace NzbDrone.Web.Controllers return View(); } - public ActionResult Trim() + public JsonResult Trim() { _historyProvider.Trim(); - return RedirectToAction("Index"); + return new JsonResult { Data = "ok" }; } - public ActionResult Purge() + public JsonResult Purge() { _historyProvider.Purge(); - return RedirectToAction("Index"); + return new JsonResult { Data = "ok" }; } [GridAction] diff --git a/NzbDrone.Web/Controllers/LogController.cs b/NzbDrone.Web/Controllers/LogController.cs index e17915924..8df032ecc 100644 --- a/NzbDrone.Web/Controllers/LogController.cs +++ b/NzbDrone.Web/Controllers/LogController.cs @@ -18,11 +18,11 @@ namespace NzbDrone.Web.Controllers return View(); } - - public ActionResult Clear() + public JsonResult Clear() { _logProvider.DeleteAll(); - return RedirectToAction("Index"); + + return new JsonResult { Data = "ok" }; } [GridAction] diff --git a/NzbDrone.Web/Controllers/MissingController.cs b/NzbDrone.Web/Controllers/MissingController.cs index 6b94dce08..581c3f541 100644 --- a/NzbDrone.Web/Controllers/MissingController.cs +++ b/NzbDrone.Web/Controllers/MissingController.cs @@ -26,10 +26,9 @@ namespace NzbDrone.Web.Controllers [GridAction] public ActionResult _AjaxBinding() { - //TODO: possible subsonic bug, IQuarible causes some issues so ToList() is called - //https://github.com/subsonic/SubSonic-3.0/issues/263 + var missingEpisodes = _episodeProvider.EpisodesWithoutFiles(false); - var missing = _episodeProvider.EpisodesWithoutFiles(true).Select(e => new MissingEpisodeModel + var missing = missingEpisodes.Select(e => new MissingEpisodeModel { EpisodeId = e.EpisodeId, SeasonNumber = e.SeasonNumber, diff --git a/NzbDrone.Web/Controllers/NotificationController.cs b/NzbDrone.Web/Controllers/NotificationController.cs index c1796cac0..43a02b5c0 100644 --- a/NzbDrone.Web/Controllers/NotificationController.cs +++ b/NzbDrone.Web/Controllers/NotificationController.cs @@ -67,6 +67,8 @@ namespace NzbDrone.Web.Controllers private string GetCurrentMessage() { + var notes = _notifications.ProgressNotifications; + if (_notifications.ProgressNotifications.Count != 0) return _notifications.ProgressNotifications[0].CurrentMessage; diff --git a/NzbDrone.Web/Controllers/SeriesController.cs b/NzbDrone.Web/Controllers/SeriesController.cs index c1d0835be..aaec2aadf 100644 --- a/NzbDrone.Web/Controllers/SeriesController.cs +++ b/NzbDrone.Web/Controllers/SeriesController.cs @@ -47,12 +47,6 @@ namespace NzbDrone.Web.Controllers return View(); } - public ActionResult RssSync() - { - _jobProvider.QueueJob(typeof(RssSyncJob)); - return RedirectToAction("Index"); - } - public ActionResult SeasonEditor(int seriesId) { var model = new List<SeasonEditModel>(); @@ -148,12 +142,17 @@ namespace NzbDrone.Web.Controllers } [HttpPost] - public JsonResult SaveSeason(int seriesId, int seasonNumber, bool monitored) + public JsonResult SaveSeasonIgnore(int seriesId, int seasonNumber, bool ignored) { - if (_episodeProvider.IsIgnored(seriesId, seasonNumber) == monitored) - { - _episodeProvider.SetSeasonIgnore(seriesId, seasonNumber, !monitored); - } + _episodeProvider.SetSeasonIgnore(seriesId, seasonNumber, ignored); + + return new JsonResult { Data = "ok" }; + } + + [HttpPost] + public JsonResult SaveEpisodeIgnore(int episodeId, bool ignored) + { + _episodeProvider.SetEpisodeIgnore(episodeId, ignored); return new JsonResult { Data = "ok" }; } @@ -180,21 +179,6 @@ namespace NzbDrone.Web.Controllers return View(model); } - public ActionResult SyncEpisodesOnDisk(int seriesId) - { - //Syncs the episodes on disk for the specified series - _jobProvider.QueueJob(typeof(DiskScanJob), seriesId); - - return RedirectToAction("Details", new { seriesId }); - } - - public ActionResult UpdateInfo(int seriesId) - { - //Syncs the episodes on disk for the specified series - _jobProvider.QueueJob(typeof(UpdateInfoJob), seriesId); - return RedirectToAction("Details", new { seriesId }); - } - private List<SeriesModel> GetSeriesModels(IList<Series> seriesInDb) { var series = seriesInDb.Select(s => new SeriesModel @@ -227,8 +211,6 @@ namespace NzbDrone.Web.Controllers var episodePath = String.Empty; var episodeQuality = String.Empty; - - if (e.EpisodeFile != null) { episodePath = e.EpisodeFile.Path; @@ -252,7 +234,8 @@ namespace NzbDrone.Web.Controllers Path = episodePath, EpisodeFileId = episodeFileId, Status = e.Status.ToString(), - Quality = episodeQuality + Quality = episodeQuality, + Ignored = e.Ignored }); } diff --git a/NzbDrone.Web/Controllers/SettingsController.cs b/NzbDrone.Web/Controllers/SettingsController.cs index 5223e4945..f263828bf 100644 --- a/NzbDrone.Web/Controllers/SettingsController.cs +++ b/NzbDrone.Web/Controllers/SettingsController.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.Model; using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Providers; using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Providers.ExternalNotification; using NzbDrone.Core.Providers.Indexer; using NzbDrone.Core.Repository; using NzbDrone.Core.Repository.Quality; @@ -30,12 +31,16 @@ namespace NzbDrone.Web.Controllers private readonly NotificationProvider _notificationProvider; private readonly DiskProvider _diskProvider; private readonly SeriesProvider _seriesProvider; + private readonly ExternalNotificationProvider _externalNotificationProvider; + private readonly ProgressNotification _progressNotification; public SettingsController(ConfigProvider configProvider, IndexerProvider indexerProvider, QualityProvider qualityProvider, RootDirProvider rootDirProvider, AutoConfigureProvider autoConfigureProvider, NotificationProvider notificationProvider, - DiskProvider diskProvider, SeriesProvider seriesProvider) + DiskProvider diskProvider, SeriesProvider seriesProvider, + ExternalNotificationProvider externalNotificationProvider) { + _externalNotificationProvider = externalNotificationProvider; _configProvider = configProvider; _indexerProvider = indexerProvider; _qualityProvider = qualityProvider; @@ -44,6 +49,8 @@ namespace NzbDrone.Web.Controllers _notificationProvider = notificationProvider; _diskProvider = diskProvider; _seriesProvider = seriesProvider; + + _progressNotification = new ProgressNotification("Settings"); } public ActionResult Test() @@ -140,7 +147,7 @@ namespace NzbDrone.Web.Controllers { var model = new NotificationSettingsModel { - XbmcEnabled = _configProvider.XbmcEnabled, + XbmcEnabled = _externalNotificationProvider.GetSettings(typeof(Xbmc)).Enable, XbmcNotifyOnGrab = _configProvider.XbmcNotifyOnGrab, XbmcNotifyOnDownload = _configProvider.XbmcNotifyOnDownload, XbmcUpdateLibrary = _configProvider.XbmcUpdateLibrary, @@ -262,14 +269,11 @@ namespace NzbDrone.Web.Controllers return new JsonResult { Data = "failed" }; } } - [HttpPost] public ActionResult SaveIndexers(IndexerSettingsModel data) { - var basicNotification = new BasicNotification(); - basicNotification.Type = BasicNotificationType.Info; - basicNotification.AutoDismiss = true; + _notificationProvider.Register(_progressNotification); if (ModelState.IsValid) { @@ -301,22 +305,20 @@ namespace NzbDrone.Web.Controllers _configProvider.NewzbinUsername = data.NewzbinUsername; _configProvider.NewzbinPassword = data.NewzbinPassword; - basicNotification.Title = SETTINGS_SAVED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_SAVED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_SAVED); } - basicNotification.Title = SETTINGS_FAILED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_FAILED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_FAILED); } [HttpPost] public ActionResult SaveSabnzbd(SabnzbdSettingsModel data) { - var basicNotification = new BasicNotification(); - basicNotification.Type = BasicNotificationType.Info; - basicNotification.AutoDismiss = true; + _notificationProvider.Register(_progressNotification); if (ModelState.IsValid) { @@ -329,22 +331,20 @@ namespace NzbDrone.Web.Controllers _configProvider.SabTvPriority = data.SabTvPriority; _configProvider.SabDropDirectory = data.SabDropDirectory; - basicNotification.Title = SETTINGS_SAVED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_SAVED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_SAVED); } - basicNotification.Title = SETTINGS_FAILED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_FAILED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_FAILED); } [HttpPost] public ActionResult SaveQuality(QualityModel data) { - var basicNotification = new BasicNotification(); - basicNotification.Type = BasicNotificationType.Info; - basicNotification.AutoDismiss = true; + _notificationProvider.Register(_progressNotification); if (ModelState.IsValid) { @@ -376,26 +376,29 @@ namespace NzbDrone.Web.Controllers _qualityProvider.Update(profile); } - basicNotification.Title = SETTINGS_SAVED; - _notificationProvider.Register(basicNotification); + + _progressNotification.CurrentMessage = SETTINGS_SAVED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_SAVED); } - basicNotification.Title = SETTINGS_FAILED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_FAILED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_FAILED); } [HttpPost] public ActionResult SaveNotifications(NotificationSettingsModel data) { - var basicNotification = new BasicNotification(); - basicNotification.Type = BasicNotificationType.Info; - basicNotification.AutoDismiss = true; + _notificationProvider.Register(_progressNotification); if (ModelState.IsValid) { - _configProvider.XbmcEnabled = data.XbmcEnabled; + //XBMC Enabled + var xbmcSettings = _externalNotificationProvider.GetSettings(typeof(Xbmc)); + xbmcSettings.Enable = data.XbmcEnabled; + _externalNotificationProvider.SaveSettings(xbmcSettings); + _configProvider.XbmcNotifyOnGrab = data.XbmcNotifyOnGrab; _configProvider.XbmcNotifyOnDownload = data.XbmcNotifyOnDownload; _configProvider.XbmcUpdateLibrary = data.XbmcUpdateLibrary; @@ -404,22 +407,20 @@ namespace NzbDrone.Web.Controllers _configProvider.XbmcUsername = data.XbmcUsername; _configProvider.XbmcPassword = data.XbmcPassword; - basicNotification.Title = SETTINGS_SAVED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_SAVED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_SAVED); } - basicNotification.Title = SETTINGS_FAILED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_FAILED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_FAILED); } [HttpPost] public ActionResult SaveEpisodeSorting(EpisodeSortingModel data) { - var basicNotification = new BasicNotification(); - basicNotification.Type = BasicNotificationType.Info; - basicNotification.AutoDismiss = true; + _notificationProvider.Register(_progressNotification); if (ModelState.IsValid) { @@ -433,13 +434,13 @@ namespace NzbDrone.Web.Controllers _configProvider.SortingNumberStyle = data.NumberStyle; _configProvider.SortingMultiEpisodeStyle = data.MultiEpisodeStyle; - basicNotification.Title = SETTINGS_SAVED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_SAVED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_SAVED); } - basicNotification.Title = SETTINGS_FAILED; - _notificationProvider.Register(basicNotification); + _progressNotification.CurrentMessage = SETTINGS_FAILED; + _progressNotification.Status = ProgressNotificationStatus.Completed; return Content(SETTINGS_FAILED); } } diff --git a/NzbDrone.Web/Models/EpisodeModel.cs b/NzbDrone.Web/Models/EpisodeModel.cs index 2384031d9..867066f99 100644 --- a/NzbDrone.Web/Models/EpisodeModel.cs +++ b/NzbDrone.Web/Models/EpisodeModel.cs @@ -15,5 +15,6 @@ namespace NzbDrone.Web.Models public String Status { get; set; } public string AirDate { get; set; } public String Quality { get; set; } + public bool Ignored { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/Models/SeriesModel.cs b/NzbDrone.Web/Models/SeriesModel.cs index 7a2429acb..be1111df2 100644 --- a/NzbDrone.Web/Models/SeriesModel.cs +++ b/NzbDrone.Web/Models/SeriesModel.cs @@ -26,16 +26,20 @@ namespace NzbDrone.Web.Models //View & Edit [DisplayName("Path")] + [Description("Where should NzbDrone store episodes for this series?")] public string Path { get; set; } [DisplayName("Quality Profile")] + [Description("Which Quality Profile should NzbDrone use to download episodes?")] public virtual int QualityProfileId { get; set; } //Editing Only [DisplayName("Use Season Folder")] + [Description("Should downloaded episodes be stored in season folders?")] public bool SeasonFolder { get; set; } [DisplayName("Monitored")] + [Description("Should NzbDrone download episodes for this series?")] public bool Monitored { get; set; } [DisplayName("Season Editor")] diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index e158a9177..01cedf526 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -135,6 +135,8 @@ </Reference> </ItemGroup> <ItemGroup> + <Content Include="Content\Menu.css" /> + <Compile Include="Controllers\CommandController.cs" /> <Compile Include="Controllers\DirectoryController.cs" /> <Compile Include="Controllers\EpisodeController.cs" /> <Compile Include="Controllers\HealthController.cs" /> @@ -183,6 +185,9 @@ <Content Include="Content\Blueprint\ie.css" /> <Content Include="Content\Blueprint\screen.css" /> <Content Include="Content\Blueprint\liquid.css" /> + <Content Include="Content\Images\ignored.png" /> + <Content Include="Content\Images\ignoredNeutral.png" /> + <Content Include="Content\Images\notIgnored.png" /> <Content Include="Content\Images\x_16.png" /> <Content Include="Content\jQueryUI\images\ui-bg_diagonals-thick_30_a32d00_40x40.png" /> <Content Include="Content\jQueryUI\images\ui-bg_flat_0_065efe_40x100.png" /> @@ -250,6 +255,7 @@ <Content Include="favicon.ico" /> <Content Include="Global.asax" /> <Content Include="Scripts\AutoComplete.js" /> + <Content Include="Scripts\seriesDetails.js" /> <Content Include="Scripts\Plugins\jquery.livequery.js" /> <Content Include="Scripts\settingsForm.js" /> <Content Include="Scripts\Plugins\doTimeout.js" /> @@ -283,11 +289,10 @@ <Content Include="Views\Shared\Footer.cshtml" /> <Content Include="Views\_ViewStart.cshtml" /> <Content Include="Views\History\Index.cshtml" /> - <Content Include="Views\Log\index.cshtml" /> + <Content Include="Views\Log\Index.cshtml" /> <Content Include="Views\Upcoming\Index.cshtml" /> <Content Include="Views\Series\Details.cshtml" /> <Content Include="Views\Series\Index.cshtml" /> - <Content Include="Views\Series\SubMenu.cshtml" /> <Content Include="Views\Series\SeriesSearchResults.cshtml" /> <Content Include="Views\Shared\Error.cshtml" /> <Content Include="Views\Settings\QualityProfileItem.cshtml" /> @@ -317,9 +322,6 @@ <ItemGroup> <Content Include="Views\Series\EditorTemplates\SeriesModel.cshtml" /> </ItemGroup> - <ItemGroup> - <Content Include="Views\Series\SeasonEditor.cshtml" /> - </ItemGroup> <ItemGroup> <Content Include="Views\Series\SingleSeason.cshtml" /> </ItemGroup> diff --git a/NzbDrone.Web/Scripts/seriesDetails.js b/NzbDrone.Web/Scripts/seriesDetails.js new file mode 100644 index 000000000..36544f364 --- /dev/null +++ b/NzbDrone.Web/Scripts/seriesDetails.js @@ -0,0 +1,136 @@ +var notIgnoredImage = '../../Content/Images/notIgnored.png'; +var ignoredImage = '../../Content/Images/ignored.png'; +var seriesId = 0; +var saveSeasonIgnoreUrl = '../Series/SaveSeasonIgnore'; +var saveEpisodeIgnoreUrl = '../Series/SaveEpisodeIgnore'; + +$(".ignoreEpisode").live("click", function () { + var toggle = $(this); + var ignored = toggle.hasClass('ignored'); + + if (ignored) { + toggle.removeClass('ignored'); + toggle.attr('src', notIgnoredImage); + } + + else { + toggle.addClass('ignored'); + toggle.attr('src', ignoredImage); + } + + var seasonNumber = 0; + + //Flip the ignored to the new state (We want the new value moving forward) + ignored = !ignored; + + if (toggle.hasClass('ignoredEpisodesMaster')) { + seasonNumber = toggle.attr('id').replace('master_', ''); + + toggleChildren(seasonNumber, ignored); + saveSeasonIgnore(seasonNumber, ignored); + } + + else { + //Check to see if this is the last one ignored or the first not ignored + seasonNumber = toggle.attr('class').split(/\s+/)[1].replace('ignoreEpisode_', ''); + var episodeId = toggle.attr('id'); + toggleMaster(seasonNumber, ignored); + saveEpisodeIgnore(episodeId, ignored); + } +}); + +function toggleChildren(seasonNumber, ignored) { + var ignoreEpisodes = $('.ignoreEpisode_' + seasonNumber); + + if (ignored) { + ignoreEpisodes.each(function (index) { + $(this).addClass('ignored'); + $(this).attr('src', ignoredImage); + }); + } + + else { + ignoreEpisodes.each(function (index) { + $(this).removeClass('ignored'); + $(this).attr('src', notIgnoredImage); + }); + } +} + +function toggleMaster(seasonNumber) { + var ignoreEpisodes = $('.ignoreEpisode_' + seasonNumber); + var ignoredCount = ignoreEpisodes.filter('.ignored').length; + var master = $('#master_' + seasonNumber); + + if (ignoreEpisodes.length == ignoredCount) { + master.attr('src', ignoredImage); + master.addClass('ignored'); + } + + else { + master.attr('src', notIgnoredImage); + master.removeClass('ignored'); + } +} + +//Functions called by the Telerik Season Grid +function grid_rowBound(e) { + var dataItem = e.dataItem; + var ignored = dataItem.Ignored; + var episodeId = dataItem.EpisodeId; + + var ignoredIcon = $('#' + episodeId); + + if (ignored) { + ignoredIcon.attr('src', ignoredImage); + } + + else { + ignoredIcon.attr('src', notIgnoredImage); + ignoredIcon.removeClass('ignored'); + } + + if (seriesId == 0) + seriesId = dataItem.SeriesId +} + +function grid_dataBound(e) { + var id = $(this).attr('id'); + var seasonNumber = id.replace('seasons_', ''); + var ignoreEpisodes = $('.ignoreEpisode_' + seasonNumber); + var master = $('#master_' + seasonNumber); + var count = ignoreEpisodes.length; + var ignoredCount = ignoreEpisodes.filter('.ignored').length; + + if (ignoredCount == count) { + master.attr('src', ignoredImage); + master.addClass('ignored'); + } + + else { + master.attr('src', notIgnoredImage); + master.removeClass('ignored'); + } +} + +function saveSeasonIgnore(seasonNumber, ignored) { + $.ajax({ + type: "POST", + url: saveSeasonIgnoreUrl, + data: jQuery.param({ seriesId: seriesId, seasonNumber: seasonNumber, ignored: ignored }), + error: function (req, status, error) { + alert("Sorry! We could save the ignore settings for Series: " + seriesId + ", Season: " + seasonNumber + " at this time. " + error); + } + }); +} + +function saveEpisodeIgnore(episodeId, ignored) { + $.ajax({ + type: "POST", + url: saveEpisodeIgnoreUrl, + data: jQuery.param({ episodeId: episodeId, ignored: ignored }), + error: function (req, status, error) { + alert("Sorry! We could save the ignore settings for Episode: " + episodeId + " at this time. " + error); + } + }); +} \ No newline at end of file diff --git a/NzbDrone.Web/Scripts/settingsForm.js b/NzbDrone.Web/Scripts/settingsForm.js index d4b051599..dfb7b8609 100644 --- a/NzbDrone.Web/Scripts/settingsForm.js +++ b/NzbDrone.Web/Scripts/settingsForm.js @@ -7,6 +7,7 @@ resetForm: false }; $('#form').ajaxForm(options); + $('#save_button').removeAttr('disabled'); }); function showRequest(formData, jqForm, options) { diff --git a/NzbDrone.Web/Views/History/Index.cshtml b/NzbDrone.Web/Views/History/Index.cshtml index 360180511..a5f67248f 100644 --- a/NzbDrone.Web/Views/History/Index.cshtml +++ b/NzbDrone.Web/Views/History/Index.cshtml @@ -4,11 +4,10 @@ History } @section ActionMenu{ - @{Html.Telerik().Menu().Name("historyMenu").Items(items => - { - items.Add().Text("Trim History").Action("Trim", "History"); - items.Add().Text("Purge History").Action("Purge", "History"); - }).Render();} + <ul id="sub-menu"> + <li>@Ajax.ActionLink("Trim History", "Trim", "History", new AjaxOptions{ OnSuccess = "reloadGrid" })</li> + <li>@Ajax.ActionLink("Purge History", "Purge", "History", new AjaxOptions{ OnSuccess = "reloadGrid" })</li> + </ul> } @section MainContent{ <div class="grid-container"> @@ -42,3 +41,12 @@ History .Render();} </div> } + +<script type="text/javascript"> + +function reloadGrid() { + var grid = $('#history').data('tGrid'); + grid.rebind(); +} + +</script> \ No newline at end of file diff --git a/NzbDrone.Web/Views/Log/Index.cshtml b/NzbDrone.Web/Views/Log/Index.cshtml index 656d6cd87..6d6c0a5c4 100644 --- a/NzbDrone.Web/Views/Log/Index.cshtml +++ b/NzbDrone.Web/Views/Log/Index.cshtml @@ -22,10 +22,13 @@ @section TitleContent{ Logs } + @section ActionMenu{ - @{Html.Telerik().Menu().Name("logMenu").Items(items => items.Add().Text("Clear Logs").Action("Clear", "Log")) - .Render();} + <ul id="sub-menu"> + <li>@Ajax.ActionLink("Clear Logs", "Clear", "Log", new AjaxOptions{ OnSuccess = "reloadGrid" })</li> + </ul> } + @section MainContent{ @{Html.Telerik().Grid(Model).Name("logsGrid") .TableHtmlAttributes(new { @class = "Grid" }) @@ -47,3 +50,10 @@ Logs .ClientEvents(c => c.OnRowDataBound("onRowDataBound")) .Render();} } + +<script type="text/javascript"> + function reloadGrid() { + var grid = $('#logsGrid').data('tGrid'); + grid.rebind(); + } +</script> \ No newline at end of file diff --git a/NzbDrone.Web/Views/Series/Details.cshtml b/NzbDrone.Web/Views/Series/Details.cshtml index 76b412db7..4405cfd21 100644 --- a/NzbDrone.Web/Views/Series/Details.cshtml +++ b/NzbDrone.Web/Views/Series/Details.cshtml @@ -4,44 +4,37 @@ @section TitleContent{ @Model.Title } + +<script src="../../Scripts/seriesDetails.js" type="text/javascript"></script> + +<style> + .ignoreEpisode + { + width: 18px; + height: 18px; + padding-bottom: -6px; + } + + .ignoredEpisodesMaster + { + width: 18px; + height: 18px; + padding-left: 18px; + padding-right: -18px; + } +</style> + @section ActionMenu{ - @{Html.Telerik().Menu().Name("SeriesMenu").Items(items => - { - items.Add().Text("Back to Series List").Action("Index", "Series"); - items.Add().Text("Scan For Episodes on Disk") - .Action("SyncEpisodesOnDisk", "Series", new { seriesId = Model.SeriesId }); - items.Add().Text("Update Info").Action("UpdateInfo", "Series", new { seriesId = Model.SeriesId }); - items.Add().Text("Rename Series").Action("RenameSeries", "Series", new { seriesId = Model.SeriesId }); - }).Render();} + <ul id="sub-menu"> + <li>@Html.ActionLink("Back to Series List", "Index", "Series")</li> + <li>@Ajax.ActionLink("Scan For Episodes on Disk", "SyncEpisodesOnDisk", "Command", new { seriesId = Model.SeriesId }, null)</li> + <li>@Ajax.ActionLink("Update Info", "UpdateInfo", "Command", new { seriesId = Model.SeriesId }, null)</li> + <li>@Ajax.ActionLink("Rename Series", "RenameSeries", "Command", new { seriesId = Model.SeriesId }, null)</li> + </ul> } @section MainContent{ - <fieldset> - <div class="display-label"> - ID</div> - <div class="display-field"> - @Model.SeriesId</div> - <div class="display-label"> - Overview</div> - <div class="display-field"> - @Model.Overview</div> - <div class="display-label"> - Status</div> - <div class="display-field"> - @Model.Status</div> - <div class="display-label"> - AirTimes</div> - <div class="display-field"> - @Model.AirsDayOfWeek</div> - <div class="display-label"> - Language</div> - <div class="display-label"> - Location</div> - <div class="display-field"> - @Model.Path</div> - </fieldset> @foreach (var season in Model.Seasons.Where(s => s > 0).Reverse()) { - <br /> <h3> Season @season</h3> <div class="grid-container"> @@ -50,11 +43,11 @@ .TableHtmlAttributes(new { @class = "Grid" }) .Columns(columns => { - columns.Bound(o => o.EpisodeId) + columns.Bound(o => o.Ignored) + .Title("<img src='../../Content/Images/ignoredNeutral.png' class='ignoredEpisodesMaster ignoreEpisode' id='master_" + season + "' />") .ClientTemplate( - "<input type='checkbox' name='checkedEpisodes' value='<#= EpisodeId #>' />") - .Title("") - .Width(1) + "<img src='../../Content/Images/ignoredNeutral.png' class='ignoreEpisode ignoreEpisode_" + season + " ignored' id='<#= EpisodeId #>' />") + .Width(20) .HtmlAttributes(new { style = "text-align:center" }); columns.Bound(c => c.EpisodeNumber).Width(0).Title("Episode"); @@ -68,7 +61,7 @@ "<a href='#Rename' onClick=\"renameEpisode('<#= EpisodeFileId #>'); return false;\">Rename</a>"); }) .DetailView(detailView => detailView.ClientTemplate("<div><#= Overview #> </br><#= Path #> </div>")) - .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber).Descending()).Enabled(true)) + .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber).Descending()).Enabled(false)) .Footer(true) .DataBinding( d => @@ -78,12 +71,16 @@ c => c.Custom().Text("Rename Season").Action("RenameSeason", "Series", new { seasonId = season }) .ButtonType(GridButtonType.Text)) + .ClientEvents(clientEvents => + { + clientEvents.OnRowDataBound("grid_rowBound"); + clientEvents.OnDataBound("grid_dataBound"); + }) .Render();} </div> } @if (Model.Seasons.Any(s => s == 0)) { - <br /> <h3> Specials</h3> @@ -92,11 +89,11 @@ .TableHtmlAttributes(new { @class = "Grid" }) .Columns(columns => { - columns.Bound(o => o.EpisodeId) + columns.Bound(o => o.Ignored) + .Title("<img src='../../Content/Images/ignoredNeutral.png' class='ignoredEpisodesMaster ignoreEpisode' id='master_0' />") .ClientTemplate( - "<input type='checkbox' name='checkedEpisodes' value='<#= EpisodeId #>' />") - .Title("") - .Width(1) + "<img src='../../Content/Images/ignoredNeutral.png' class='ignoreEpisode ignoreEpisode_0 ignored' id='<#= EpisodeId #>' />") + .Width(20) .HtmlAttributes(new { style = "text-align:center" }); columns.Bound(c => c.EpisodeNumber).Width(10).Title("Episode"); @@ -106,7 +103,7 @@ columns.Bound(c => c.Status).Width(10); }) .DetailView(detailView => detailView.ClientTemplate("<div><#= Overview #> </br><#= Path #> </div>")) - .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber).Descending()).Enabled(true)) + .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber).Descending()).Enabled(false)) .Footer(true) .DataBinding( d => @@ -134,6 +131,8 @@ } }); } + + seriesId = @Model.SeriesId; </script> } diff --git a/NzbDrone.Web/Views/Series/EditorTemplates/SeriesModel.cshtml b/NzbDrone.Web/Views/Series/EditorTemplates/SeriesModel.cshtml index 66426ce84..8beff06af 100644 --- a/NzbDrone.Web/Views/Series/EditorTemplates/SeriesModel.cshtml +++ b/NzbDrone.Web/Views/Series/EditorTemplates/SeriesModel.cshtml @@ -5,31 +5,29 @@ Layout = null; } -<div class=".settingsForm"> - @Html.HiddenFor(m => m.SeriesId) - <label class="labelClass">@Html.LabelFor(m => m.Monitored) - <span class="small">@Html.DescriptionFor(m => m.Monitored)</span> - </label> - @Html.CheckBoxFor(m => m.Monitored, new { @class = "inputClass checkClass" }) - <label class="labelClass">@Html.LabelFor(m => m.SeasonFolder) - <span class="small">@Html.DescriptionFor(m => m.SeasonFolder)</span> - </label> - @Html.CheckBoxFor(m => m.SeasonFolder, new { @class = "inputClass checkClass" }) - <label class="labelClass">@Html.LabelFor(m => m.QualityProfileId) - <span class="small">@Html.DescriptionFor(m => m.QualityProfileId)</span> - </label> - @Html.DropDownListFor(m => m.QualityProfileId, (SelectList)ViewData["SelectList"], new { @class = "inputClass" }) - - <div id="seasonEditorSection"> - <div style="font-weight: bold; padding-right: 15px; padding-bottom: 5px;"> - @Html.LabelFor(m => m.SeasonEditor) - <span id="seasonEditorLoader"> - <img src="../../../Content/Images/ajax-loader.gif" width="14px" height="14px" style="margin-bottom: -2px;" /></span> - </div> - <div id="season-editor"> - </div> +<link rel="stylesheet" type="text/css" href="../../../Content/Settings.css" /> + +<div id="stylized" style="border-color: transparent;"> + <div class="settingsForm clearfix"> + @Html.HiddenFor(m => m.SeriesId) + <label class="labelClass">@Html.LabelFor(m => m.Monitored) + <span class="small">@Html.DescriptionFor(m => m.Monitored)</span> + </label> + @Html.CheckBoxFor(m => m.Monitored, new { @class = "inputClass checkClass" }) + <label class="labelClass">@Html.LabelFor(m => m.SeasonFolder) + <span class="small">@Html.DescriptionFor(m => m.SeasonFolder)</span> + </label> + @Html.CheckBoxFor(m => m.SeasonFolder, new { @class = "inputClass checkClass" }) + <label class="labelClass">@Html.LabelFor(m => m.QualityProfileId) + <span class="small">@Html.DescriptionFor(m => m.QualityProfileId)</span> + </label> + @Html.DropDownListFor(m => m.QualityProfileId, (SelectList)ViewData["SelectList"], new { @class = "inputClass" }) + <label class="labelClass">@Html.LabelFor(m => m.Path) + <span class="small">@Html.DescriptionFor(m => m.Path)</span> + </label> + @Html.TextBoxFor(m => m.Path, new { @class = "inputClass" }) </div> </div> -<span id="ajaxSaveWheel" style="display: none; float: right; padding-right: 368px; +<span id="ajaxSaveWheel" style="display: none; float: right; padding-right: 550px; padding-top: 1.5px;"> <img src="../../../Content/Images/ajax-loader.gif" width="20px" height="20px" /></span> \ No newline at end of file diff --git a/NzbDrone.Web/Views/Series/Index.cshtml b/NzbDrone.Web/Views/Series/Index.cshtml index 380e19584..fee117b0d 100644 --- a/NzbDrone.Web/Views/Series/Index.cshtml +++ b/NzbDrone.Web/Views/Series/Index.cshtml @@ -1,9 +1,11 @@ @using NzbDrone.Core.Repository; +@using NzbDrone.Web.Controllers @using NzbDrone.Web.Models; @model IEnumerable<NzbDrone.Core.Repository.Series> @section TitleContent{ NZBDrone } + <style> /* progress bar container */ .progressbar @@ -46,8 +48,12 @@ NZBDrone background: #E5ECF9; } </style> + @section ActionMenu{ - @{Html.RenderPartial("SubMenu");} + <ul id="sub-menu"> + <li>@Html.ActionLink("Add Series", "Index", "AddSeries")</li> + <li>@Ajax.ActionLink("Start RSS Sync", "RssSync", "Command", null)</li> + </ul> } @section MainContent{ <div class="grid-container"> @@ -103,37 +109,10 @@ NZBDrone .closest(".t-window") .data("tWindow") .center(); - - var seriesId = args.dataItem.SeriesId; - var url = '@Url.Action("SeasonEditor", "Series")'; - $('#season-editor').load(url, { seriesId: seriesId }, function (response, status, xhr) { - $('#seasonEditorLoader').hide(); - }); } function grid_save(e) { $('#ajaxSaveWheel').show(); - - var seasonEditor = e.form.SeasonEditor_collection; - var saveSeasonEditUrl = '@Url.Action("SaveSeason", "Series")'; - - jQuery.each(seasonEditor, function () { - var guid = $(this).val(); - var prefix = '#SeasonEditor_' + guid + '__'; - var seriesId = $(prefix + 'SeriesId').val(); - var seasonNumber = $(prefix + 'SeasonNumber').val(); - var monitored = $(prefix + 'Monitored').attr('checked'); - - $.ajax({ - type: "POST", - url: saveSeasonEditUrl, - data: jQuery.param({ seriesId: seriesId, seasonNumber: seasonNumber, monitored: monitored }), - error: function (req, status, error) { - alert("Sorry! We could save season changes at this time. " + error); - }, - success: function (data, textStatus, jqXHR) { } - }); - }); } function grid_rowBound(e) { @@ -144,8 +123,7 @@ NZBDrone $("#progressbar_" + seriesId).episodeProgress(episodeFileCount, episodeCount); } - </script> - <script type="text/javascript"> + (function ($) { $.fn.episodeProgress = function (episodes, totalEpisodes) { return this.each( diff --git a/NzbDrone.Web/Views/Series/SeasonEditor.cshtml b/NzbDrone.Web/Views/Series/SeasonEditor.cshtml deleted file mode 100644 index 3aad85958..000000000 --- a/NzbDrone.Web/Views/Series/SeasonEditor.cshtml +++ /dev/null @@ -1,36 +0,0 @@ -@using NzbDrone.Web.Models; -@model List<SeasonEditModel> -@{ - Layout = null; -} -<div style="vertical-align: middle"> - @foreach (var season in Model) - { - Html.RenderAction("GetSingleSeasonView", "Series", season); - } -</div> -@section Scripts{ - <script type="text/javascript"> - var lastChecked = null; - - $(document).ready(function () { - $('.chkbox').click(function (event) { - if (!lastChecked) { - lastChecked = this; - return; - } - - if (event.shiftKey) { - var start = $('.chkbox').index(this); - var end = $('.chkbox').index(lastChecked); - - for (i = Math.min(start, end); i <= Math.max(start, end); i++) { - $('.chkbox')[i].checked = lastChecked.checked; - } - } - - lastChecked = this; - }); - }); - </script> -} diff --git a/NzbDrone.Web/Views/Series/SubMenu.cshtml b/NzbDrone.Web/Views/Series/SubMenu.cshtml deleted file mode 100644 index 7f599b762..000000000 --- a/NzbDrone.Web/Views/Series/SubMenu.cshtml +++ /dev/null @@ -1,7 +0,0 @@ -@using NzbDrone.Web.Controllers - -@{Html.Telerik().Menu().Name("telerikGrid").Items(items => - { - items.Add().Text("Add Series").Action<AddSeriesController>(c => c.Index()); - items.Add().Text("Start RSS Sync").Action<SeriesController>(c => c.RssSync()); - }).Render();} \ No newline at end of file diff --git a/NzbDrone.Web/Views/Settings/EpisodeSorting.cshtml b/NzbDrone.Web/Views/Settings/EpisodeSorting.cshtml index dd96cf082..1bc05aa36 100644 --- a/NzbDrone.Web/Views/Settings/EpisodeSorting.cshtml +++ b/NzbDrone.Web/Views/Settings/EpisodeSorting.cshtml @@ -81,7 +81,7 @@ </div> </div> - <button type="submit" id="save_button" >Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> + <button type="submit" id="save_button" disabled="disabled">Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> } </div> <div id="result" class="hiddenResult"></div> diff --git a/NzbDrone.Web/Views/Settings/Indexers.cshtml b/NzbDrone.Web/Views/Settings/Indexers.cshtml index 1f0e104f7..0d8927859 100644 --- a/NzbDrone.Web/Views/Settings/Indexers.cshtml +++ b/NzbDrone.Web/Views/Settings/Indexers.cshtml @@ -9,8 +9,7 @@ { padding-top: 20px; } - -</style> + </style> } @section TitleContent{ @@ -26,7 +25,7 @@ @using (Html.BeginForm("SaveIndexers", "Settings", FormMethod.Post, new { id = "form", name = "form", @class = "settingsForm" })) { - <h1>Indexer</h1> + <h1>Indexers</h1> <p></p> @Html.ValidationSummary(true, "Unable to save your settings. Please correct the errors and try again.") @@ -126,10 +125,14 @@ } </div> <br/> - <button type="submit" id="save_button" >Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> + <button type="submit" id="save_button" disabled="disabled">Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> } </div> <div id="result" class="hiddenResult"></div> +} + +@section Scripts{ + <script src="/Scripts/settingsForm.js" type="text/javascript"></script> } \ No newline at end of file diff --git a/NzbDrone.Web/Views/Settings/Notifications.cshtml b/NzbDrone.Web/Views/Settings/Notifications.cshtml index 0a040bdae..e5e859d63 100644 --- a/NzbDrone.Web/Views/Settings/Notifications.cshtml +++ b/NzbDrone.Web/Views/Settings/Notifications.cshtml @@ -92,7 +92,7 @@ @Html.TextBoxFor(m => m.XbmcPassword, new { @class = "inputClass" }) </div> - <button type="submit" id="save_button" >Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> + <button type="submit" id="save_button" disabled="disabled">Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> } </div> diff --git a/NzbDrone.Web/Views/Settings/Quality.cshtml b/NzbDrone.Web/Views/Settings/Quality.cshtml index b27bed24c..7be8f82ed 100644 --- a/NzbDrone.Web/Views/Settings/Quality.cshtml +++ b/NzbDrone.Web/Views/Settings/Quality.cshtml @@ -40,7 +40,7 @@ Settings </div> </div> <br /> - <button type="submit" id="save_button" >Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> + <button type="submit" id="save_button" disabled="disabled">Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> </div> } </div> diff --git a/NzbDrone.Web/Views/Settings/Sabnzbd.cshtml b/NzbDrone.Web/Views/Settings/Sabnzbd.cshtml index a731bc2d7..03696c16c 100644 --- a/NzbDrone.Web/Views/Settings/Sabnzbd.cshtml +++ b/NzbDrone.Web/Views/Settings/Sabnzbd.cshtml @@ -66,7 +66,7 @@ </label> @Html.TextBoxFor(m => m.SabDropDirectory, new { @class = "inputClass folderLookup" }) - <button type="submit" id="save_button" >Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> + <button type="submit" id="save_button" disabled="disabled">Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/> } </div> diff --git a/NzbDrone.Web/Views/Settings/SubMenu.cshtml b/NzbDrone.Web/Views/Settings/SubMenu.cshtml index ba71af2df..7e728e81d 100644 --- a/NzbDrone.Web/Views/Settings/SubMenu.cshtml +++ b/NzbDrone.Web/Views/Settings/SubMenu.cshtml @@ -1,13 +1,9 @@ -@{Html.Telerik().Menu().Name("SubMenu").Items(items => - { - items.Add().Text("Indexers").Action("Indexers", "Settings"); - items.Add().Text("SABnzbd").Action("Sabnzbd", "Settings"); - items.Add().Text("Quality").Action("Quality", "Settings"); - items.Add().Text("Episode Sorting").Action("EpisodeSorting", - "Settings"); - items.Add().Text("Notifications").Action("Notifications", - "Settings"); - }).Render(); -} +<ul id="sub-menu"> + <li>@Html.ActionLink("Indexers", "Indexers", "Settings")</li> + <li>@Html.ActionLink("SABnzbd", "Sabnzbd", "Settings")</li> + <li>@Html.ActionLink("Quality", "Quality", "Settings")</li> + <li>@Html.ActionLink("Episode Sorting", "EpisodeSorting", "Settings")</li> + <li>@Html.ActionLink("Notifications", "Notifications", "Settings")</li> + </ul> <div style="margin-bottom: 10px"></div> \ No newline at end of file diff --git a/NzbDrone.Web/Views/Settings/Test.cshtml b/NzbDrone.Web/Views/Settings/Test.cshtml index 27792a291..5f282702b 100644 --- a/NzbDrone.Web/Views/Settings/Test.cshtml +++ b/NzbDrone.Web/Views/Settings/Test.cshtml @@ -1,94 +1 @@ -<style> -/*body{ -font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; -font-size:12px; -} -p, h1, form, button{border:0; margin:0; padding:0;}*/ -.spacer{clear:both; height:1px;} -/* ----------- My Form ----------- */ -.myform{ -margin:0 auto; -width:400px; -padding:14px; -} - -/* ----------- stylized ----------- */ -#stylized{ -border:solid 2px #b7ddf2; -background:#ebf4fb; -} -#stylized h1 { -font-size:14px; -font-weight:bold; -margin-bottom:8px; -} -#stylized p{ -font-size:11px; -color:#666666; -margin-bottom:20px; -border-bottom:solid 1px #b7ddf2; -padding-bottom:10px; -} -#stylized label{ -display:block; -font-weight:bold; -text-align:right; -width:140px; -float:left; -} -#stylized .small{ -color:#666666; -display:block; -font-size:11px; -font-weight:normal; -text-align:right; -width:140px; -} -#stylized input{ -float:left; -font-size:12px; -padding:4px 2px; -border:solid 1px #aacfe4; -width:200px; -margin:2px 0 20px 10px; -} -#stylized button{ -clear:both; -margin-left:150px; -width:125px; -height:31px; -background:#666666 url(img/button.png) no-repeat; -text-align:center; -line-height:31px; -color:#FFFFFF; -font-size:11px; -font-weight:bold; -} -</style> - -<div id="stylized" class="myform"> - <form id="form" name="form" method="post"> - <fieldset> - <h1>Sign-up form</h1> - <p>This is the basic look of my form without table</p> - - <label>Name - <span class="small">Add your name</span> - </label> - <input type="text" name="name" id="name" /> - - <label>Email - <span class="small">Add a valid address</span> - </label> - <input type="text" name="email" id="email" /> - - <label>Password - <span class="small">Min. size 6 chars</span> - </label> - <input type="text" name="password" id="password" /> - - <button type="submit">Sign-up</button> - <div class="spacer"></div> - </fieldset> - </form> -</div> \ No newline at end of file + \ No newline at end of file diff --git a/NzbDrone.Web/Views/Shared/_Layout.cshtml b/NzbDrone.Web/Views/Shared/_Layout.cshtml index 510c9733f..e0f040b4b 100644 --- a/NzbDrone.Web/Views/Shared/_Layout.cshtml +++ b/NzbDrone.Web/Views/Shared/_Layout.cshtml @@ -13,6 +13,7 @@ <link type="text/css" rel="stylesheet" href="/Content/Notibar.css" /> <link type="text/css" rel="stylesheet" href="/Content/ActionButton.css" /> <link type="text/css" rel="stylesheet" href="/Content/overrides.css" /> + <link type="text/css" rel="stylesheet" href="/Content/Menu.css" /> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/yui/3.3.0/build/yui/yui-min.js"></script> diff --git a/NzbDrone.Web/Views/Upcoming/Index.cshtml b/NzbDrone.Web/Views/Upcoming/Index.cshtml index 492d18a5d..0522ea0d2 100644 --- a/NzbDrone.Web/Views/Upcoming/Index.cshtml +++ b/NzbDrone.Web/Views/Upcoming/Index.cshtml @@ -4,8 +4,9 @@ Upcoming } @section ActionMenu{ - @{Html.Telerik().Menu().Name("historyMenu").Items( - items => { items.Add().Text("Start RSS Sync").Action("RssSync", "Series"); }).Render();} + <ul id="sub-menu"> + <li>@Ajax.ActionLink("Start RSS Sync", "RssSync", "Command", null, null)</li> + </ul> } @section MainContent{ <div id="yesterday">