diff --git a/.gitignore b/.gitignore index 6dd830e09..f091a3646 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,4 @@ _ReSharper*/ [Ll]ogs/ /[Pp]ackage/ #NZBDrone specific -nzbdrone.db \ No newline at end of file +*.db \ No newline at end of file diff --git a/IISExpress/config/redirection.config b/IISExpress/config/redirection.config new file mode 100644 index 000000000..270935794 --- /dev/null +++ b/IISExpress/config/redirection.config @@ -0,0 +1,15 @@ + + + +
+ + + + + + + + + + + diff --git a/NzbDrone.Core.Test/Fixtures.cs b/NzbDrone.Core.Test/Fixtures.cs index 0f35747e4..609518e78 100644 --- a/NzbDrone.Core.Test/Fixtures.cs +++ b/NzbDrone.Core.Test/Fixtures.cs @@ -1,6 +1,9 @@ using System; using System.IO; using MbUnit.Framework; +using NLog; +using NLog.Config; +using System.Linq; namespace NzbDrone.Core.Test { @@ -10,24 +13,43 @@ namespace NzbDrone.Core.Test [TearDown] public void TearDown() { - var dbFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.testdb"); - - foreach (var dbFile in dbFiles) + foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.db", SearchOption.AllDirectories)) { try { - File.Delete(dbFile); + File.Delete(file); } catch { } + } + } + [FixtureTearDown] + public void FixtureTearDown() + { + foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.*", SearchOption.AllDirectories)) + { + try + { + File.Delete(file); + } + catch { } } } [SetUp] - public void Setup() + public void SetUp() { - Instrumentation.Setup(); + try + { + LogManager.Configuration = new XmlLoggingConfiguration(Path.Combine(CentralDispatch.AppPath, "log.config"), false); + } + catch (Exception e) + { + Console.WriteLine("Unable to configure logging. " + e); + } } + + } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/MediaFileProviderTests.cs b/NzbDrone.Core.Test/MediaFileProviderTests.cs index dbbb77012..2c2fc1be3 100644 --- a/NzbDrone.Core.Test/MediaFileProviderTests.cs +++ b/NzbDrone.Core.Test/MediaFileProviderTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using System.Text; +using FizzWare.NBuilder; using Gallio.Framework; using MbUnit.Framework; using MbUnit.Framework.ContractVerifiers; @@ -18,68 +20,144 @@ namespace NzbDrone.Core.Test // ReSharper disable InconsistentNaming public class MediaFileProviderTests { + [Test] - public void scan_test() + [Description("Verifies that a new file imported properly")] + public void import_new_file() { //Arrange - var repository = new Mock(); - repository.Setup(c => c.Update(It.IsAny())).Verifiable(); + ///////////////////////////////////////// - var diskProvider = MockLib.GetStandardDisk(1, 2); + //Constants + const string fileName = "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi"; + const int seasonNumber = 3; + const int episodeNumner = 01; + const int size = 12345; + + //Fakes + var fakeSeries = Builder.CreateNew().Build(); + var fakeEpisode = Builder.CreateNew().With(c => c.SeriesId = fakeSeries.SeriesId).Build(); + + //Mocks + var repository = new Mock(); + repository.Setup(r => r.Exists(It.IsAny>>())).Returns(false).Verifiable(); + + var episodeProvider = new Mock(); + episodeProvider.Setup(e => e.GetEpisode(fakeSeries.SeriesId, seasonNumber, episodeNumner)).Returns(fakeEpisode).Verifiable(); + + var diskProvider = new Mock(); + diskProvider.Setup(e => e.GetSize(fileName)).Returns(12345).Verifiable(); var kernel = new MockingKernel(); - kernel.Bind().ToConstant(diskProvider); kernel.Bind().ToConstant(repository.Object); + + kernel.Bind().ToConstant(episodeProvider.Object); + kernel.Bind().ToConstant(diskProvider.Object); kernel.Bind().To(); - var fakeSeries = new Series() - { - Path = MockLib.StandardSeries[0] - }; - //Act - kernel.Get().Scan(fakeSeries); + var result = kernel.Get().ImportFile(fakeSeries, fileName); //Assert - repository.Verify(c => c.Update(It.IsAny()), Times.Exactly(1 * 2)); + repository.VerifyAll(); + episodeProvider.VerifyAll(); + diskProvider.VerifyAll(); + Assert.IsNotNull(result); + repository.Verify(r => r.Add(result), Times.Once()); + + Assert.AreEqual(fakeEpisode.EpisodeId, result.EpisodeId); + Assert.AreEqual(fakeEpisode.SeriesId, result.SeriesId); + Assert.AreEqual(QualityTypes.DVD, result.Quality); + Assert.AreEqual(Parser.NormalizePath(fileName), result.Path); + Assert.AreEqual(size, result.Size); + Assert.AreEqual(false, result.Proper); + Assert.AreNotEqual(new DateTime(), result.DateAdded); + } + [Test] + [Description("Verifies that an existing file will skip import")] + public void import_existing_file() + { + //Arrange + ///////////////////////////////////////// + + //Constants + const string fileName = "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi"; + + //Fakes + var fakeSeries = Builder.CreateNew().Build(); + var fakeEpisode = Builder.CreateNew().With(c => c.SeriesId = fakeSeries.SeriesId).Build(); + + //Mocks + var repository = new Mock(MockBehavior.Strict); + repository.Setup(r => r.Exists(It.IsAny>>())).Returns(true).Verifiable(); + + var episodeProvider = new Mock(MockBehavior.Strict); + var diskProvider = new Mock(MockBehavior.Strict); + + var kernel = new MockingKernel(); + kernel.Bind().ToConstant(repository.Object); + + kernel.Bind().ToConstant(episodeProvider.Object); + kernel.Bind().ToConstant(diskProvider.Object); + kernel.Bind().To(); + + //Act + var result = kernel.Get().ImportFile(fakeSeries, fileName); + + //Assert + repository.VerifyAll(); + episodeProvider.VerifyAll(); + diskProvider.VerifyAll(); + Assert.IsNull(result); + repository.Verify(r => r.Add(result), Times.Never()); } [Test] - [Row("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", 3, 1)] - [Row("Two.and.a.Half.Me.103.720p.HDTV.X264-DIMENSION", 1, 3)] - [Row("Chuck.4x05.HDTV.XviD-LOL", 4, 5)] - [Row("The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", 3, 6)] - [Row("Degrassi.S10E27.WS.DSR.XviD-2HD", 10, 27)] - public void episode_parse(string path, int season, int episode) + [Description("Verifies that a file with no episode is skipped")] + public void import_file_with_no_episode() { - var result = Parser.ParseEpisodeInfo(path); - Assert.Count(1, result); - Assert.AreEqual(season, result[0].SeasonNumber); - Assert.AreEqual(episode, result[0].EpisodeNumber); + //Arrange + ///////////////////////////////////////// + + //Constants + const string fileName = "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi"; + const int seasonNumber = 3; + const int episodeNumner = 01; + + //Fakes + var fakeSeries = Builder.CreateNew().Build(); + + //Mocks + var repository = new Mock(MockBehavior.Strict); + repository.Setup(r => r.Exists(It.IsAny>>())).Returns(false).Verifiable(); + + var episodeProvider = new Mock(MockBehavior.Strict); + episodeProvider.Setup(e => e.GetEpisode(fakeSeries.SeriesId, seasonNumber, episodeNumner)).Returns(null).Verifiable(); + + var diskProvider = new Mock(MockBehavior.Strict); + + + var kernel = new MockingKernel(); + kernel.Bind().ToConstant(repository.Object); + kernel.Bind().ToConstant(episodeProvider.Object); + kernel.Bind().ToConstant(diskProvider.Object); + kernel.Bind().To(); + + //Act + var result = kernel.Get().ImportFile(fakeSeries, fileName); + + //Assert + repository.VerifyAll(); + episodeProvider.VerifyAll(); + diskProvider.VerifyAll(); + Assert.IsNull(result); + repository.Verify(r => r.Add(result), Times.Never()); } - [Test] - [Row("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", QualityTypes.DVD)] - [Row("WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", QualityTypes.Bluray)] - [Row("Two.and.a.Half.Men.S08E05.720p.HDTV.X264-DIMENSION", QualityTypes.HDTV)] - [Row("Chuck.S04E05.HDTV.XviD-LOL", QualityTypes.TV)] - [Row("The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", QualityTypes.DVD)] - [Row("Degrassi.S10E27.WS.DSR.XviD-2HD", QualityTypes.TV)] - [Row("Sonny.With.a.Chance.S02E15.720p.WEB-DL.DD5.1.H.264-SURFER", QualityTypes.WEBDL)] - [Row("Sonny.With.a.Chance.S02E15.720p", QualityTypes.HDTV)] - [Row("Sonny.With.a.Chance.S02E15.mkv", QualityTypes.HDTV)] - [Row("Sonny.With.a.Chance.S02E15.avi", QualityTypes.TV)] - [Row("Sonny.With.a.Chance.S02E15.xvid", QualityTypes.TV)] - [Row("Sonny.With.a.Chance.S02E15.divx", QualityTypes.TV)] - [Row("Sonny.With.a.Chance.S02E15", QualityTypes.Unknown)] - public void quality_parse(string path, object quality) - { - var result = Parser.ParseQuality(path); - Assert.AreEqual(quality, result); - } + } diff --git a/NzbDrone.Core.Test/MockLib.cs b/NzbDrone.Core.Test/MockLib.cs index 1a23a270c..0697e9726 100644 --- a/NzbDrone.Core.Test/MockLib.cs +++ b/NzbDrone.Core.Test/MockLib.cs @@ -7,6 +7,7 @@ using System.Text; using FizzWare.NBuilder; using Moq; using NLog; +using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Providers; using SubSonic.DataProviders; using SubSonic.Repository; @@ -32,10 +33,10 @@ namespace NzbDrone.Core.Test public static IRepository GetEmptyRepository(bool enableLogging) { Console.WriteLine("Creating an empty SQLite database"); - var provider = ProviderFactory.GetProvider("Data Source=" + Guid.NewGuid() + ".testdb;Version=3;New=True", "System.Data.SQLite"); + var provider = ProviderFactory.GetProvider("Data Source=" + Guid.NewGuid() + ".db;Version=3;New=True", "System.Data.SQLite"); if (enableLogging) { - provider.Log = new Instrumentation.NlogWriter(); + provider.Log = new NlogWriter(); provider.LogParams = true; } return new SimpleRepository(provider, SimpleRepositoryOptions.RunMigrations); @@ -55,7 +56,7 @@ namespace NzbDrone.Core.Test { var mock = new Mock(); mock.Setup(c => c.GetDirectories(It.IsAny())).Returns(StandardSeries); - mock.Setup(c => c.Exists(It.Is(d => StandardSeries.Contains(d)))).Returns(true); + mock.Setup(c => c.FolderExists(It.Is(d => StandardSeries.Contains(d)))).Returns(true); foreach (var series in StandardSeries) diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index f360342de..64ed87f4e 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -74,6 +74,7 @@ + diff --git a/NzbDrone.Core.Test/ParserTest.cs b/NzbDrone.Core.Test/ParserTest.cs new file mode 100644 index 000000000..53e686d0d --- /dev/null +++ b/NzbDrone.Core.Test/ParserTest.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Gallio.Framework; +using MbUnit.Framework; +using MbUnit.Framework.ContractVerifiers; +using NzbDrone.Core.Repository.Quality; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class ParserTest + { + [Test] + [Row("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", 3, 1)] + [Row("Two.and.a.Half.Me.103.720p.HDTV.X264-DIMENSION", 1, 3)] + [Row("Chuck.4x05.HDTV.XviD-LOL", 4, 5)] + [Row("The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", 3, 6)] + [Row("Degrassi.S10E27.WS.DSR.XviD-2HD", 10, 27)] + public void episode_parse(string path, int season, int episode) + { + var result = Parser.ParseEpisodeInfo(path); + Assert.Count(1, result); + Assert.AreEqual(season, result[0].SeasonNumber); + Assert.AreEqual(episode, result[0].EpisodeNumber); + } + + [Test] + [Row("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", QualityTypes.DVD)] + [Row("WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", QualityTypes.Bluray)] + [Row("Two.and.a.Half.Men.S08E05.720p.HDTV.X264-DIMENSION", QualityTypes.HDTV)] + [Row("Chuck.S04E05.HDTV.XviD-LOL", QualityTypes.TV)] + [Row("The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", QualityTypes.DVD)] + [Row("Degrassi.S10E27.WS.DSR.XviD-2HD", QualityTypes.TV)] + [Row("Sonny.With.a.Chance.S02E15.720p.WEB-DL.DD5.1.H.264-SURFER", QualityTypes.WEBDL)] + [Row("Sonny.With.a.Chance.S02E15.720p", QualityTypes.HDTV)] + [Row("Sonny.With.a.Chance.S02E15.mkv", QualityTypes.HDTV)] + [Row("Sonny.With.a.Chance.S02E15.avi", QualityTypes.TV)] + [Row("Sonny.With.a.Chance.S02E15.xvid", QualityTypes.TV)] + [Row("Sonny.With.a.Chance.S02E15.divx", QualityTypes.TV)] + [Row("Sonny.With.a.Chance.S02E15", QualityTypes.Unknown)] + public void quality_parse(string path, object quality) + { + var result = Parser.ParseQuality(path); + Assert.AreEqual(quality, result); + } + + [Test] + [Row(@"c:\test\", @"c:\test")] + [Row(@"c:\\test\\", @"c:\test")] + [Row(@"C:\\Test\\", @"c:\test")] + [Row(@"C:\\Test\\Test\", @"c:\test\test")] + public void Normalize_Path(string dirty, string clean) + { + var result = Parser.NormalizePath(dirty); + Assert.AreEqual(clean, result); + } + } +} diff --git a/NzbDrone.Core.Test/RepoTest.cs b/NzbDrone.Core.Test/RepoTest.cs index bf0fa2ce1..f4229acbe 100644 --- a/NzbDrone.Core.Test/RepoTest.cs +++ b/NzbDrone.Core.Test/RepoTest.cs @@ -6,8 +6,13 @@ using FizzWare.NBuilder; using Gallio.Framework; using MbUnit.Framework; using MbUnit.Framework.ContractVerifiers; +using Ninject; +using NLog; +using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Model; using NzbDrone.Core.Repository; +using LogLevel = NzbDrone.Core.Instrumentation.LogLevel; +using NLog.Config; namespace NzbDrone.Core.Test { @@ -62,5 +67,69 @@ namespace NzbDrone.Core.Test Console.WriteLine(new Episode().ToString()); Console.WriteLine(new EpisodeModel().ToString()); } + + [Test] + public void write_log() + { + //setup + var message = Guid.NewGuid().ToString(); + + var sonicRepo = MockLib.GetEmptyRepository(); + + var sonicTarget = new SubsonicTarget(sonicRepo); + LogManager.Configuration.AddTarget("DbLogger", sonicTarget); + LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", NLog.LogLevel.Info, sonicTarget)); + LogManager.Configuration.Reload(); + + Logger Logger = LogManager.GetCurrentClassLogger(); + //Act + + Logger.Info(message); + + //Assert + Assert.IsNotEmpty(sonicRepo.All()); + Assert.Count(1, sonicRepo.All()); + + var logItem = sonicRepo.All().First(); + Assert.AreNotEqual(new DateTime(), logItem.Time); + Assert.AreEqual(message, logItem.Message); + Assert.AreEqual(Logger.Name, logItem.Logger); + Assert.AreEqual(Logger.Name, logItem.Logger); + Assert.AreEqual(LogLevel.Info, logItem.Level); + } + + [Test] + public void write_log_exception() + { + //setup + var message = Guid.NewGuid().ToString(); + + var sonicRepo = MockLib.GetEmptyRepository(); + + var sonicTarget = new SubsonicTarget(sonicRepo); + LogManager.Configuration.AddTarget("DbLogger", sonicTarget); + LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", NLog.LogLevel.Info, sonicTarget)); + LogManager.Configuration.Reload(); + + Logger Logger = LogManager.GetCurrentClassLogger(); + + var ex = new InvalidOperationException("Fake Exception"); + //Act + + Logger.ErrorException(message, ex); + + //Assert + Assert.IsNotEmpty(sonicRepo.All()); + Assert.Count(1, sonicRepo.All()); + + var logItem = sonicRepo.All().First(); + Assert.AreNotEqual(new DateTime(), logItem.Time); + Assert.AreEqual(message, logItem.Message); + Assert.AreEqual(Logger.Name, logItem.Logger); + Assert.AreEqual(LogLevel.Error, logItem.Level); + Assert.AreEqual(ex.GetType().ToString(), logItem.ExceptionType); + Assert.AreEqual(ex.ToString(), logItem.ExceptionString); + Assert.AreEqual(ex.Message, logItem.ExceptionMessage); + } } } diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 0464d7a5a..c78e78771 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -5,6 +5,7 @@ using System.Web; using Ninject; using NLog.Config; using NLog.Targets; +using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Providers; using NzbDrone.Core.Providers.Fakes; using NzbDrone.Core.Repository; @@ -29,10 +30,15 @@ namespace NzbDrone.Core _kernel = new StandardKernel(); string connectionString = String.Format("Data Source={0};Version=3;", Path.Combine(AppPath, "nzbdrone.db")); - var provider = ProviderFactory.GetProvider(connectionString, "System.Data.SQLite"); - provider.Log = new Instrumentation.NlogWriter(); - provider.LogParams = true; + var dbProvider = ProviderFactory.GetProvider(connectionString, "System.Data.SQLite"); + string logConnectionString = String.Format("Data Source={0};Version=3;", Path.Combine(AppPath, "log.db")); + var logDbProvider = ProviderFactory.GetProvider(logConnectionString, "System.Data.SQLite"); + var logRepository = new SimpleRepository(logDbProvider, SimpleRepositoryOptions.RunMigrations); + + dbProvider.Log = new NlogWriter(); + dbProvider.LogParams = true; + _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To(); _kernel.Bind().To(); @@ -41,7 +47,12 @@ namespace NzbDrone.Core _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); - _kernel.Bind().ToMethod(c => new SimpleRepository(provider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope(); + _kernel.Bind().To().InSingletonScope(); + _kernel.Bind().ToMethod(c => new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope(); + + _kernel.Bind().ToConstant(logRepository).WhenInjectedInto().InSingletonScope(); + _kernel.Bind().ToConstant(logRepository).WhenInjectedInto().InSingletonScope(); + ForceMigration(_kernel.Get()); } @@ -57,7 +68,6 @@ namespace NzbDrone.Core } return Directory.GetCurrentDirectory(); } - } public static IKernel NinjectKernel @@ -75,6 +85,7 @@ namespace NzbDrone.Core private static void ForceMigration(IRepository repository) { repository.GetPaged(0, 1); + repository.GetPaged(0, 1); repository.GetPaged(0, 1); } diff --git a/NzbDrone.Core/Instrumentation.cs b/NzbDrone.Core/Instrumentation.cs deleted file mode 100644 index adbe06871..000000000 --- a/NzbDrone.Core/Instrumentation.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using Exceptioneer.WindowsFormsClient; -using NLog; -using NLog.Config; -using NLog.Targets; - -namespace NzbDrone.Core -{ - public static class Instrumentation - { - public static void Setup() - { - if (Debugger.IsAttached) - { - LogManager.ThrowExceptions = true; - } - - LogManager.Configuration = new XmlLoggingConfiguration(Path.Combine(CentralDispatch.AppPath, "log.config"), false); - LogManager.ConfigurationReloaded += ((s, e) => BindExceptioneer()); - BindExceptioneer(); - } - - private static void BindExceptioneer() - { - var exTarget = new ExceptioneerTarget(); - LogManager.Configuration.AddTarget("Exceptioneer", exTarget); - LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Error, exTarget)); - LogManager.Configuration.Reload(); - } - - - - public class NlogWriter : TextWriter - { - private static readonly Logger Logger = LogManager.GetLogger("DB"); - - - public override void Write(char[] buffer, int index, int count) - { - Write(new string(buffer, index, count)); - } - - public override void Write(string value) - { - DbAction(value); - } - - private static void DbAction(string value) - { - Logger.Trace(value); - } - - public override Encoding Encoding - { - get { return Encoding.Default; } - } - } - - - public class ExceptioneerTarget : Target - { - protected override void Write(LogEventInfo logEvent) - { - if (logEvent.Exception == null) - throw new InvalidOperationException(@"Missing Exception Object.. Please Use Logger.FatalException() or Logger.ErrorException() rather - than Logger.Fatal() and Logger.Error()"); - - if (!Debugger.IsAttached) - { - new Client - { - ApiKey = "43BBF60A-EB2A-4C1C-B09E-422ADF637265", - ApplicationName = "NZBDrone", - CurrentException = logEvent.Exception - }.Submit(); - } - } - } - } -} - - - diff --git a/NzbDrone.Core/Instrumentation/ExceptioneerTarget.cs b/NzbDrone.Core/Instrumentation/ExceptioneerTarget.cs new file mode 100644 index 000000000..c0d233b10 --- /dev/null +++ b/NzbDrone.Core/Instrumentation/ExceptioneerTarget.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics; +using Exceptioneer.WindowsFormsClient; +using NLog; +using NLog.Targets; + +namespace NzbDrone.Core.Instrumentation +{ + public class ExceptioneerTarget : Target + { + protected override void Write(LogEventInfo logEvent) + { + if (logEvent.Exception == null) + throw new InvalidOperationException(@"Missing Exception Object.. Please Use Logger.FatalException() or Logger.ErrorException() rather + than Logger.Fatal() and Logger.Error()"); + + if (!Debugger.IsAttached) + { + new Client + { + ApiKey = "43BBF60A-EB2A-4C1C-B09E-422ADF637265", + ApplicationName = "NZBDrone", + CurrentException = logEvent.Exception + }.Submit(); + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/ILogProvider.cs b/NzbDrone.Core/Instrumentation/ILogProvider.cs new file mode 100644 index 000000000..26da96875 --- /dev/null +++ b/NzbDrone.Core/Instrumentation/ILogProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.Instrumentation +{ + public interface ILogProvider + { + IQueryable GetAllLogs(); + void DeleteAll(); + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/Log.cs b/NzbDrone.Core/Instrumentation/Log.cs new file mode 100644 index 000000000..95883a59c --- /dev/null +++ b/NzbDrone.Core/Instrumentation/Log.cs @@ -0,0 +1,30 @@ +using System; +using SubSonic.SqlGeneration.Schema; + +namespace NzbDrone.Core.Instrumentation +{ + public class Log + { + public int LogId { get; set; } + public string Message { get; set; } + public DateTime Time { get; set; } + public string Logger { get; set; } + + [SubSonicNullString] + public string Stack { get; set; } + [SubSonicNullString] + public string ExceptionMessage { get; set; } + [SubSonicNullString] + public string ExceptionString { get; set; } + [SubSonicNullString] + public string ExceptionType { get; set; } + + public LogLevel Level { get; set; } + + //This is needed for telerik grid binding + [SubSonicIgnore] + public string DisplayLevel{ + get { return Level.ToString(); } + } + } +} diff --git a/NzbDrone.Core/Instrumentation/LogConfiguration.cs b/NzbDrone.Core/Instrumentation/LogConfiguration.cs new file mode 100644 index 000000000..aaf807324 --- /dev/null +++ b/NzbDrone.Core/Instrumentation/LogConfiguration.cs @@ -0,0 +1,40 @@ +using System.Diagnostics; +using System.IO; +using NLog; +using NLog.Config; +using Ninject; + +namespace NzbDrone.Core.Instrumentation +{ + public static class LogConfiguration + { + public static void Setup() + { + if (Debugger.IsAttached) + { + LogManager.ThrowExceptions = true; + } + + LogManager.Configuration = new XmlLoggingConfiguration(Path.Combine(CentralDispatch.AppPath, "log.config"), false); + LogManager.ConfigurationReloaded += ((s, e) => BindCustomLoggers()); + BindCustomLoggers(); + } + + private static void BindCustomLoggers() + { + var exTarget = new ExceptioneerTarget(); + LogManager.Configuration.AddTarget("Exceptioneer", exTarget); + LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", NLog.LogLevel.Error, exTarget)); + + var sonicTarget = CentralDispatch.NinjectKernel.Get(); + LogManager.Configuration.AddTarget("DbLogger", sonicTarget); + LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", NLog.LogLevel.Info, sonicTarget)); + + LogManager.Configuration.Reload(); + } + + } +} + + + diff --git a/NzbDrone.Core/Instrumentation/LogLevel.cs b/NzbDrone.Core/Instrumentation/LogLevel.cs new file mode 100644 index 000000000..704a1e06a --- /dev/null +++ b/NzbDrone.Core/Instrumentation/LogLevel.cs @@ -0,0 +1,12 @@ +namespace NzbDrone.Core.Instrumentation +{ + public enum LogLevel + { + Trace, + Debug, + Info, + Warn, + Error, + Fatal + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/LogProvider.cs b/NzbDrone.Core/Instrumentation/LogProvider.cs new file mode 100644 index 000000000..d95f7a3ea --- /dev/null +++ b/NzbDrone.Core/Instrumentation/LogProvider.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using SubSonic.Repository; + +namespace NzbDrone.Core.Instrumentation +{ + + public class LogProvider : ILogProvider + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private readonly IRepository _repository; + + public LogProvider(IRepository repository) + { + _repository = repository; + } + + public IQueryable GetAllLogs() + { + return _repository.All(); + } + + public void DeleteAll() + { + _repository.DeleteMany(GetAllLogs()); + Logger.Info("Cleared Log History"); + } + } +} diff --git a/NzbDrone.Core/Instrumentation/NlogWriter.cs b/NzbDrone.Core/Instrumentation/NlogWriter.cs new file mode 100644 index 000000000..03240e7b1 --- /dev/null +++ b/NzbDrone.Core/Instrumentation/NlogWriter.cs @@ -0,0 +1,32 @@ +using System.IO; +using System.Text; +using NLog; + +namespace NzbDrone.Core.Instrumentation +{ + public class NlogWriter : TextWriter + { + private static readonly Logger Logger = LogManager.GetLogger("DB"); + + + public override void Write(char[] buffer, int index, int count) + { + Write(new string(buffer, index, count)); + } + + public override void Write(string value) + { + DbAction(value); + } + + private static void DbAction(string value) + { + Logger.Trace(value); + } + + public override Encoding Encoding + { + get { return Encoding.Default; } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/SubsonicTarget.cs b/NzbDrone.Core/Instrumentation/SubsonicTarget.cs new file mode 100644 index 000000000..5992666bf --- /dev/null +++ b/NzbDrone.Core/Instrumentation/SubsonicTarget.cs @@ -0,0 +1,78 @@ +using System; +using System.Diagnostics; +using Exceptioneer.WindowsFormsClient; +using NLog; +using NLog.Targets; +using SubSonic.Repository; +using Ninject; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Instrumentation +{ + public class SubsonicTarget : Target + { + private readonly IRepository _repo; + + public SubsonicTarget(IRepository repo) + { + _repo = repo; + } + + protected override void Write(LogEventInfo logEvent) + { + var log = new Log(); + log.Time = logEvent.TimeStamp; + log.Message = logEvent.FormattedMessage; + + if (log.Stack != null) + { + log.Stack = logEvent.StackTrace.ToString(); + } + + log.Logger = logEvent.LoggerName; + + if (logEvent.Exception != null) + { + log.ExceptionMessage = logEvent.Exception.Message; + log.ExceptionString = logEvent.Exception.ToString(); + log.ExceptionType = logEvent.Exception.GetType().ToString(); + } + + switch (logEvent.Level.Name.ToLower()) + { + case "trace": + { + log.Level = LogLevel.Trace; + break; + } + case "debug": + { + log.Level = LogLevel.Debug; + break; + } + case "info": + { + log.Level = LogLevel.Info; + break; + } + case "warn": + { + log.Level = LogLevel.Warn; + break; + } + case "error": + { + log.Level = LogLevel.Error; + break; + } + case "fatal": + { + log.Level = LogLevel.Fatal; + break; + } + } + + _repo.Add(log); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Libraries/Castle.Core.dll b/NzbDrone.Core/Libraries/Castle.Core.dll index 31dc49548..34c155ab4 100644 Binary files a/NzbDrone.Core/Libraries/Castle.Core.dll and b/NzbDrone.Core/Libraries/Castle.Core.dll differ diff --git a/NzbDrone.Core/Libraries/SubSonic.Core.dll b/NzbDrone.Core/Libraries/SubSonic.Core.dll index cc2b515f6..cb44927f6 100644 Binary files a/NzbDrone.Core/Libraries/SubSonic.Core.dll and b/NzbDrone.Core/Libraries/SubSonic.Core.dll differ diff --git a/NzbDrone.Core/Libraries/SubSonic.Core.pdb b/NzbDrone.Core/Libraries/SubSonic.Core.pdb new file mode 100644 index 000000000..14f65566b Binary files /dev/null and b/NzbDrone.Core/Libraries/SubSonic.Core.pdb differ diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index bdfb08d1e..695502a22 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -150,13 +150,19 @@ + + + + + + - + @@ -182,6 +188,7 @@ + diff --git a/NzbDrone.Core/Parser.cs b/NzbDrone.Core/Parser.cs index 65e4022d1..faa32613f 100644 --- a/NzbDrone.Core/Parser.cs +++ b/NzbDrone.Core/Parser.cs @@ -131,5 +131,12 @@ namespace NzbDrone.Core { return NormalizeRegex.Replace(title, String.Empty).ToLower(); } + + public static string NormalizePath(string path) + { + if (String.IsNullOrEmpty(path)) + throw new ArgumentException("Path can not be null or empty"); + return new FileInfo(path).FullName.ToLower().Trim('/', '\\', ' '); + } } } diff --git a/NzbDrone.Core/Providers/DiskProvider.cs b/NzbDrone.Core/Providers/DiskProvider.cs index 29a2b0d57..2ffdb4111 100644 --- a/NzbDrone.Core/Providers/DiskProvider.cs +++ b/NzbDrone.Core/Providers/DiskProvider.cs @@ -7,11 +7,16 @@ namespace NzbDrone.Core.Providers { #region IDiskProvider Members - public bool Exists(string path) + public bool FolderExists(string path) { return Directory.Exists(path); } + public bool FileExists(string path) + { + return File.Exists(path); + } + public string[] GetDirectories(string path) { return Directory.GetDirectories(path); @@ -22,18 +27,16 @@ namespace NzbDrone.Core.Providers return Directory.GetFiles(path, pattern, searchOption); } + public long GetSize(string path) + { + return new FileInfo(path).Length; + } + public String CreateDirectory(string path) { return Directory.CreateDirectory(path).FullName; } #endregion - - public static string CleanPath(string path) - { - if (string.IsNullOrEmpty(path)) - throw new ArgumentException("Path can not be null or empty"); - return path.ToLower().Trim('/', '\\', ' '); - } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index 67031e04b..42aa0f300 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -30,7 +30,12 @@ namespace NzbDrone.Core.Providers public Episode GetEpisode(long id) { - return _sonicRepo.Single(e => e.EpisodeId == id); + return _sonicRepo.Single(id); + } + + public Episode GetEpisode(int seriesId, int seasonNumber, int episodeNumber) + { + return _sonicRepo.Single(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber && c.EpisodeNumber == episodeNumber); } public IList GetEpisodeBySeries(long seriesId) diff --git a/NzbDrone.Core/Providers/IDiskProvider.cs b/NzbDrone.Core/Providers/IDiskProvider.cs index 6a2643c84..bf6bf1d9a 100644 --- a/NzbDrone.Core/Providers/IDiskProvider.cs +++ b/NzbDrone.Core/Providers/IDiskProvider.cs @@ -5,9 +5,11 @@ namespace NzbDrone.Core.Providers { public interface IDiskProvider { - bool Exists(string path); + bool FolderExists(string path); string[] GetDirectories(string path); String CreateDirectory(string path); string[] GetFiles(string path, string pattern, SearchOption searchOption); + bool FileExists(string path); + long GetSize(string path); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/IEpisodeProvider.cs b/NzbDrone.Core/Providers/IEpisodeProvider.cs index df444ee75..758c3cdf8 100644 --- a/NzbDrone.Core/Providers/IEpisodeProvider.cs +++ b/NzbDrone.Core/Providers/IEpisodeProvider.cs @@ -8,6 +8,7 @@ namespace NzbDrone.Core.Providers public interface IEpisodeProvider { Episode GetEpisode(long id); + Episode GetEpisode(int seriesId, int seasonNumber, int episodeNumber); IList GetEpisodeBySeries(long seriesId); String GetSabTitle(Episode episode); diff --git a/NzbDrone.Core/Providers/IMediaFileProvider.cs b/NzbDrone.Core/Providers/IMediaFileProvider.cs index 41f1e4b59..651e59fcc 100644 --- a/NzbDrone.Core/Providers/IMediaFileProvider.cs +++ b/NzbDrone.Core/Providers/IMediaFileProvider.cs @@ -9,5 +9,7 @@ namespace NzbDrone.Core.Providers /// /// The series to be scanned void Scan(Series series); + + EpisodeFile ImportFile(Series series, string filePath); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index ffdd2b0ce..dc6fad676 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -5,22 +5,24 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using NLog; +using NzbDrone.Core.Model; using NzbDrone.Core.Repository; +using SubSonic.Repository; namespace NzbDrone.Core.Providers { public class MediaFileProvider : IMediaFileProvider { - - + private readonly IRepository _repository; private readonly IDiskProvider _diskProvider; private readonly IEpisodeProvider _episodeProvider; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly string[] MediaExtentions = new[] { "*.mkv", "*.avi", "*.wmv" }; - public MediaFileProvider(IDiskProvider diskProvider, IEpisodeProvider episodeProvider) + public MediaFileProvider(IRepository repository, IDiskProvider diskProvider, IEpisodeProvider episodeProvider) { + _repository = repository; _diskProvider = diskProvider; _episodeProvider = episodeProvider; } @@ -31,19 +33,82 @@ namespace NzbDrone.Core.Providers /// The series to be scanned public void Scan(Series series) { + var mediaFileList = GetMediaFileList(series.Path); + + foreach (var filePath in mediaFileList) + { + ImportFile(series, filePath); + } + } + + public EpisodeFile ImportFile(Series series, string filePath) + { + Logger.Trace("Importing file to database [{0}]", filePath); + + if (!_repository.Exists(e => e.Path == Parser.NormalizePath(filePath))) + { + var episodesInFile = Parser.ParseEpisodeInfo(filePath); + + foreach (var parsedEpisode in episodesInFile) + { + EpisodeParseResult closureEpisode = parsedEpisode; + var episode = _episodeProvider.GetEpisode(series.SeriesId, closureEpisode.SeasonNumber, closureEpisode.EpisodeNumber); + if (episode != null) + { + var epFile = new EpisodeFile(); + epFile.DateAdded = DateTime.Now; + epFile.SeriesId = series.SeriesId; + epFile.EpisodeId = episode.EpisodeId; + epFile.Path = Parser.NormalizePath(filePath); + epFile.Size = _diskProvider.GetSize(filePath); + epFile.Quality = Parser.ParseQuality(filePath); + epFile.Proper = Parser.ParseProper(filePath); + _repository.Add(epFile); + Logger.Info("File '{0}' successfully attached to {1}", episode.EpisodeId); + + return epFile; + } + + Logger.Warn("Unable to find Series:{0} Season:{1} Episode:{2} in the database.", series.Title, closureEpisode.SeasonNumber, closureEpisode.EpisodeNumber); + } + } + else + { + Logger.Trace("[{0}] already exists in the database. skipping.", filePath); + } + + return null; + } + + /// + /// Removes files that no longer exist from the database + /// + /// list of files to verify + public void CleanUp(List files) + { + foreach (var episodeFile in files) + { + if (!_diskProvider.FileExists(episodeFile.Path)) + { + Logger.Trace("File {0} no longer exists on disk. removing from database.", episodeFile.Path); + _repository.Delete(episodeFile); + } + } + } + + private List GetMediaFileList(string path) + { + Logger.Info("Scanning '{0}' for episodes", path); + var mediaFileList = new List(); - Logger.Info("Scanning '{0}'", series.Path); + foreach (var ext in MediaExtentions) { - mediaFileList.AddRange(_diskProvider.GetFiles(series.Path, ext, SearchOption.AllDirectories)); + mediaFileList.AddRange(_diskProvider.GetFiles(path, ext, SearchOption.AllDirectories)); } - Logger.Info("{0} media files were found", mediaFileList.Count); - - foreach (var file in mediaFileList) - { - var episode = Parser.ParseEpisodeInfo(file); - } + Logger.Info("{0} media files were found in {1}", mediaFileList.Count, path); + return mediaFileList; } } } diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 0e1906f3b..4a393cd5d 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -61,7 +61,7 @@ namespace NzbDrone.Core.Providers var results = new List(); foreach (string seriesFolder in _diskProvider.GetDirectories(_config.SeriesRoot)) { - var cleanPath = DiskProvider.CleanPath(new DirectoryInfo(seriesFolder).FullName); + var cleanPath = Parser.NormalizePath(new DirectoryInfo(seriesFolder).FullName); if (!_sonioRepo.Exists(s => s.Path == cleanPath)) { results.Add(cleanPath); diff --git a/NzbDrone.Core/Repository/Episode.cs b/NzbDrone.Core/Repository/Episode.cs index 91445091f..bfebd5fea 100644 --- a/NzbDrone.Core/Repository/Episode.cs +++ b/NzbDrone.Core/Repository/Episode.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using SubSonic.SqlGeneration.Schema; namespace NzbDrone.Core.Repository @@ -16,14 +17,13 @@ namespace NzbDrone.Core.Repository public string Overview { get; set; } public string Language { get; set; } - [SubSonicNullString] - public string Path { get; set; } - public long? Size { get; set; } - [SubSonicToOneRelation(ThisClassContainsJoinKey = true)] public virtual Season Season { get; set; } [SubSonicToOneRelation(ThisClassContainsJoinKey = true)] public virtual Series Series { get; private set; } + + [SubSonicToManyRelation] + public virtual List Files { get; private set; } } } diff --git a/NzbDrone.Core/Repository/EpisodeFile.cs b/NzbDrone.Core/Repository/EpisodeFile.cs index 4a8891ee4..4ae2ec0ef 100644 --- a/NzbDrone.Core/Repository/EpisodeFile.cs +++ b/NzbDrone.Core/Repository/EpisodeFile.cs @@ -4,18 +4,19 @@ using SubSonic.SqlGeneration.Schema; namespace NzbDrone.Core.Repository { - class EpisodeFile + public class EpisodeFile { [SubSonicPrimaryKey] public virtual int FileId { get; set; } public int EpisodeId { get; set; } + public int SeriesId { get; set; } public string Path { get; set; } public QualityTypes Quality { get; set; } public bool Proper { get; set; } public long Size { get; set; } public DateTime DateAdded { get; set; } - [SubSonicToOneRelation] + [SubSonicToOneRelation(ThisClassContainsJoinKey = true)] public virtual Episode Episode { get; set; } } } diff --git a/NzbDrone.Core/Repository/Series.cs b/NzbDrone.Core/Repository/Series.cs index 40f70f2ac..e9c03b929 100644 --- a/NzbDrone.Core/Repository/Series.cs +++ b/NzbDrone.Core/Repository/Series.cs @@ -35,6 +35,7 @@ namespace NzbDrone.Core.Repository [SubSonicToManyRelation] public virtual List Episodes { get; private set; } - + [SubSonicToManyRelation] + public virtual List Files { get; private set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/LogController.cs b/NzbDrone.Web/Controllers/LogController.cs new file mode 100644 index 000000000..69ad51452 --- /dev/null +++ b/NzbDrone.Web/Controllers/LogController.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using NzbDrone.Core.Instrumentation; +using SubSonic.Repository; +using Telerik.Web.Mvc; + +namespace NzbDrone.Web.Controllers +{ + public class LogController : Controller + { + private readonly ILogProvider _logProvider; + + public LogController(ILogProvider logProvider) + { + _logProvider = logProvider; + } + + public ActionResult Index() + { + return View(); + } + + + public ActionResult Clear() + { + _logProvider.DeleteAll(); + return RedirectToAction("Index"); + + } + + [GridAction] + public ActionResult _AjaxBinding() + { + return View(new GridModel(_logProvider.GetAllLogs())); + } + + + + } +} diff --git a/NzbDrone.Web/Controllers/SettingsController.cs b/NzbDrone.Web/Controllers/SettingsController.cs index dde13a1ea..2add481f3 100644 --- a/NzbDrone.Web/Controllers/SettingsController.cs +++ b/NzbDrone.Web/Controllers/SettingsController.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Web.Controllers _configProvider.SeriesRoot = model.TvFolder; //return RedirectToAction("index"); } - return View(model); + return RedirectToAction("index", "series"); } } diff --git a/NzbDrone.Web/Global.asax.cs b/NzbDrone.Web/Global.asax.cs index 0e3fbbee3..e6d45f04d 100644 --- a/NzbDrone.Web/Global.asax.cs +++ b/NzbDrone.Web/Global.asax.cs @@ -8,6 +8,8 @@ using Ninject; using Ninject.Web.Mvc; using NLog; using NzbDrone.Core; +using NzbDrone.Core.Instrumentation; +using SubSonic.Repository; namespace NzbDrone.Web { @@ -30,7 +32,8 @@ namespace NzbDrone.Web protected override void OnApplicationStarted() { - Instrumentation.Setup(); + LogConfiguration.Setup(); + Logger.Info("NZBDrone Starting up."); CentralDispatch.DedicateToHost(); AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); @@ -39,7 +42,10 @@ namespace NzbDrone.Web protected override IKernel CreateKernel() { - return CentralDispatch.NinjectKernel; + var kernel = CentralDispatch.NinjectKernel; + // kernel.Bind().ToConstant(kernel.Get("LogDb")); + + return kernel; } // ReSharper disable InconsistentNaming diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index 08497b220..b7fed5ad0 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -38,6 +38,9 @@ + + D:\My Dropbox\Git\NzbDrone\NzbDrone.Core\Libraries\SubSonic.Core.dll + @@ -74,6 +77,7 @@ + @@ -209,7 +213,10 @@ + + + @@ -244,6 +251,7 @@ + @@ -269,7 +277,15 @@ - True + False + True + 21704 + / + http://localhost/NzbDrone + False + True + http://localhost:8989 + False diff --git a/NzbDrone.Web/Scripts/Notification.js b/NzbDrone.Web/Scripts/Notification.js index 502978d7e..a7fce3024 100644 --- a/NzbDrone.Web/Scripts/Notification.js +++ b/NzbDrone.Web/Scripts/Notification.js @@ -5,7 +5,7 @@ $(function () { refreshNotifications(); var timer = window.setInterval(function () { - speed = 1800; + speed = 1000; refreshNotifications(); }, 2000); diff --git a/NzbDrone.Web/Views/Home/Index.aspx b/NzbDrone.Web/Views/Home/Index.aspx index 6a83d24ba..495767f1e 100644 --- a/NzbDrone.Web/Views/Home/Index.aspx +++ b/NzbDrone.Web/Views/Home/Index.aspx @@ -1,12 +1,19 @@ <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> +<%@ Import Namespace="Telerik.Web.Mvc.UI" %> - Home Page + Logs + + + <% + Html.Telerik().Menu().Name("logMenu").Items(items => items.Add().Text("Clear Logs").Action("Clear", "Log")).Render(); + %> - -

