fixed a bunch of things. added basic support for file scanning. logs are now avilable in the UI

This commit is contained in:
Keivan 2010-10-24 00:46:58 -07:00
parent c8a8fb4d62
commit fa0af257ff
48 changed files with 914 additions and 208 deletions

2
.gitignore vendored
View File

@ -30,4 +30,4 @@ _ReSharper*/
[Ll]ogs/
/[Pp]ackage/
#NZBDrone specific
nzbdrone.db
*.db

View File

@ -0,0 +1,15 @@
<configuration>
<configSections>
<section name="configurationRedirection" />
</configSections>
<configProtectedData>
<providers>
<add name="IISRsaProvider" type="" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="iisConfigurationKey" cspProviderName="" useMachineContainer="true" useOAEP="false" />
</providers>
</configProtectedData>
<configurationRedirection />
</configuration>

View File

@ -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);
}
}
}
}

View File

@ -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<IRepository>();
repository.Setup(c => c.Update(It.IsAny<Episode>())).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<Series>.CreateNew().Build();
var fakeEpisode = Builder<Episode>.CreateNew().With(c => c.SeriesId = fakeSeries.SeriesId).Build();
//Mocks
var repository = new Mock<IRepository>();
repository.Setup(r => r.Exists<EpisodeFile>(It.IsAny<Expression<Func<EpisodeFile, Boolean>>>())).Returns(false).Verifiable();
var episodeProvider = new Mock<IEpisodeProvider>();
episodeProvider.Setup(e => e.GetEpisode(fakeSeries.SeriesId, seasonNumber, episodeNumner)).Returns(fakeEpisode).Verifiable();
var diskProvider = new Mock<IDiskProvider>();
diskProvider.Setup(e => e.GetSize(fileName)).Returns(12345).Verifiable();
var kernel = new MockingKernel();
kernel.Bind<IDiskProvider>().ToConstant(diskProvider);
kernel.Bind<IRepository>().ToConstant(repository.Object);
kernel.Bind<IEpisodeProvider>().ToConstant(episodeProvider.Object);
kernel.Bind<IDiskProvider>().ToConstant(diskProvider.Object);
kernel.Bind<IMediaFileProvider>().To<MediaFileProvider>();
var fakeSeries = new Series()
{
Path = MockLib.StandardSeries[0]
};
//Act
kernel.Get<IMediaFileProvider>().Scan(fakeSeries);
var result = kernel.Get<IMediaFileProvider>().ImportFile(fakeSeries, fileName);
//Assert
repository.Verify(c => c.Update(It.IsAny<Episode>()), Times.Exactly(1 * 2));
repository.VerifyAll();
episodeProvider.VerifyAll();
diskProvider.VerifyAll();
Assert.IsNotNull(result);
repository.Verify(r => r.Add<EpisodeFile>(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<Series>.CreateNew().Build();
var fakeEpisode = Builder<Episode>.CreateNew().With(c => c.SeriesId = fakeSeries.SeriesId).Build();
//Mocks
var repository = new Mock<IRepository>(MockBehavior.Strict);
repository.Setup(r => r.Exists<EpisodeFile>(It.IsAny<Expression<Func<EpisodeFile, Boolean>>>())).Returns(true).Verifiable();
var episodeProvider = new Mock<IEpisodeProvider>(MockBehavior.Strict);
var diskProvider = new Mock<IDiskProvider>(MockBehavior.Strict);
var kernel = new MockingKernel();
kernel.Bind<IRepository>().ToConstant(repository.Object);
kernel.Bind<IEpisodeProvider>().ToConstant(episodeProvider.Object);
kernel.Bind<IDiskProvider>().ToConstant(diskProvider.Object);
kernel.Bind<IMediaFileProvider>().To<MediaFileProvider>();
//Act
var result = kernel.Get<IMediaFileProvider>().ImportFile(fakeSeries, fileName);
//Assert
repository.VerifyAll();
episodeProvider.VerifyAll();
diskProvider.VerifyAll();
Assert.IsNull(result);
repository.Verify(r => r.Add<EpisodeFile>(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<Series>.CreateNew().Build();
//Mocks
var repository = new Mock<IRepository>(MockBehavior.Strict);
repository.Setup(r => r.Exists<EpisodeFile>(It.IsAny<Expression<Func<EpisodeFile, Boolean>>>())).Returns(false).Verifiable();
var episodeProvider = new Mock<IEpisodeProvider>(MockBehavior.Strict);
episodeProvider.Setup(e => e.GetEpisode(fakeSeries.SeriesId, seasonNumber, episodeNumner)).Returns<Episode>(null).Verifiable();
var diskProvider = new Mock<IDiskProvider>(MockBehavior.Strict);
var kernel = new MockingKernel();
kernel.Bind<IRepository>().ToConstant(repository.Object);
kernel.Bind<IEpisodeProvider>().ToConstant(episodeProvider.Object);
kernel.Bind<IDiskProvider>().ToConstant(diskProvider.Object);
kernel.Bind<IMediaFileProvider>().To<MediaFileProvider>();
//Act
var result = kernel.Get<IMediaFileProvider>().ImportFile(fakeSeries, fileName);
//Assert
repository.VerifyAll();
episodeProvider.VerifyAll();
diskProvider.VerifyAll();
Assert.IsNull(result);
repository.Verify(r => r.Add<EpisodeFile>(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);
}
}

View File

@ -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<IDiskProvider>();
mock.Setup(c => c.GetDirectories(It.IsAny<String>())).Returns(StandardSeries);
mock.Setup(c => c.Exists(It.Is<String>(d => StandardSeries.Contains(d)))).Returns(true);
mock.Setup(c => c.FolderExists(It.Is<String>(d => StandardSeries.Contains(d)))).Returns(true);
foreach (var series in StandardSeries)

View File

@ -74,6 +74,7 @@
<Compile Include="Ninject.Moq\ExtensionsForBindingSyntax.cs" />
<Compile Include="Ninject.Moq\MockingKernel.cs" />
<Compile Include="Ninject.Moq\MockProvider.cs" />
<Compile Include="ParserTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="QualityProfileTest.cs" />
<Compile Include="RepoTest.cs" />

View File

@ -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);
}
}
}

