diff --git a/NzbDrone.Core.Test/Fixtures.cs b/NzbDrone.Core.Test/Fixtures.cs index 609518e78..ae4af6754 100644 --- a/NzbDrone.Core.Test/Fixtures.cs +++ b/NzbDrone.Core.Test/Fixtures.cs @@ -43,6 +43,7 @@ namespace NzbDrone.Core.Test try { LogManager.Configuration = new XmlLoggingConfiguration(Path.Combine(CentralDispatch.AppPath, "log.config"), false); + LogManager.ThrowExceptions = true; } catch (Exception e) { diff --git a/NzbDrone.Core.Test/MediaFileProviderTests (X201's conflicted copy 2010-10-19).cs b/NzbDrone.Core.Test/MediaFileProviderTests (X201's conflicted copy 2010-10-19).cs deleted file mode 100644 index a8825b3f6..000000000 --- a/NzbDrone.Core.Test/MediaFileProviderTests (X201's conflicted copy 2010-10-19).cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using Gallio.Framework; -using MbUnit.Framework; -using MbUnit.Framework.ContractVerifiers; -using Moq; -using Ninject; -using Ninject.Moq; -using NzbDrone.Core.Entities; -using NzbDrone.Core.Entities.Episode; -using NzbDrone.Core.Entities.Quality; -using NzbDrone.Core.Providers; -using SubSonic.Repository; - -namespace NzbDrone.Core.Test -{ - [TestFixture] - // ReSharper disable InconsistentNaming - public class MediaFileProviderTests - { - [Test] - public void scan_test() - { - //Arrange - var repository = new Mock(); - repository.Setup(c => c.Update(It.IsAny())).Verifiable(); - - var diskProvider = MockLib.GetStandardDisk(1, 2); - - var kernel = new MockingKernel(); - kernel.Bind().ToConstant(diskProvider); - kernel.Bind().ToConstant(repository.Object); - kernel.Bind().To(); - - var fakeSeries = new Series() - { - Path = MockLib.StandardSeries[0] - }; - - //Act - kernel.Get().Scan(fakeSeries); - - //Assert - repository.Verify(c => c.Update(It.IsAny()), Times.Exactly(1 * 2)); - - - } - - [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.ParseBasicEpisode(path); - Assert.AreEqual(season, result.SeasonNumber); - Assert.AreEqual(episode, result.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] - [Timeout(2)] - public void quality_parse() - { - var sw = Stopwatch.StartNew(); - var name = "WEEDSawdawdadawdawd\\awdawdawdadadad.mkv"; - var quality = QualityTypes.HDTV; - - for (int i = 0; i < 100000; i++) - { - Assert.AreEqual(quality, Parser.ParseQuality(name)); - } - - Console.WriteLine(sw.Elapsed.ToString()); - - - } - - } - - -} diff --git a/NzbDrone.Core.Test/MediaFileProviderTests.cs b/NzbDrone.Core.Test/MediaFileProviderTests.cs index 2c2fc1be3..a74ef8ea8 100644 --- a/NzbDrone.Core.Test/MediaFileProviderTests.cs +++ b/NzbDrone.Core.Test/MediaFileProviderTests.cs @@ -9,6 +9,7 @@ using MbUnit.Framework.ContractVerifiers; using Moq; using Ninject; using Ninject.Moq; +using NzbDrone.Core.Model; using NzbDrone.Core.Providers; using NzbDrone.Core.Repository; using NzbDrone.Core.Repository.Quality; @@ -29,7 +30,7 @@ namespace NzbDrone.Core.Test ///////////////////////////////////////// //Constants - const string fileName = "WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi"; + const string fileName = @"WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi"; const int seasonNumber = 3; const int episodeNumner = 01; const int size = 12345; @@ -157,7 +158,34 @@ namespace NzbDrone.Core.Test } + [Test] + [Row("Season {season}\\S{season:00}E{episode:00} - {title} - {quality}", "Season 6\\S06E08 - Lethal Inspection - hdtv")] + [Row("Season {season}\\{series} - {season:##}{episode:00} - {title} - {quality}", "Season 6\\Futurama - 608 - Lethal Inspection - hdtv")] + [Row("Season {season}\\{series} - {season:##}{episode:00} - {title}", "Season 6\\Futurama - 608 - Lethal Inspection")] + public void test_file_path_generation(string patern, string path) + { + var fakeConfig = new Mock(); + fakeConfig.Setup(c => c.EpisodeNameFormat).Returns(patern); + var kernel = new MockingKernel(); + kernel.Bind().ToConstant(fakeConfig.Object); + kernel.Bind().To(); + + var fakeEpisode = new EpisodeModel + { + SeasonNumber = 6, + EpisodeNumber = 8, + EpisodeTitle = "Lethal Inspection", + Quality = QualityTypes.HDTV, + SeriesTitle = "Futurama" + }; + + //Act + var result = kernel.Get().GenerateEpisodePath(fakeEpisode); + + //Assert + Assert.AreEqual(path.ToLowerInvariant(), result.ToLowerInvariant()); + } } diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 64ed87f4e..4eb6565e2 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -49,7 +49,9 @@ False ..\NzbDrone.Core\Libraries\Ninject.dll - + + ..\NzbDrone.Core\Libraries\NLog.dll + False ..\NzbDrone.Core\Libraries\SubSonic.Core.dll diff --git a/NzbDrone.Core.Test/ParserTest.cs b/NzbDrone.Core.Test/ParserTest.cs index 53e686d0d..dad7b1a87 100644 --- a/NzbDrone.Core.Test/ParserTest.cs +++ b/NzbDrone.Core.Test/ParserTest.cs @@ -18,6 +18,9 @@ namespace NzbDrone.Core.Test [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)] + [Row(@"z:\tv shows\battlestar galactica (2003)\Season 3\S03E05 - Collaborators.mkv", 3, 5)] + [Row(@"z:\tv shows\modern marvels\Season 16\S16E03 - The Potato.mkv", 16, 3)] + [Row(@"z:\tv shows\robot chicken\Specials\S00E16 - Dear Consumer - SD TV.avi", 0, 16)] public void episode_parse(string path, int season, int episode) { var result = Parser.ParseEpisodeInfo(path); @@ -40,6 +43,10 @@ namespace NzbDrone.Core.Test [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)] + [Row("S01E04 - So Old - Playdate - 720p TV.mkv", QualityTypes.HDTV)] + [Row("S22E03 - MoneyBART - HD TV.mkv", QualityTypes.HDTV)] + [Row("S01E03 - Come Fly With Me - 720p BluRay.mkv", QualityTypes.Bluray)] + [Row("S11E06 - D-Yikes! - 720p WEB-DL.mkv", QualityTypes.WEBDL)] public void quality_parse(string path, object quality) { var result = Parser.ParseQuality(path); diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 6e02ea2e8..2a6bd44a7 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -10,6 +10,7 @@ using NzbDrone.Core.Providers; using NzbDrone.Core.Providers.Fakes; using NzbDrone.Core.Repository; using SubSonic.DataProviders; +using SubSonic.Query; using SubSonic.Repository; using NLog; using System.Linq; @@ -31,14 +32,15 @@ namespace NzbDrone.Core string connectionString = String.Format("Data Source={0};Version=3;", Path.Combine(AppPath, "nzbdrone.db")); 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.ExecuteQuery(new QueryCommand("VACUUM", dbProvider)); dbProvider.Log = new NlogWriter(); dbProvider.LogParams = true; - + _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To(); _kernel.Bind().To(); @@ -48,6 +50,7 @@ namespace NzbDrone.Core _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); + _kernel.Bind().To().InSingletonScope(); _kernel.Bind().ToMethod(c => new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope(); _kernel.Bind().ToConstant(logRepository).WhenInjectedInto().InSingletonScope(); diff --git a/NzbDrone.Core/Instrumentation/LogConfiguration.cs b/NzbDrone.Core/Instrumentation/LogConfiguration.cs index aaf807324..19e0f84ff 100644 --- a/NzbDrone.Core/Instrumentation/LogConfiguration.cs +++ b/NzbDrone.Core/Instrumentation/LogConfiguration.cs @@ -22,9 +22,12 @@ namespace NzbDrone.Core.Instrumentation private static void BindCustomLoggers() { +#if Release var exTarget = new ExceptioneerTarget(); LogManager.Configuration.AddTarget("Exceptioneer", exTarget); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", NLog.LogLevel.Error, exTarget)); +#endif + var sonicTarget = CentralDispatch.NinjectKernel.Get(); LogManager.Configuration.AddTarget("DbLogger", sonicTarget); diff --git a/NzbDrone.Core/Model/EpisodeModel.cs b/NzbDrone.Core/Model/EpisodeModel.cs index 7656d89cb..caa4ed596 100644 --- a/NzbDrone.Core/Model/EpisodeModel.cs +++ b/NzbDrone.Core/Model/EpisodeModel.cs @@ -6,6 +6,7 @@ namespace NzbDrone.Core.Model public class EpisodeModel { public string SeriesTitle { get; set; } + public string EpisodeTitle { get; set; } public int SeasonNumber { get; set; } public int EpisodeNumber { get; set; } public QualityTypes Quality { get; set; } diff --git a/NzbDrone.Core/Model/EpisodeParseResult.cs b/NzbDrone.Core/Model/EpisodeParseResult.cs index d280f539b..ab9638421 100644 --- a/NzbDrone.Core/Model/EpisodeParseResult.cs +++ b/NzbDrone.Core/Model/EpisodeParseResult.cs @@ -3,10 +3,16 @@ using SubSonic.SqlGeneration.Schema; namespace NzbDrone.Core.Model { - internal struct EpisodeParseResult + internal class EpisodeParseResult { internal string SeriesTitle { get; set; } internal int SeasonNumber { get; set; } internal int EpisodeNumber { get; set; } + + public override string ToString() + { + return string.Format("Series:{0} Season:{1} Episode:{2}", SeriesTitle, SeasonNumber, EpisodeNumber); + } + } } \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 695502a22..873ae09db 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -144,6 +144,7 @@ + diff --git a/NzbDrone.Core/Parser.cs b/NzbDrone.Core/Parser.cs index faa32613f..df2d18618 100644 --- a/NzbDrone.Core/Parser.cs +++ b/NzbDrone.Core/Parser.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Core private static readonly Regex[] ReportTitleRegex = new[] { - new Regex(@"(?.+?)?\W(S)?(?<season>\d+)\w(?<episode>\d+)\W", RegexOptions.IgnoreCase | RegexOptions.Compiled) + new Regex(@"(?<title>.+?)?\W(S)?(?<season>\d+)\w(?<episode>\d+)\W(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled) }; private static readonly Regex NormalizeRegex = new Regex(@"((\s|^)the(\s|$))|((\s|^)and(\s|$))|[^a-z]", RegexOptions.IgnoreCase | RegexOptions.Compiled); @@ -43,22 +43,20 @@ namespace NzbDrone.Core foreach (Match matchGroup in match) { - var tuple = new EpisodeParseResult + var parsedEpisode = new EpisodeParseResult { SeriesTitle = seriesName, SeasonNumber = Convert.ToInt32(matchGroup.Groups["season"].Value), EpisodeNumber = Convert.ToInt32(matchGroup.Groups["episode"].Value) }; - result.Add(tuple); + result.Add(parsedEpisode); - Logger.Trace("Episode Parsed. {0}", tuple); + Logger.Trace("Episode Parsed. {0}", parsedEpisode); } } } - Logger.Trace("{0} episodes parsed from string.", result.Count); - return result; } diff --git a/NzbDrone.Core/Providers/ConfigProvider.cs b/NzbDrone.Core/Providers/ConfigProvider.cs index 748cc06f2..1bbbd7b65 100644 --- a/NzbDrone.Core/Providers/ConfigProvider.cs +++ b/NzbDrone.Core/Providers/ConfigProvider.cs @@ -8,6 +8,8 @@ namespace NzbDrone.Core.Providers public class ConfigProvider : IConfigProvider { private const string SERIES_ROOTS = "SeriesRoots"; + private const string EPISODE_NAME_FORMAT = "EpisodeNameFormat"; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly IRepository _sonicRepo; @@ -21,6 +23,13 @@ namespace NzbDrone.Core.Providers return GetValue(key, String.Empty, false); } + public String EpisodeNameFormat + { + get { return GetValue(EPISODE_NAME_FORMAT); } + + set { SetValue(EPISODE_NAME_FORMAT, value); } + } + public String SeriesRoot { get { return GetValue(SERIES_ROOTS); } diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index 42aa0f300..7bbd808d3 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -43,6 +43,11 @@ namespace NzbDrone.Core.Providers return _sonicRepo.Find<Episode>(e => e.SeriesId == seriesId); } + public IList<Episode> GetEpisodeBySeason(long seasonId) + { + return _sonicRepo.Find<Episode>(e => e.SeasonId == seasonId); + } + public String GetSabTitle(Episode episode) { var series = _series.GetSeries(episode.SeriesId); @@ -72,7 +77,7 @@ namespace NzbDrone.Core.Providers var updateList = new List<Episode>(); var newList = new List<Episode>(); - Logger.Debug("Updating season info for series:{0}", seriesId); + Logger.Debug("Updating season info for series:{0}", targetSeries.SeriesName); targetSeries.Episodes.Select(e => new { e.SeasonId, e.SeasonNumber }) .Distinct().ToList() .ForEach(s => _seasons.EnsureSeason(seriesId, s.SeasonId, s.SeasonNumber)); @@ -81,7 +86,7 @@ namespace NzbDrone.Core.Providers { try { - Logger.Debug("Updating info for series:{0} - episode:{1}", seriesId, episode.Id); + Logger.Trace("Updating info for series:{0} - episode:{1}", targetSeries.SeriesName, episode.EpisodeNumber); var newEpisode = new Episode() { AirDate = episode.FirstAired, @@ -116,7 +121,7 @@ namespace NzbDrone.Core.Providers _sonicRepo.AddMany(newList); _sonicRepo.UpdateMany(updateList); - Logger.Info("Finished episode refresh for series:{0}. Success:{1} - Fail:{2} ", seriesId, successCount, failCount); + Logger.Debug("Finished episode refresh for series:{0}. Successful:{1} - Failed:{2} ", targetSeries.SeriesName, successCount, failCount); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/IConfigProvider.cs b/NzbDrone.Core/Providers/IConfigProvider.cs index 649249fbb..e34998a63 100644 --- a/NzbDrone.Core/Providers/IConfigProvider.cs +++ b/NzbDrone.Core/Providers/IConfigProvider.cs @@ -5,6 +5,7 @@ namespace NzbDrone.Core.Providers public interface IConfigProvider { String SeriesRoot { get; set; } + String EpisodeNameFormat { get; set; } string GetValue(string key, object defaultValue, bool makePermanent); void SetValue(string key, string value); diff --git a/NzbDrone.Core/Providers/IEpisodeProvider.cs b/NzbDrone.Core/Providers/IEpisodeProvider.cs index 758c3cdf8..30f3054d1 100644 --- a/NzbDrone.Core/Providers/IEpisodeProvider.cs +++ b/NzbDrone.Core/Providers/IEpisodeProvider.cs @@ -20,5 +20,6 @@ namespace NzbDrone.Core.Providers bool IsNeeded(EpisodeModel episode); void RefreshEpisodeInfo(int seriesId); + IList<Episode> GetEpisodeBySeason(long seasonId); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/IMediaFileProvider.cs b/NzbDrone.Core/Providers/IMediaFileProvider.cs index 651e59fcc..c9c19ed9d 100644 --- a/NzbDrone.Core/Providers/IMediaFileProvider.cs +++ b/NzbDrone.Core/Providers/IMediaFileProvider.cs @@ -1,3 +1,4 @@ +using NzbDrone.Core.Model; using NzbDrone.Core.Repository; namespace NzbDrone.Core.Providers @@ -11,5 +12,6 @@ namespace NzbDrone.Core.Providers void Scan(Series series); EpisodeFile ImportFile(Series series, string filePath); + string GenerateEpisodePath(EpisodeModel episode); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index dc6fad676..07aadc607 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -14,15 +14,17 @@ namespace NzbDrone.Core.Providers public class MediaFileProvider : IMediaFileProvider { private readonly IRepository _repository; + private readonly IConfigProvider _configProvider; 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(IRepository repository, IDiskProvider diskProvider, IEpisodeProvider episodeProvider) + public MediaFileProvider(IRepository repository, IConfigProvider _configProvider, IDiskProvider diskProvider, IEpisodeProvider episodeProvider) { _repository = repository; + this._configProvider = _configProvider; _diskProvider = diskProvider; _episodeProvider = episodeProvider; } @@ -55,21 +57,21 @@ namespace NzbDrone.Core.Providers 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); + var episodeFile = new EpisodeFile(); + episodeFile.DateAdded = DateTime.Now; + episodeFile.SeriesId = series.SeriesId; + episodeFile.EpisodeId = episode.EpisodeId; + episodeFile.Path = Parser.NormalizePath(filePath); + episodeFile.Size = _diskProvider.GetSize(filePath); + episodeFile.Quality = Parser.ParseQuality(filePath); + episodeFile.Proper = Parser.ParseProper(filePath); + _repository.Add(episodeFile); + Logger.Trace("File {0}:{1} attached to '{2}'", episodeFile.FileId, filePath, episode.EpisodeId); - return epFile; + return episodeFile; } - Logger.Warn("Unable to find Series:{0} Season:{1} Episode:{2} in the database.", series.Title, closureEpisode.SeasonNumber, closureEpisode.EpisodeNumber); + Logger.Warn("Unable to find Series:{0} Season:{1} Episode:{2} in the database. File:{3}", series.Title, closureEpisode.SeasonNumber, closureEpisode.EpisodeNumber, filePath); } } else @@ -96,9 +98,23 @@ namespace NzbDrone.Core.Providers } } + + public string GenerateEpisodePath(EpisodeModel episode) + { + var episodeNamePattern = _configProvider.EpisodeNameFormat; + + episodeNamePattern = episodeNamePattern.Replace("{series}", "{0}"); + episodeNamePattern = episodeNamePattern.Replace("{episode", "{1"); + episodeNamePattern = episodeNamePattern.Replace("{season", "{2"); + episodeNamePattern = episodeNamePattern.Replace("{title}", "{3}"); + episodeNamePattern = episodeNamePattern.Replace("{quality}", "{4}"); + + return String.Format(episodeNamePattern, episode.SeriesTitle, episode.EpisodeNumber, episode.SeasonNumber, episode.EpisodeTitle, episode.Quality); + } + private List<string> GetMediaFileList(string path) { - Logger.Info("Scanning '{0}' for episodes", path); + Logger.Debug("Scanning '{0}' for episodes", path); var mediaFileList = new List<string>(); @@ -107,8 +123,9 @@ namespace NzbDrone.Core.Providers mediaFileList.AddRange(_diskProvider.GetFiles(path, ext, SearchOption.AllDirectories)); } - Logger.Info("{0} media files were found in {1}", mediaFileList.Count, path); + Logger.Trace("{0} media files were found in {1}", mediaFileList.Count, path); return mediaFileList; } + } } diff --git a/NzbDrone.Core/Providers/SeasonProvider.cs b/NzbDrone.Core/Providers/SeasonProvider.cs index bb97862c8..85a22d85e 100644 --- a/NzbDrone.Core/Providers/SeasonProvider.cs +++ b/NzbDrone.Core/Providers/SeasonProvider.cs @@ -31,11 +31,10 @@ namespace NzbDrone.Core.Providers if (_sonicRepo.Exists<Season>(s => s.SeasonId == seasonId)) return; //TODO: Calculate Season Folder - Logger.Debug("Adding Season To DB. [SeriesID:{0} SeasonID:{1} SeasonNumber:{2} Folder:{3}]", seriesId, seasonId, seasonNumber, "????"); + Logger.Trace("Adding Season To DB. [SeriesID:{0} SeasonID:{1} SeasonNumber:{2}]", seriesId, seasonId, seasonNumber, "????"); var newSeason = new Season() { - Folder = String.Empty, Monitored = true, SeasonId = seasonId, SeasonNumber = seasonNumber, diff --git a/NzbDrone.Core/Providers/SyncProvider.cs b/NzbDrone.Core/Providers/SyncProvider.cs index 8c8644598..99e518a2e 100644 --- a/NzbDrone.Core/Providers/SyncProvider.cs +++ b/NzbDrone.Core/Providers/SyncProvider.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.Providers { private readonly ISeriesProvider _seriesProvider; private readonly IEpisodeProvider _episodeProvider; + private readonly IMediaFileProvider _mediaFileProvider; private readonly INotificationProvider _notificationProvider; private ProgressNotification _seriesSyncNotification; @@ -21,10 +22,11 @@ namespace NzbDrone.Core.Providers private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - public SyncProvider(ISeriesProvider seriesProvider, IEpisodeProvider episodeProvider, INotificationProvider notificationProvider) + public SyncProvider(ISeriesProvider seriesProvider, IEpisodeProvider episodeProvider, IMediaFileProvider mediaFileProvider, INotificationProvider notificationProvider) { _seriesProvider = seriesProvider; _episodeProvider = episodeProvider; + _mediaFileProvider = mediaFileProvider; _notificationProvider = notificationProvider; } @@ -65,7 +67,7 @@ namespace NzbDrone.Core.Providers { try { - _seriesSyncNotification.CurrentStatus = String.Format("Analysing Folder: {0}", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(new DirectoryInfo(seriesFolder).Name)); + _seriesSyncNotification.CurrentStatus = String.Format("Searching For: {0}", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(new DirectoryInfo(seriesFolder).Name)); Logger.Debug("Folder '{0}' isn't mapped in the database. Trying to map it.'", seriesFolder); var mappedSeries = _seriesProvider.MapPathToSeries(seriesFolder); @@ -79,9 +81,12 @@ namespace NzbDrone.Core.Providers //Check if series is mapped to another folder if (_seriesProvider.GetSeries(mappedSeries.Id) == null) { - _seriesSyncNotification.CurrentStatus = String.Format("Downloading Info for '{0}'", mappedSeries.SeriesName); + _seriesSyncNotification.CurrentStatus = String.Format("{0}: downloading series info...", mappedSeries.SeriesName); _seriesProvider.AddSeries(seriesFolder, mappedSeries); _episodeProvider.RefreshEpisodeInfo(mappedSeries.Id); + _seriesSyncNotification.CurrentStatus = String.Format("{0}: finding episodes on disk...", mappedSeries.SeriesName); + _mediaFileProvider.Scan(_seriesProvider.GetSeries(mappedSeries.Id)); + } else { diff --git a/NzbDrone.Core/Repository/Episode.cs b/NzbDrone.Core/Repository/Episode.cs index bfebd5fea..27a58e713 100644 --- a/NzbDrone.Core/Repository/Episode.cs +++ b/NzbDrone.Core/Repository/Episode.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Web.Script.Serialization; using SubSonic.SqlGeneration.Schema; namespace NzbDrone.Core.Repository diff --git a/NzbDrone.Core/Repository/Season.cs b/NzbDrone.Core/Repository/Season.cs index 42e5ad1fd..aaeb11a90 100644 --- a/NzbDrone.Core/Repository/Season.cs +++ b/NzbDrone.Core/Repository/Season.cs @@ -10,8 +10,7 @@ namespace NzbDrone.Core.Repository public long SeriesId { get; set; } public int SeasonNumber { get; set; } public bool Monitored { get; set; } - public string Folder { get; set; } - + [SubSonicToManyRelation] public virtual List<Episode> Episodes { get; private set; } diff --git a/NzbDrone.Web/Content/style.css b/NzbDrone.Web/Content/style.css index 6a2accce0..bb626824d 100644 --- a/NzbDrone.Web/Content/style.css +++ b/NzbDrone.Web/Content/style.css @@ -1,4 +1,3 @@ - /* Design by Free CSS Templates http://www.freecsstemplates.org @@ -6,7 +5,6 @@ Released for free under a Creative Commons Attribution 2.5 License */ body { - margin: 20px 0px 0px 0px; padding: 0; background: #191919 url(images/img07.jpg) no-repeat right top; font-family: Segoe UI, Tahoma, Geneva, sans-serif; @@ -19,7 +17,7 @@ h1, h2, h3 { margin: 0; color: #549900; - font-family: Segoe UI Light; + font-family: Segoe UI Light,Tahoma, Geneva, sans-serif; text-transform: lowercase; } @@ -173,63 +171,16 @@ hr { } -/* Search */ - -#search -{ - float: right; - padding: 10px 10px 0px 0px; -} - -#search form -{ - float: right; - margin: 0; - padding: 4px 0px 0 0; -} - -#search fieldset -{ - margin: 0; - padding: 0; - border: none; -} - -#search input -{ - float: left; - font: 12px Georgia, "Times New Roman" , Times, serif; -} - -#search-text -{ - width: 213px; - height: 28px; - padding: 6px 0 0 7px; - border: none; - background: url(images/img02.jpg) no-repeat left top; - color: #000000; -} - -#search-submit -{ - width: 82px; - height: 28px; - margin-left: 10px; - padding: 0px 5px; - background: url(images/img03.jpg) no-repeat left top; - border: none; - text-indent: -9999px; - color: #FFFFFF; -} /* Page */ #page { - width: 950px; margin: 0 auto 60px; overflow: hidden; + background-color: White; + padding: 10px 20px 20px 20px; + width: 910px; } @@ -293,79 +244,9 @@ hr background: #FFFFFF no-repeat; float: left; width: 100%; - padding: 0px 0px 0px 0px; -} - -/* Post */ - -.post -{ padding: 0px 30px 20px 30px; } -.post .title -{ - height: 44px; - margin: 0px; - padding: 36px 0px 0px 0px; - width: 576px; -} - -.post .title a -{ - letter-spacing: -2px; - text-decoration: none; - text-transform: lowercase; - font-size: 36px; - color: #424242; -} - -.post .title a:hover -{ - color: #065EFE; -} - -.post .meta -{ - margin-left: 2px; - padding: 4px 30px 2px 0px; - text-transform: uppercase; - font-weight: bold; - font-size: 11px; - color: #66665E; -} - -.post .meta span -{ - margin: 0px; -} - -.post .meta a -{ - text-decoration: none; -} - -.post .entry -{ - padding: 10px 0px 0px 0px; -} - -.post .comments -{ - display: block; - width: 120px; - height: 18px; - margin: 0px; - padding: 3px 0px 0px 40px; - background: #90BF35; - border: 1px solid #5F817E; - text-decoration: none; - font-size: 11px; - text-decoration: none; - font-weight: bold; - color: #FFFFFF; -} - /* Footer */ #footer @@ -376,10 +257,6 @@ hr padding: 0; } -#footer-bgcontent -{ -} - #footer p { margin: 0; @@ -393,8 +270,14 @@ hr { } +.stackframe +{ + font-family: Consolas, Monospace; +} -.t-grid .t-last { -border-bottom:1px solid #EEEEEE; -border-color:#EEEEEE; -} \ No newline at end of file +/*fix telerik grid missing boarder*/ +.t-grid .t-last +{ + border-bottom: 1px solid #EEEEEE; + border-color: #EEEEEE; +} diff --git a/NzbDrone.Web/Controllers/LogController.cs b/NzbDrone.Web/Controllers/LogController.cs index 69ad51452..49727a190 100644 --- a/NzbDrone.Web/Controllers/LogController.cs +++ b/NzbDrone.Web/Controllers/LogController.cs @@ -38,6 +38,26 @@ namespace NzbDrone.Web.Controllers } + [GridAction] + public ActionResult _AjaxBinding2() + { + var l= _logProvider.GetAllLogs().Select(c => new { + c.DisplayLevel, + c.ExceptionMessage, + c.ExceptionString, + c.ExceptionType, + //c.Level, + c.Logger, + c.LogId, + c.Message, + c.Stack, + c.Time + }); + + return View(new GridModel(l)); + } + + } } diff --git a/NzbDrone.Web/Controllers/SeriesController.cs b/NzbDrone.Web/Controllers/SeriesController.cs index e0bc6471e..9eb42c5dd 100644 --- a/NzbDrone.Web/Controllers/SeriesController.cs +++ b/NzbDrone.Web/Controllers/SeriesController.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Web; using System.Web.Mvc; using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository; +using NzbDrone.Web.Models; +using Telerik.Web.Mvc; namespace NzbDrone.Web.Controllers { @@ -52,38 +56,93 @@ namespace NzbDrone.Web.Controllers }); } - public JsonResult MediaDetect() + + [GridAction] + public ActionResult _AjaxSeasonGrid(int seasonId) { - Core.Providers.IMediaDiscoveryProvider disco = new Core.Providers.MediaDiscoveryProvider(); - return Json(new { Discovered = disco.DiscoveredMedia }, JsonRequestBehavior.AllowGet); + var episodes = _episodeProvider.GetEpisodeBySeason(seasonId).Select(c => new EpisodeModel() + { + EpisodeId = c.EpisodeId, + EpisodeNumber = c.EpisodeNumber, + SeasonNumber = c.SeasonNumber, + Title = c.Title, + Overview = c.Overview, + AirDate = c.AirDate + }); + return View(new GridModel(episodes)); } - public JsonResult LightUpMedia() + + + [GridAction] + public ActionResult _CustomBinding(GridCommand command, int seasonId) { - Core.Providers.IMediaDiscoveryProvider disco = new Core.Providers.MediaDiscoveryProvider(); - IMediaProvider p = disco.Providers[0]; - return Json(new { ID = 0, HTML = "<span class='MediaRenderer XBMC'><span class='Play'>Play</span><span class='Pause'>Pause</span><span class='Stop'>Stop</span></span>" }, JsonRequestBehavior.AllowGet); - } - public JsonResult ControlMedia() - { - Core.Providers.IMediaDiscoveryProvider disco = new Core.Providers.MediaDiscoveryProvider(); - IMediaProvider p = disco.Providers[0]; - string action = Request["Action"]; - switch (action) + IEnumerable<Episode> data = GetData(command); + return View(new GridModel { - case "Play": - p.Play(); - break; - case "Pause": - p.Pause(); - break; - case "Stop": - p.Stop(); - break; - default: - break; + Data = data, + Total = data.Count() + }); + } + private IEnumerable<Episode> GetData(GridCommand command) + { + + return null; + /* + IQueryable<Episode> data = .Orders; + //Apply filtering + if (command.FilterDescriptors.Any()) + { + data = data.Where(ExpressionBuilder.Expression<Order>(command.FilterDescriptors)); } - return Json(new { Success = true }, JsonRequestBehavior.AllowGet); + // Apply sorting + foreach (SortDescriptor sortDescriptor in command.SortDescriptors) + { + if (sortDescriptor.SortDirection == ListSortDirection.Ascending) + { + switch (sortDescriptor.Member) + { + case "OrderID": + data = data.OrderBy(ExpressionBuilder.Expression<Order, int>(sortDescriptor.Member)); + break; + case "Customer.ContactName": + data = data.OrderBy(order => order.Customer.ContactName); + break; + case "ShipAddress": + data = data.OrderBy(order => order.ShipAddress); + break; + case "OrderDate": + data = data.OrderBy(order => order.OrderDate); + break; + } + } + else + { + switch (sortDescriptor.Member) + { + case "OrderID": + data = data.OrderByDescending(order => order.OrderID); + break; + case "Customer.ContactName": + data = data.OrderByDescending(order => order.Customer.ContactName); + break; + case "ShipAddress": + data = data.OrderByDescending(order => order.ShipAddress); + break; + case "OrderDate": + data = data.OrderByDescending(order => order.OrderDate); + break; + } + } + } + count = data.Count(); + // ... and paging + if (command.PageSize > 0) + { + data = data.Skip((command.Page - 1) * command.PageSize); + } + data = data.Take(command.PageSize); + return data;*/ } // diff --git a/NzbDrone.Web/Models/EpisodeModel.cs b/NzbDrone.Web/Models/EpisodeModel.cs new file mode 100644 index 000000000..6447a3702 --- /dev/null +++ b/NzbDrone.Web/Models/EpisodeModel.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace NzbDrone.Web.Models +{ + public class EpisodeModel + { + public string Title { get; set; } + public int EpisodeId { get; set; } + public int EpisodeNumber { get; set; } + public int SeasonNumber { get; set; } + public string Overview { get; set; } + + public DateTime AirDate { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index 1a5742c00..f9ec2b48b 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -86,6 +86,7 @@ </Compile> <Compile Include="Helpers\IsCurrentActionHelper.cs" /> <Compile Include="Models\AccountModels.cs" /> + <Compile Include="Models\EpisodeModel.cs" /> <Compile Include="Models\SettingsModels.cs" /> <Compile Include="Ninject.Web.Mvc\ControllerMissingBindingResolver.cs" /> <Compile Include="Ninject.Web.Mvc\FilterInjector.cs" /> diff --git a/NzbDrone.Web/Views/Log/Index.aspx b/NzbDrone.Web/Views/Log/Index.aspx index f56e389ee..6e331f120 100644 --- a/NzbDrone.Web/Views/Log/Index.aspx +++ b/NzbDrone.Web/Views/Log/Index.aspx @@ -15,7 +15,7 @@ } else if (e.dataItem.Level == 5) { e.row.style.backgroundColor = "black"; - e.row.style.Color = "white"; + e.row.style.color = "red"; } //e.row.style.color = 'blue'; } @@ -34,25 +34,21 @@ .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.ClientTemplate( - + .DetailView(detailView => detailView.ClientTemplate( + "<div><#= Logger #></div>" + "<div><#= ExceptionType #></div>" + "<div><#= ExceptionMessage #></div>" + - "<div><#= ExceptionString #></div>" - - )) + "<div class='stackframe'><#= ExceptionString #></div>" + + )).DataBinding(data => data.Ajax().Select("_AjaxBinding", "Log")) .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() + .Pageable(c => c.PageSize(20).Position(GridPagerPosition.Both).Style(GridPagerStyles.PageInput | GridPagerStyles.NextPreviousAndNumeric)) .Filterable() .ClientEvents(c => c.OnRowDataBound("onRowDataBound")) - //.Groupable(grouping => grouping.Groups(groups => groups.Add(c => c.Time.Date)).Enabled(true)) .Render(); %> </asp:Content> diff --git a/NzbDrone.Web/Views/Series/Details.aspx b/NzbDrone.Web/Views/Series/Details.aspx index ed60f15dd..833183ef0 100644 --- a/NzbDrone.Web/Views/Series/Details.aspx +++ b/NzbDrone.Web/Views/Series/Details.aspx @@ -2,6 +2,7 @@ <%@ Import Namespace="Telerik.Web.Mvc.UI" %> <%@ Import Namespace="NzbDrone.Core.Repository" %> +<%@ Import Namespace="NzbDrone.Web.Models" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> <%: Model.Title %> </asp:Content> @@ -42,18 +43,23 @@ Season <%: season.SeasonNumber %></h3> <% - 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(); + Season season1 = season; + Html.Telerik().Grid<EpisodeModel>().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).Title("Title"); + columns.Bound(c => c.AirDate).Format("{0:d}").Width(0); + }) + //.DetailView(detailView => detailView.Template(e => Html.RenderPartial("EpisodeDetail", e))) + .DetailView(detailView => detailView.ClientTemplate("<div id ='ep_<#= EpisodeId #>'/>")) + .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber)).Enabled(false)) + .Footer(false) + .DataBinding(d => d.Ajax().Select("_AjaxSeasonGrid", "Series", new RouteValueDictionary { { "seasonId", season1.SeasonId.ToString() } })) + //.EnableCustomBinding(true) + .ClientEvents(e => e.OnDetailViewExpand("episodeDetailExpanded")) + .Render(); } //Specials @@ -74,8 +80,7 @@ Html.Telerik().Grid(specialSeasons.Episodes).Name("seasons_specials") 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)) + .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber)).Enabled(false)) .Footer(false) .Render(); } @@ -85,3 +90,12 @@ Html.Telerik().Grid(specialSeasons.Episodes).Name("seasons_specials") <%: Html.ActionLink("Back to Series", "Index") %> </p> </asp:Content> +<asp:Content ContentPlaceHolderID="Scripts" runat="server"> + <script type="text/javascript"> + + function episodeDetailExpanded(e) { + $console.log("OnDetailViewExpand :: " + e.masterRow.cells[1].innerHTML); + } + + </script> +</asp:Content> diff --git a/NzbDrone.Web/Views/Series/EpisodeDetail.ascx b/NzbDrone.Web/Views/Series/EpisodeDetail.ascx index 6f31877e2..67d7fc94a 100644 --- a/NzbDrone.Web/Views/Series/EpisodeDetail.ascx +++ b/NzbDrone.Web/Views/Series/EpisodeDetail.ascx @@ -1,7 +1,7 @@ <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NzbDrone.Core.Repository.Episode>" %> <%@ Import Namespace="Telerik.Web.Mvc.UI" %> <%: Model.Overview %> -<%: +<%--<%: Html.Telerik().Grid(Model.Files) .Name("files_" + Model.EpisodeId) .Columns(columns => @@ -11,4 +11,4 @@ columns.Bound(c => c.DateAdded); }) .Footer(false) -%> \ No newline at end of file +%>--%> \ No newline at end of file diff --git a/NzbDrone.Web/Views/Shared/Site.Master b/NzbDrone.Web/Views/Shared/Site.Master index 7e1ed0b1d..0076a0ec0 100644 --- a/NzbDrone.Web/Views/Shared/Site.Master +++ b/NzbDrone.Web/Views/Shared/Site.Master @@ -1,40 +1,28 @@ -<!-- -Design by Free CSS Templates -http://www.freecsstemplates.org -Released for free under a Creative Commons Attribution 2.5 License - -Name : Concurrence -Description: A two-column, fixed-width design for 1024x768 screen resolutions. -Version : 1.0 -Released : 20100727 ---> - -<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %> - +<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %> <%@ Import Namespace="Helpers" %> <%@ Import Namespace="Telerik.Web.Mvc.UI" %> + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <asp:ContentPlaceHolder runat="server" ID="Scripts" /> <title>NZBDrone <% Html.Telerik().StyleSheetRegistrar().DefaultGroup(group => group.Add("telerik.common.css") - .Add("telerik.sitefinity.css") - .Add("style.css") - .Add("notibar.css")).Render(); + .Add("telerik.sitefinity.css") + .Add("notibar.css")) + .Render(); %> + - Fork me on GitHub + +