<%: ViewData["Message"] %>

+

+ <%: ViewData["Message"] %>

- To learn more about ASP.NET MVC visit http://asp.net/mvc. + To learn more about ASP.NET MVC visit + http://asp.net/mvc.

diff --git a/NzbDrone.Web/Views/Log/Index.aspx b/NzbDrone.Web/Views/Log/Index.aspx new file mode 100644 index 000000000..c99aeb467 --- /dev/null +++ b/NzbDrone.Web/Views/Log/Index.aspx @@ -0,0 +1,31 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage>" %> + +<%@ Import Namespace="Telerik.Web.Mvc.UI" %> + + Logs + + + <% + Html.Telerik().Menu().Name("logMenu").Items(items => items.Add().Text("Clear Logs").Action("Clear", "Log")).Render(); + %> + + + <%Html.Telerik().Grid(Model).Name("logs") + .Columns(columns => + { + columns.Bound(c => c.Time).Title("Time").Width(190); + //columns.Bound(c => c.Time).Title("Time").Template(c => c.Time.ToShortTimeString()).Groupable(false); + columns.Bound(c => c.DisplayLevel).Title("Level").Width(0); + columns.Bound(c => c.Message); + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("_AjaxBinding", "Log")) + //.DetailView(detailView => detailView.Template(e => Html.RenderPartial("LogDetail", e))) + .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.Time).Descending()).Enabled(true)) + .Pageable(c => c.PageSize(50).Position(GridPagerPosition.Both).Style(GridPagerStyles.PageInput | GridPagerStyles.NextPreviousAndNumeric)) + //.Groupable() + .Filterable() + //.Groupable(grouping => grouping.Groups(groups => groups.Add(c => c.Time.Date)).Enabled(true)) + .Render(); + %> + + diff --git a/NzbDrone.Web/Views/Log/LogDetail.ascx b/NzbDrone.Web/Views/Log/LogDetail.ascx new file mode 100644 index 000000000..0bc673c40 --- /dev/null +++ b/NzbDrone.Web/Views/Log/LogDetail.ascx @@ -0,0 +1,15 @@ +<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> +
    +
  • + <%: Model.Logger %> +
  • +
  • + <%: Model.ExceptionType%> +
  • +
  • + <%: Model.ExceptionMessage%> +
  • +
  • + <%: Model.ExceptionString%> +
  • +