View File

@ -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<Log>());
Assert.Count(1, sonicRepo.All<Log>());
var logItem = sonicRepo.All<Log>().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<Log>());
Assert.Count(1, sonicRepo.All<Log>());
var logItem = sonicRepo.All<Log>().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);
}
}
}

View File

@ -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<ISeriesProvider>().To<SeriesProvider>().InSingletonScope();
_kernel.Bind<ISeasonProvider>().To<SeasonProvider>();
_kernel.Bind<IEpisodeProvider>().To<EpisodeProvider>();
@ -41,7 +47,12 @@ namespace NzbDrone.Core
_kernel.Bind<IConfigProvider>().To<ConfigProvider>().InSingletonScope();
_kernel.Bind<ISyncProvider>().To<SyncProvider>().InSingletonScope();
_kernel.Bind<INotificationProvider>().To<NotificationProvider>().InSingletonScope();
_kernel.Bind<IRepository>().ToMethod(c => new SimpleRepository(provider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope();
_kernel.Bind<ILogProvider>().To<LogProvider>().InSingletonScope();
_kernel.Bind<IRepository>().ToMethod(c => new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope();
_kernel.Bind<IRepository>().ToConstant(logRepository).WhenInjectedInto<SubsonicTarget>().InSingletonScope();
_kernel.Bind<IRepository>().ToConstant(logRepository).WhenInjectedInto<LogProvider>().InSingletonScope();
ForceMigration(_kernel.Get<IRepository>());
}
@ -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<Series>(0, 1);
repository.GetPaged<EpisodeFile>(0, 1);
repository.GetPaged<Episode>(0, 1);
}

View File

@ -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();
}
}
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Core.Instrumentation
{
public interface ILogProvider
{
IQueryable<Log> GetAllLogs();
void DeleteAll();
}
}

View File

@ -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(); }
}
}
}

View File

@ -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<SubsonicTarget>();
LogManager.Configuration.AddTarget("DbLogger", sonicTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", NLog.LogLevel.Info, sonicTarget));
LogManager.Configuration.Reload();
}
}
}

View File

@ -0,0 +1,12 @@
namespace NzbDrone.Core.Instrumentation
{
public enum LogLevel
{
Trace,
Debug,
Info,
Warn,
Error,
Fatal
}
}

View File