diff --git a/NzbDrone.Web/Views/Series/Details.aspx b/NzbDrone.Web/Views/Series/Details.aspx index 0052d81ce..ed60f15dd 100644 --- a/NzbDrone.Web/Views/Series/Details.aspx +++ b/NzbDrone.Web/Views/Series/Details.aspx @@ -1,6 +1,7 @@ <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> <%@ Import Namespace="Telerik.Web.Mvc.UI" %> +<%@ Import Namespace="NzbDrone.Core.Repository" %> <%: Model.Title %> @@ -31,22 +32,56 @@
<%: Model.Path %>
- <%= Html.Telerik().Grid(Model.Episodes) - .Name("Episodes") - .Columns(columns => + <% + //Normal Seasons + foreach (var season in Model.Seasons.Where(s => s.SeasonNumber > 0)) { - columns.Bound(c => c.EpisodeNumber).Width(10); - columns.Bound(c => c.Title); - columns.Bound(c => c.AirDate).Format("{0:d}").Width(150); - }) - .Groupable(grouping => grouping.Groups(groups => groups.Add(c => c.SeasonNumber))) - .Sortable(rows=>rows - .OrderBy(epSort => epSort.Add(c => c.EpisodeNumber))) + %> +
+

+ Season + <%: season.SeasonNumber %>

+ <% + Html.Telerik().Grid(season.Episodes).Name("seasons_" + season.SeasonNumber) + .Columns(columns => + { + columns.Bound(c => c.SeasonNumber).Width(0).Title("Seasons"); + columns.Bound(c => c.EpisodeNumber).Width(0).Title("Episode"); + columns.Bound(c => c.Title); + columns.Bound(c => c.AirDate).Format("{0:d}").Width(0); + }) + .DetailView(detailView => detailView.Template(e => Html.RenderPartial("EpisodeDetail", e))) + .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber)).Enabled(false)) + .Footer(false) + .Render(); + } + + //Specials + var specialSeasons = Model.Seasons.Where(s => s.SeasonNumber == 0).FirstOrDefault(); + + if (specialSeasons != null) + { + %> +
+