@ -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<Log> GetAllLogs()
{
return _repository.All<Log>();
}
public void DeleteAll()
{
_repository.DeleteMany(GetAllLogs());
Logger.Info("Cleared Log History");
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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);
}
}
}

Binary file not shown.

View File

@ -150,13 +150,19 @@
<Reference Include="UPnP, Version=1.0.3932.37442, Culture=neutral, processorArchitecture=MSIL" />
</ItemGroup>
<ItemGroup>
<Compile Include="Instrumentation\ILogProvider.cs" />
<Compile Include="Instrumentation\LogLevel.cs" />
<Compile Include="Instrumentation\LogProvider.cs" />
<Compile Include="Instrumentation\SubsonicTarget.cs" />
<Compile Include="Instrumentation\ExceptioneerTarget.cs" />
<Compile Include="Instrumentation\NlogWriter.cs" />
<Compile Include="Model\EpisodeParseResult.cs" />
<Compile Include="Model\EpisodeModel.cs" />
<Compile Include="Repository\EpisodeFile.cs" />
<Compile Include="Model\Notification\BasicNotification.cs" />
<Compile Include="Model\Notification\ProgressNotificationStatus.cs" />
<Compile Include="Model\Notification\BasicNotificationType.cs" />
<Compile Include="Instrumentation.cs" />
<Compile Include="Instrumentation\LogConfiguration.cs" />
<Compile Include="Parser.cs" />
<Compile Include="Providers\Fakes\FakeNotificationProvider.cs" />
<Compile Include="Providers\IMediaFileProvider.cs" />
@ -182,6 +188,7 @@
<Compile Include="Providers\SabProvider.cs" />
<Compile Include="Providers\SeasonProvider.cs" />
<Compile Include="Repository\Episode.cs" />
<Compile Include="Instrumentation\Log.cs" />
<Compile Include="Repository\Quality\AllowedQuality.cs" />
<Compile Include="Repository\Config.cs" />
<Compile Include="Repository\Quality\QualityProfile.cs" />

View File

@ -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('/', '\\', ' ');
}
}
}

View File

@ -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('/', '\\', ' ');
}
}
}

View File

@ -30,7 +30,12 @@ namespace NzbDrone.Core.Providers
public Episode GetEpisode(long id)
{
return _sonicRepo.Single<Episode>(e => e.EpisodeId == id);
return _sonicRepo.Single<Episode>(id);
}
public Episode GetEpisode(int seriesId, int seasonNumber, int episodeNumber)
{
return _sonicRepo.Single<Episode>(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber && c.EpisodeNumber == episodeNumber);
}
public IList<Episode> GetEpisodeBySeries(long seriesId)

View File

@ -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);
}
}

View File

@ -8,6 +8,7 @@ namespace NzbDrone.Core.Providers
public interface IEpisodeProvider
{
Episode GetEpisode(long id);
Episode GetEpisode(int seriesId, int seasonNumber, int episodeNumber);
IList<Episode> GetEpisodeBySeries(long seriesId);
String GetSabTitle(Episode episode);

View File

@ -9,5 +9,7 @@ namespace NzbDrone.Core.Providers
/// </summary>
/// <param name="series">The series to be scanned</param>
void Scan(Series series);
EpisodeFile ImportFile(Series series, string filePath);
}
}

View File

@ -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
/// <param name="series">The series to be scanned</param>
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<EpisodeFile>(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;
}
/// <summary>
/// Removes files that no longer exist from the database
/// </summary>
/// <param name="files">list of files to verify</param>
public void CleanUp(List<EpisodeFile> 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>(episodeFile);
}
}
}
private List<string> GetMediaFileList(string path)
{
Logger.Info("Scanning '{0}' for episodes", path);
var mediaFileList = new List<string>();
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;
}
}
}

View File

@ -61,7 +61,7 @@ namespace NzbDrone.Core.Providers
var results = new List<String>();
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<Series>(s => s.Path == cleanPath))
{
results.Add(cleanPath);

View File

@ -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<EpisodeFile> Files { get; private set; }
}
}

View File

@ -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; }
}
}

View File

@ -35,6 +35,7 @@ namespace NzbDrone.Core.Repository
[SubSonicToManyRelation]
public virtual List<Episode> Episodes { get; private set; }
[SubSonicToManyRelation]
public virtual List<EpisodeFile> Files { get; private set; }
}
}