+ Specials

+ <% +Html.Telerik().Grid(specialSeasons.Episodes).Name("seasons_specials") + .Columns(columns => + { + columns.Bound(c => c.EpisodeNumber).Width(0).Title("Episode"); + columns.Bound(c => c.Title); + columns.Bound(c => c.AirDate).Format("{0:d}").Width(0); + }) + .DetailView(detailView => detailView.Template(e => Html.RenderPartial("EpisodeDetail", e))) + .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber)).Enabled(false)) + .Footer(false) + .Render(); + } %>

<%-- <%: Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |--%> - <%: Html.ActionLink("Back to List", "Index") %> - <%: Html.ActionLink("Load Episodes", "LoadEpisodes", new{seriesId= Model.SeriesId}) %> + <%: Html.ActionLink("Back to Series", "Index") %>

diff --git a/NzbDrone.Web/Views/Series/EpisodeDetail.ascx b/NzbDrone.Web/Views/Series/EpisodeDetail.ascx new file mode 100644 index 000000000..6f31877e2 --- /dev/null +++ b/NzbDrone.Web/Views/Series/EpisodeDetail.ascx @@ -0,0 +1,14 @@ +<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> +<%@ Import Namespace="Telerik.Web.Mvc.UI" %> +<%: Model.Overview %> +<%: + Html.Telerik().Grid(Model.Files) + .Name("files_" + Model.EpisodeId) + .Columns(columns => + { + columns.Bound(c => c.Path); + columns.Bound(c => c.Quality); + columns.Bound(c => c.DateAdded); + }) + .Footer(false) +%> \ No newline at end of file diff --git a/NzbDrone.Web/Views/Series/index.aspx b/NzbDrone.Web/Views/Series/index.aspx index f8ba79d03..e3c051e23 100644 --- a/NzbDrone.Web/Views/Series/index.aspx +++ b/NzbDrone.Web/Views/Series/index.aspx @@ -5,9 +5,7 @@ Series - -
-
+ <% Html.Telerik().Menu().Name("telerikGrid").Items(items => { items.Add().Text("View Unmapped Folders").Action("Unmapped", "Series"); }) .Items(items => items.Add().Text("Sync With Disk").Action("Sync", "Series")) diff --git a/NzbDrone.Web/Views/Shared/Site.Master b/NzbDrone.Web/Views/Shared/Site.Master index efcc0473e..28969c07a 100644 --- a/NzbDrone.Web/Views/Shared/Site.Master +++ b/NzbDrone.Web/Views/Shared/Site.Master @@ -32,6 +32,10 @@ Released : 20100727 %> + + Fork me on GitHub +