View File

@ -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()));
}
}
}

View File

@ -34,7 +34,7 @@ namespace NzbDrone.Web.Controllers
_configProvider.SeriesRoot = model.TvFolder;
//return RedirectToAction("index");
}
return View(model);
return RedirectToAction("index", "series");
}
}

View File

@ -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<IRepository>().ToConstant(kernel.Get<IRepository>("LogDb"));
return kernel;
}
// ReSharper disable InconsistentNaming

View File

@ -38,6 +38,9 @@
<ItemGroup>
<Reference Include="Ninject, Version=2.1.0.76, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL" />
<Reference Include="NLog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL" />
<Reference Include="SubSonic.Core, Version=3.0.0.3, Culture=neutral, processorArchitecture=MSIL">
<HintPath>D:\My Dropbox\Git\NzbDrone\NzbDrone.Core\Libraries\SubSonic.Core.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" />
@ -74,6 +77,7 @@
<ItemGroup>
<Compile Include="Controllers\AccountController.cs" />
<Compile Include="Controllers\HomeController.cs" />
<Compile Include="Controllers\LogController.cs" />
<Compile Include="Controllers\NotificationController.cs" />
<Compile Include="Controllers\SeriesController.cs" />
<Compile Include="Controllers\SettingsController.cs" />
@ -209,7 +213,10 @@
<Content Include="Scripts\jquery-ui-1.8.5.custom.min.js" />
<Content Include="Scripts\jquery.jgrowl.js" />
<Content Include="Scripts\Notification.js" />
<Content Include="Views\Log\LogDetail.ascx" />
<Content Include="Views\Log\Index.aspx" />
<Content Include="Views\Series\Details.aspx" />
<Content Include="Views\Series\EpisodeDetail.ascx" />
<Content Include="Views\Series\index.aspx" />
<Content Include="Views\Series\Unmapped.aspx" />
<Content Include="Views\Settings\Index.aspx" />
@ -244,6 +251,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />
<Folder Include="Views\Series\DisplayTemplates\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\NzbDrone.Core.csproj">
@ -269,7 +277,15 @@
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<SaveServerSettingsInUserFile>True</SaveServerSettingsInUserFile>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>21704</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost/NzbDrone</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://localhost:8989</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>

View File

@ -5,7 +5,7 @@ $(function () {
refreshNotifications();
var timer = window.setInterval(function () {
speed = 1800;
speed = 1000;
refreshNotifications();
}, 2000);

View File

@ -1,12 +1,19 @@
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="Telerik.Web.Mvc.UI" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Home Page
Logs
</asp:Content>
<asp:Content ID="Menu" ContentPlaceHolderID="ActionMenu" runat="server">
<%
Html.Telerik().Menu().Name("logMenu").Items(items => items.Add().Text("Clear Logs").Action("Clear", "Log")).Render();
%>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2><%: ViewData["Message"] %></h2>
<h2>
<%: ViewData["Message"] %></h2>
<p>
To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">
http://asp.net/mvc</a>.
</p>
</asp:Content>

View File

@ -0,0 +1,31 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<NzbDrone.Core.Instrumentation.Log>>" %>
<%@ Import Namespace="Telerik.Web.Mvc.UI" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Logs
</asp:Content>
<asp:Content ID="Menu" ContentPlaceHolderID="ActionMenu" runat="server">
<%
Html.Telerik().Menu().Name("logMenu").Items(items => items.Add().Text("Clear Logs").Action("Clear", "Log")).Render();
%>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<%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();
%>
</asp:Content>

View File

@ -0,0 +1,15 @@
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NzbDrone.Core.Instrumentation.Log>" %>
<ul>
<li>
<%: Model.Logger %>
</li>
<li>
<%: Model.ExceptionType%>
</li>
<li>
<%: Model.ExceptionMessage%>
</li>
<li>
<%: Model.ExceptionString%>
</li>
</ul>

View File

@ -1,6 +1,7 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<NzbDrone.Core.Repository.Series>" %>
<%@ Import Namespace="Telerik.Web.Mvc.UI" %>
<%@ Import Namespace="NzbDrone.Core.Repository" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
<%: Model.Title %>
</asp:Content>
@ -31,22 +32,56 @@
<div class="display-field">
<%: Model.Path %></div>
</fieldset>
<%= 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)))
%>
<br />
<h3>
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();
}
//Specials
var specialSeasons = Model.Seasons.Where(s => s.SeasonNumber == 0).FirstOrDefault();
if (specialSeasons != null)
{
%>
<br />
<h3>
Specials</h3>
<%
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();
}
%>
<p>
<%-- <%: 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") %>
</p>
</asp:Content>

View File

@ -0,0 +1,14 @@
<%@ 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 =>
{
columns.Bound(c => c.Path);
columns.Bound(c => c.Quality);
columns.Bound(c => c.DateAdded);
})
.Footer(false)
%>

View File

@ -5,9 +5,7 @@
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Series
</asp:Content>
<asp:Content ID="Menue" ContentPlaceHolderID="ActionMenue" runat="server">
<div id="Mediabox">
</div>
<asp:Content ID="Menu" ContentPlaceHolderID="ActionMenu" runat="server">
<%
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"))

View File

@ -32,6 +32,10 @@ Released : 20100727
%>
</head>
<body>
<a href="http://github.com/kayone/NzbDrone">
<img style="position: absolute; top: 0; left: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_left_darkblue_121621.png"
alt="Fork me on GitHub" />
</a>
<div id="header">
<div id="msgBox">
<span id="msgText">Scanning Series Folder...</span>
@ -40,6 +44,7 @@ Released : 20100727
<ul>
<%=Html.CurrentActionLink("Series", "Index", "Series") %>
<%=Html.CurrentActionLink("Settings", "Index", "Settings") %>
<%=Html.CurrentActionLink("Logs", "Index", "Log") %>
</ul>
</div>
<!-- end #menu -->
@ -53,7 +58,7 @@ Released : 20100727
<hr />
<!-- end #logo -->
<div id="page">
<asp:ContentPlaceHolder ID="ActionMenue" runat="server" />
<asp:ContentPlaceHolder ID="ActionMenu" runat="server" />
<div id="content">
<div class="post">
<div class="entry">

View File

@ -1,16 +1,17 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Web.Tests", "NzbDrone.Web.Test\NzbDrone.Web.Tests.csproj", "{99CDD5DC-698F-4624-B431-2D6381CE3A15}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone", "NzbDrone\NzbDrone.csproj", "{D12F7F2F-8A3C-415F-88FA-6DD061A84869}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Core", "NzbDrone.Core\NzbDrone.Core.csproj", "{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Web", "NzbDrone.Web\NzbDrone.Web.csproj", "{43BD3BBD-1531-4D8F-9C08-E1CD544AB2CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Core.Test", "NzbDrone.Core.Test\NzbDrone.Core.Test.csproj", "{193ADD3B-792B-4173-8E4C-5A3F8F0237F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Core", "NzbDrone.Core\NzbDrone.Core.csproj", "{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone", "NzbDrone\NzbDrone.csproj", "{D12F7F2F-8A3C-415F-88FA-6DD061A84869}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NzbDrone.Web.Tests", "NzbDrone.Web.Test\NzbDrone.Web.Tests.csproj", "{99CDD5DC-698F-4624-B431-2D6381CE3A15}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{57A04B72-8088-4F75-A582-1158CF8291F7}"
EndProject
Global

View File

@ -35,8 +35,8 @@ namespace NzbDrone
dte2 = (DTE2)System.Runtime.InteropServices.Marshal.
GetActiveObject("VisualStudio.DTE.10.0");
var pa = new ProcessAttacher(dte2, "iisexpress", 20);
pa.OptimisticAttachManaged();
var pa = new ProcessAttacher(dte2, "iisexpress", 10);
pa.PessimisticAttachManaged();
}
@ -87,10 +87,9 @@ namespace NzbDrone
}
catch (Exception ex)
{
if (ex.Message.Contains("Invalid index."))
{
return AttachResult.NotRunning;
}
return AttachResult.NotRunning;
}
proc.Attach2(eng);

View File

@ -41,7 +41,15 @@ namespace NzbDrone
//Manually Attach debugger to IISExpress
if (Debugger.IsAttached)
{
ProcessAttacher.Attach();
try
{
ProcessAttacher.Attach();
}
catch (Exception e)
{
Logger.Warn("Unable to attach to debugger", e);
}
}
#endif