diff --git a/.gitignore b/.gitignore index df7630793..be082609e 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ config.xml UpdateLogs/ NzbDrone.Web/MediaCover NzbDrone.fpr -nzbdrone.log*txt \ No newline at end of file +nzbdrone.log*txt +_rawPackage_service/ \ No newline at end of file diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index 5b93f8ccd..d9bf52186 100644 --- a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -64,6 +64,7 @@ + diff --git a/NzbDrone.Common.Test/ReportingService_ReportParseError_Fixture.cs b/NzbDrone.Common.Test/ReportingService_ReportParseError_Fixture.cs new file mode 100644 index 000000000..a69fa1f94 --- /dev/null +++ b/NzbDrone.Common.Test/ReportingService_ReportParseError_Fixture.cs @@ -0,0 +1,68 @@ +using System.Linq; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Contract; +using NzbDrone.Test.Common; + +namespace NzbDrone.Common.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class ReportingService_ReportParseError_Fixture : TestBase + { + [SetUp] + public void SetUp() + { + ReportingService.ClearCache(); + } + + [TearDown] + public void TearDown() + { + ReportingService.ClearCache(); + } + + [Test] + public void report_parse_error_should_send_report_to_server() + { + const string badTitle = "Bad Title"; + + ReportingService.ReportParseError(badTitle); + MockedRestProvider.Verify(p => p.PostData(It.IsAny(), It.Is(c => c.Title == badTitle)), Times.Once()); + } + + [Test] + public void report_parse_error_should_send_duplicated_report_once() + { + const string badTitle = "Bad Title"; + + ReportingService.ReportParseError(badTitle); + ReportingService.ReportParseError(badTitle); + + MockedRestProvider.Verify(p => p.PostData(It.IsAny(), It.IsAny()), Times.Once()); + } + + [Test] + public void report_parse_error_should_send_duplicated_report_once_with_diffrent_casing() + { + const string badTitle = "Bad Title"; + + ReportingService.ReportParseError(badTitle.ToUpper()); + ReportingService.ReportParseError(badTitle); + + MockedRestProvider.Verify(p => p.PostData(It.IsAny(), It.IsAny()), Times.Once()); + } + + [Test] + public void report_parse_error_should_send_multiple_reports_if_titles_are_diffrent() + { + ReportingService.ReportParseError("title 1"); + ReportingService.ReportParseError("title 2"); + + MockedRestProvider.Verify(p => p.PostData(It.IsAny(), It.IsAny()), Times.Exactly(2)); + MockedRestProvider.Verify(p => p.PostData(It.IsAny(), It.Is(c => c.Title == "title 1")), Times.Once()); + MockedRestProvider.Verify(p => p.PostData(It.IsAny(), It.Is(c => c.Title == "title 2")), Times.Once()); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Contract/ExceptionReport.cs b/NzbDrone.Common/Contract/ExceptionReport.cs new file mode 100644 index 000000000..86ad06533 --- /dev/null +++ b/NzbDrone.Common/Contract/ExceptionReport.cs @@ -0,0 +1,18 @@ +using System.Linq; +using Newtonsoft.Json; + +namespace NzbDrone.Common.Contract +{ + public class ExceptionReport : ReportBase + { + [JsonProperty("t")] + public string Type { get; set; } + [JsonProperty("l")] + public string Logger { get; set; } + [JsonProperty("lm")] + public string LogMessage { get; set; } + [JsonProperty("s")] + public string String { get; set; } + } + +} diff --git a/NzbDrone.Common/Contract/ParseErrorRequ.cs b/NzbDrone.Common/Contract/ParseErrorRequ.cs new file mode 100644 index 000000000..72b10f314 --- /dev/null +++ b/NzbDrone.Common/Contract/ParseErrorRequ.cs @@ -0,0 +1,11 @@ +using System.Linq; +using Newtonsoft.Json; + +namespace NzbDrone.Common.Contract +{ + public class ParseErrorReport : ReportBase + { + [JsonProperty("t")] + public string Title { get; set; } + } +} diff --git a/NzbDrone.Common/Contract/ReportBase.cs b/NzbDrone.Common/Contract/ReportBase.cs new file mode 100644 index 000000000..3b0c2d3a4 --- /dev/null +++ b/NzbDrone.Common/Contract/ReportBase.cs @@ -0,0 +1,18 @@ +using System; +using System.Linq; +using Newtonsoft.Json; + +namespace NzbDrone.Common.Contract +{ + public abstract class ReportBase + { + [JsonProperty("v")] + public string Version { get; set; } + + [JsonProperty("p")] + public bool IsProduction { get; set; } + + [JsonProperty("u")] + public Guid UGuid { get; set; } + } +} diff --git a/NzbDrone.Common/EnviromentProvider.cs b/NzbDrone.Common/EnviromentProvider.cs index 15b99e8dc..47c9d0146 100644 --- a/NzbDrone.Common/EnviromentProvider.cs +++ b/NzbDrone.Common/EnviromentProvider.cs @@ -40,9 +40,11 @@ namespace NzbDrone.Common } } + public static Guid UGuid { get; set; } + public static bool IsNewInstall { get; set; } - + public virtual bool IsUserInteractive { get { return Environment.UserInteractive; } diff --git a/NzbDrone.Common/LogConfiguration.cs b/NzbDrone.Common/LogConfiguration.cs index a2633af4e..cf9469147 100644 --- a/NzbDrone.Common/LogConfiguration.cs +++ b/NzbDrone.Common/LogConfiguration.cs @@ -3,6 +3,7 @@ using System.IO; using NLog; using NLog.Config; using NLog.Targets; +using NzbDrone.Common.NlogTargets; namespace NzbDrone.Common { @@ -94,7 +95,7 @@ namespace NzbDrone.Common { var fileTarget = GetBaseTarget(); fileTarget.FileName = fileName; - + LogManager.Configuration.AddTarget(Guid.NewGuid().ToString(), fileTarget); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", level, fileTarget)); } @@ -112,7 +113,7 @@ namespace NzbDrone.Common LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", level, fileTarget)); } - public static void RegisterExceptioneer() + public static void RegisterRemote() { if (EnviromentProvider.IsProduction) { @@ -121,14 +122,25 @@ namespace NzbDrone.Common var exTarget = new ExceptioneerTarget(); LogManager.Configuration.AddTarget("Exceptioneer", exTarget); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, exTarget)); - - LogManager.ConfigurationReloaded += (sender, args) => RegisterExceptioneer(); } catch (Exception e) { Console.WriteLine(e); } } + + try + { + var remoteTarget = new RemoteTarget(); + LogManager.Configuration.AddTarget("RemoteTarget", remoteTarget); + LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, remoteTarget)); + } + catch (Exception e) + { + Console.WriteLine(e); + } + + LogManager.ConfigurationReloaded += (sender, args) => RegisterRemote(); } public static void Reload() diff --git a/NzbDrone.Common/ExceptioneerTarget.cs b/NzbDrone.Common/NlogTargets/ExceptioneerTarget.cs similarity index 93% rename from NzbDrone.Common/ExceptioneerTarget.cs rename to NzbDrone.Common/NlogTargets/ExceptioneerTarget.cs index fc31c26d4..8865a0d32 100644 --- a/NzbDrone.Common/ExceptioneerTarget.cs +++ b/NzbDrone.Common/NlogTargets/ExceptioneerTarget.cs @@ -1,10 +1,11 @@ -using System; +using System.Linq; +using System; using System.Diagnostics; using Exceptioneer.WindowsFormsClient; using NLog; using NLog.Targets; -namespace NzbDrone.Common +namespace NzbDrone.Common.NlogTargets { public class ExceptioneerTarget : Target { diff --git a/NzbDrone.Common/NlogTargets/RemoteTarget.cs b/NzbDrone.Common/NlogTargets/RemoteTarget.cs new file mode 100644 index 000000000..30c219bf7 --- /dev/null +++ b/NzbDrone.Common/NlogTargets/RemoteTarget.cs @@ -0,0 +1,21 @@ +using System.Linq; +using System.Diagnostics; +using NLog; +using NLog.Targets; + +namespace NzbDrone.Common.NlogTargets +{ + public class RemoteTarget : Target + { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + protected override void Write(LogEventInfo logEvent) + { + if (logEvent == null || logEvent.Exception == null) return; + + logger.Trace("Sending Exception to Service.NzbDrone.com . Process Name: {0}", Process.GetCurrentProcess().ProcessName); + + ReportingService.ReportException(logEvent); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index fbfdc5348..ec6bc1cf9 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -36,6 +36,9 @@ ..\Libraries\Exceptioneer.WindowsFormsClient.dll + + ..\packages\Newtonsoft.Json.4.0.7\lib\net40\Newtonsoft.Json.dll + ..\packages\Ninject.2.2.1.4\lib\net40-Full\Ninject.dll @@ -52,17 +55,23 @@ + + + + - + + + diff --git a/NzbDrone.Common/Properties/AssemblyInfo.cs b/NzbDrone.Common/Properties/AssemblyInfo.cs index 56062366d..450b0a81e 100644 --- a/NzbDrone.Common/Properties/AssemblyInfo.cs +++ b/NzbDrone.Common/Properties/AssemblyInfo.cs @@ -12,5 +12,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.0.*")] -[assembly: AssemblyFileVersion("0.0.0.*")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.0.*")] +[assembly: AssemblyFileVersion("1.0.0.*")] \ No newline at end of file diff --git a/NzbDrone.Common/ReportingService.cs b/NzbDrone.Common/ReportingService.cs new file mode 100644 index 000000000..afbc3c5ed --- /dev/null +++ b/NzbDrone.Common/ReportingService.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common.Contract; + +namespace NzbDrone.Common +{ + public static class ReportingService + { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + private const string SERVICE_URL = "http://localhost.:1542/reporting"; + private const string PARSE_URL = SERVICE_URL + "/parser"; + private const string EXCEPTION_URL = SERVICE_URL + "/exception"; + + public static RestProvider RestProvider { get; set; } + private static readonly HashSet parserErrorCache = new HashSet(); + + + public static void ClearCache() + { + lock (parserErrorCache) + { + parserErrorCache.Clear(); + } + } + + public static void ReportParseError(string title) + { + try + { + if (RestProvider == null && EnviromentProvider.IsProduction) + return; + + lock (parserErrorCache) + { + if (parserErrorCache.Contains(title.ToLower())) return; + + parserErrorCache.Add(title.ToLower()); + } + + var report = new ParseErrorReport { Title = title }; + RestProvider.PostData(PARSE_URL, report); + } + catch (Exception e) + { + if (!EnviromentProvider.IsProduction) + { + throw; + } + + e.Data.Add("title", title); + logger.ErrorException("Unable to report parse error", e); + } + } + + public static void ReportException(LogEventInfo logEvent) + { + try + { + if (RestProvider == null && EnviromentProvider.IsProduction) + return; + + var report = new ExceptionReport(); + report.LogMessage = logEvent.FormattedMessage; + report.String = logEvent.Exception.ToString(); + report.Logger = logEvent.LoggerName; + report.Type = logEvent.Exception.GetType().Name; + + RestProvider.PostData(EXCEPTION_URL, report); + } + catch (Exception e) + { + if (!EnviromentProvider.IsProduction) + { + throw; + } + + //this shouldn't log an exception since it might cause a recursive loop. + logger.Error("Unable to report exception. " + e); + } + } + } +} diff --git a/NzbDrone.Common/RestProvider.cs b/NzbDrone.Common/RestProvider.cs new file mode 100644 index 000000000..b8b8a93c3 --- /dev/null +++ b/NzbDrone.Common/RestProvider.cs @@ -0,0 +1,67 @@ +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using Newtonsoft.Json; +using Ninject; +using NzbDrone.Common.Contract; + +namespace NzbDrone.Common +{ + + public class RestProvider + { + private readonly EnviromentProvider _enviromentProvider; + + + [Inject] + public RestProvider(EnviromentProvider enviromentProvider) + { + _enviromentProvider = enviromentProvider; + } + + public RestProvider() + { + + } + + private const int TIMEOUT = 10000; + private const string METHOD = "POST"; + + public virtual void PostData(string url, ReportBase reportBase) + { + reportBase.UGuid = EnviromentProvider.UGuid; + reportBase.Version = _enviromentProvider.Version.ToString(); + reportBase.IsProduction = EnviromentProvider.IsProduction; + + PostData(url, reportBase as object); + } + + + private static void PostData(string url, object message) + { + var json = JsonConvert.SerializeObject(message); + + var request = (HttpWebRequest)WebRequest.Create(url); + request.Timeout = TIMEOUT; + + request.Proxy = WebRequest.DefaultWebProxy; + + request.KeepAlive = false; + request.ProtocolVersion = HttpVersion.Version10; + request.Method = METHOD; + request.ContentType = "application/json"; + + byte[] postBytes = Encoding.UTF8.GetBytes(json); + request.ContentLength = postBytes.Length; + + var requestStream = request.GetRequestStream(); + requestStream.Write(postBytes, 0, postBytes.Length); + requestStream.Close(); + + var response = (HttpWebResponse)request.GetResponse(); + var streamreader = new StreamReader(response.GetResponseStream()); + streamreader.Close(); + } + } +} diff --git a/NzbDrone.Common/SecurityProvider.cs b/NzbDrone.Common/SecurityProvider.cs index f1186d817..bbde053cd 100644 --- a/NzbDrone.Common/SecurityProvider.cs +++ b/NzbDrone.Common/SecurityProvider.cs @@ -70,9 +70,7 @@ namespace NzbDrone.Common { try { - var currentIdentity = WindowsIdentity.GetCurrent(); - - var principal = new WindowsPrincipal(currentIdentity); + var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); return principal.IsInRole(WindowsBuiltInRole.Administrator); } catch(Exception ex) diff --git a/NzbDrone.Common/packages.config b/NzbDrone.Common/packages.config index 4f6876419..43f648378 100644 --- a/NzbDrone.Common/packages.config +++ b/NzbDrone.Common/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/NzbDrone.Core.Test/Framework/CoreTest.cs b/NzbDrone.Core.Test/Framework/CoreTest.cs index 15f6c65e1..0aed5ed31 100644 --- a/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -33,6 +33,7 @@ namespace NzbDrone.Core.Test.Framework TestDbHelper.CreateDataBaseTemplate(); } + private IDatabase _db; protected IDatabase Db { diff --git a/NzbDrone.Core.Test/JobTests/AppUpdateJobFixture.cs b/NzbDrone.Core.Test/JobTests/AppUpdateJobFixture.cs index 1f15f2598..d173cbb67 100644 --- a/NzbDrone.Core.Test/JobTests/AppUpdateJobFixture.cs +++ b/NzbDrone.Core.Test/JobTests/AppUpdateJobFixture.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.JobTests { Mocker.GetMock().SetupGet(c => c.SystemTemp).Returns(@"C:\Temp\"); Mocker.GetMock().SetupGet(c => c.Guid).Returns(_clientGuid); - Mocker.GetMock().Setup(c => c.GetAvilableUpdate()).Returns(updatePackage); + Mocker.GetMock().Setup(c => c.GetAvilableUpdate(It.IsAny())).Returns(updatePackage); } @@ -125,7 +125,7 @@ namespace NzbDrone.Core.Test.JobTests [Test] public void when_no_updates_are_available_should_return_without_error_or_warnings() { - Mocker.GetMock().Setup(c => c.GetAvilableUpdate()).Returns((UpdatePackage)null); + Mocker.GetMock().Setup(c => c.GetAvilableUpdate(It.IsAny())).Returns((UpdatePackage)null); StartUpdate(); diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index a8f67f5df..75290390c 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -57,6 +57,10 @@ ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + False + ..\packages\Newtonsoft.Json.4.0.7\lib\net40\Newtonsoft.Json.dll + ..\packages\Ninject.2.2.1.4\lib\net40-Full\Ninject.dll @@ -131,6 +135,7 @@ + diff --git a/NzbDrone.Core.Test/ParserTest.cs b/NzbDrone.Core.Test/ParserTest.cs index ccb9ccc0d..a261d740b 100644 --- a/NzbDrone.Core.Test/ParserTest.cs +++ b/NzbDrone.Core.Test/ParserTest.cs @@ -2,7 +2,10 @@ using System; using System.Linq; using FluentAssertions; +using Moq; using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Common.Contract; using NzbDrone.Core.Model; using NzbDrone.Core.Repository.Quality; using NzbDrone.Core.Test.Framework; @@ -50,7 +53,7 @@ namespace NzbDrone.Core.Test [TestCase("Breaking.In.S01E07.21.0.Jump.Street.720p.WEB-DL.DD5.1.h.264-KiNGS", "Breaking In", 1, 7)] [TestCase("CSI525", "CSI", 5, 25)] [TestCase("King of the Hill - 10x12 - 24 Hour Propane People [SDTV]", "King of the Hill", 10, 12)] - [TestCase("Brew Masters S01E06 3 Beers For Batali DVDRip XviD SPRiNTER","Brew Masters", 1, 6)] + [TestCase("Brew Masters S01E06 3 Beers For Batali DVDRip XviD SPRiNTER", "Brew Masters", 1, 6)] [TestCase("24 7 Flyers Rangers Road to the NHL Winter Classic Part01 720p HDTV x264 ORENJI", "24 7 Flyers Rangers Road to the NHL Winter Classic", 1, 1)] [TestCase("24 7 Flyers Rangers Road to the NHL Winter Classic Part 02 720p HDTV x264 ORENJI", "24 7 Flyers Rangers Road to the NHL Winter Classic", 1, 2)] [TestCase("24-7 Flyers-Rangers- Road to the NHL Winter Classic - S01E01 - Part 1", "24 7 Flyers Rangers Road to the NHL Winter Classic", 1, 1)] @@ -93,16 +96,24 @@ namespace NzbDrone.Core.Test } [Test] - public void unparsable_path() + public void unparsable_path_should_report_the_path() { Parser.ParsePath("C:\\").Should().BeNull(); + + MockedRestProvider.Verify(c => c.PostData(It.IsAny(), It.IsAny()), Times.Exactly(2)); + ExceptionVerification.IgnoreWarns(); } [Test] - public void unparsable_title() + public void unparsable_title_should_report_title() { - Parser.ParseTitle("SOMETHING").Should().BeNull(); + const string TITLE = "SOMETHING"; + + Parser.ParseTitle(TITLE).Should().BeNull(); + + MockedRestProvider.Verify(c => c.PostData(It.IsAny(), It.Is(r => r.Title == TITLE)), Times.Once()); + ExceptionVerification.IgnoreWarns(); } diff --git a/NzbDrone.Core.Test/ProviderTests/ConfigProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/ConfigProviderTest.cs index 92d2dc781..480b7d873 100644 --- a/NzbDrone.Core.Test/ProviderTests/ConfigProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/ConfigProviderTest.cs @@ -131,8 +131,7 @@ namespace NzbDrone.Core.Test.ProviderTests public void uguid_should_return_valid_result_on_first_call() { var guid = Mocker.Resolve().UGuid; - guid.Should().NotBeBlank(); - Guid.Parse(guid).ToString().Should().NotBe(new Guid().ToString()); + guid.Should().NotBeEmpty(); } diff --git a/NzbDrone.Core.Test/ProviderTests/UpdateProviderTests/GetAvilableUpdateFixture.cs b/NzbDrone.Core.Test/ProviderTests/UpdateProviderTests/GetAvilableUpdateFixture.cs index 70ae88a02..f4cd2c542 100644 --- a/NzbDrone.Core.Test/ProviderTests/UpdateProviderTests/GetAvilableUpdateFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/UpdateProviderTests/GetAvilableUpdateFixture.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.ProviderTests.UpdateProviderTests private static string _latestsTestFileName = "NzbDrone.master.0.6.0.3.zip"; [SetUp] - public void setup() + public void Setup() { WithStrictMocker(); @@ -31,9 +31,8 @@ namespace NzbDrone.Core.Test.ProviderTests.UpdateProviderTests [TestCase("1.0.0.0")] public void should_return_null_if_latests_is_lower_than_current_version(string currentVersion) { - Mocker.GetMock().SetupGet(c => c.Version).Returns(new Version(currentVersion)); - var updatePackage = Mocker.Resolve().GetAvilableUpdate(); + var updatePackage = Mocker.Resolve().GetAvilableUpdate(new Version(currentVersion)); updatePackage.Should().BeNull(); } @@ -41,9 +40,7 @@ namespace NzbDrone.Core.Test.ProviderTests.UpdateProviderTests [Test] public void should_return_null_if_latests_is_equal_to_current_version() { - Mocker.GetMock().SetupGet(c => c.Version).Returns(_latestsTestVersion); - - var updatePackage = Mocker.Resolve().GetAvilableUpdate(); + var updatePackage = Mocker.Resolve().GetAvilableUpdate(_latestsTestVersion); updatePackage.Should().BeNull(); } @@ -53,9 +50,7 @@ namespace NzbDrone.Core.Test.ProviderTests.UpdateProviderTests [TestCase("0.0.10.10")] public void should_return_update_if_latests_is_higher_than_current_version(string currentVersion) { - Mocker.GetMock().SetupGet(c => c.Version).Returns(new Version(currentVersion)); - - var updatePackage = Mocker.Resolve().GetAvilableUpdate(); + var updatePackage = Mocker.Resolve().GetAvilableUpdate(new Version(currentVersion)); updatePackage.Should().NotBeNull(); updatePackage.Version.Should().Be(_latestsTestVersion); diff --git a/NzbDrone.Core.Test/Services/ParseErrorServiceFixture.cs b/NzbDrone.Core.Test/Services/ParseErrorServiceFixture.cs new file mode 100644 index 000000000..f246852a8 --- /dev/null +++ b/NzbDrone.Core.Test/Services/ParseErrorServiceFixture.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Services +{ + [TestFixture] + public class ParseErrorServiceFixture : CoreTest + { + + public ParseErrorServiceFixture() + { + AppDomain.CurrentDomain.AssemblyResolve += + new ResolveEventHandler(CurrentDomain_AssemblyResolve); + } + + + Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + var name = new AssemblyName(args.Name); + if (name.Name == "Newtonsoft.Json") + { + return typeof(Newtonsoft.Json.JsonSerializer).Assembly; + } + return null; + } + } +} diff --git a/NzbDrone.Core.Test/packages.config b/NzbDrone.Core.Test/packages.config index 59023fa65..ac9d7b5e8 100644 --- a/NzbDrone.Core.Test/packages.config +++ b/NzbDrone.Core.Test/packages.config @@ -7,6 +7,7 @@ + diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 98ae058e3..6c1263d14 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Core Kernel = new StandardKernel(); InitDatabase(); - InitAnalytics(); + InitReporting(); InitQuality(); InitExternalNotifications(); @@ -58,13 +58,17 @@ namespace NzbDrone.Core LogConfiguration.Reload(); } - private void InitAnalytics() + private void InitReporting() { + EnviromentProvider.UGuid = Kernel.Get().UGuid; + ReportingService.RestProvider = Kernel.Get(); + var appId = AnalyticsProvider.DESKMETRICS_TEST_ID; + if (EnviromentProvider.IsProduction) appId = AnalyticsProvider.DESKMETRICS_PRODUCTION_ID; - var deskMetricsClient = new DeskMetricsClient(Kernel.Get().UGuid, appId, _enviromentProvider.Version); + var deskMetricsClient = new DeskMetricsClient(Kernel.Get().UGuid.ToString(), appId, _enviromentProvider.Version); Kernel.Bind().ToConstant(deskMetricsClient); Kernel.Get().Checkpoint(); } diff --git a/NzbDrone.Core/Datastore/MigrationLogger.cs b/NzbDrone.Core/Datastore/MigrationLogger.cs index 84ceb5fd3..fc008377f 100644 --- a/NzbDrone.Core/Datastore/MigrationLogger.cs +++ b/NzbDrone.Core/Datastore/MigrationLogger.cs @@ -5,7 +5,7 @@ using NLog; namespace NzbDrone.Core.Datastore { - class MigrationLogger : ILogger + public class MigrationLogger : ILogger { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/NzbDrone.Core/Datastore/Migrations/SchemaInfo.cs b/NzbDrone.Core/Datastore/Migrations/SchemaInfo.cs new file mode 100644 index 000000000..de23255ba --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/SchemaInfo.cs @@ -0,0 +1,9 @@ +using System.Linq; + +namespace NzbDrone.Core.Datastore.Migrations +{ + public class SchemaInfo + { + public int Version { get; set; } + } +} diff --git a/NzbDrone.Core/Jobs/AppUpdateJob.cs b/NzbDrone.Core/Jobs/AppUpdateJob.cs index 48cf5da3e..108efda95 100644 --- a/NzbDrone.Core/Jobs/AppUpdateJob.cs +++ b/NzbDrone.Core/Jobs/AppUpdateJob.cs @@ -47,10 +47,10 @@ namespace NzbDrone.Core.Jobs } public virtual void Start(ProgressNotification notification, int targetId, int secondaryTargetId) - { + { notification.CurrentMessage = "Checking for updates"; - var updatePackage = _updateProvider.GetAvilableUpdate(); + var updatePackage = _updateProvider.GetAvilableUpdate(_enviromentProvider.Version); //No updates available if (updatePackage == null) diff --git a/NzbDrone.Core/Model/EpisodeParseResult.cs b/NzbDrone.Core/Model/EpisodeParseResult.cs index 820f5bb98..79a045125 100644 --- a/NzbDrone.Core/Model/EpisodeParseResult.cs +++ b/NzbDrone.Core/Model/EpisodeParseResult.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using NzbDrone.Core.Repository; @@ -47,11 +48,11 @@ namespace NzbDrone.Core.Model if (FullSeason) return string.Format("{0} - Season {1:00}", SeriesTitle, SeasonNumber); - if (EpisodeNumbers != null) + if (EpisodeNumbers != null && EpisodeNumbers.Any()) return string.Format("{0} - S{1:00}E{2} {3}", SeriesTitle, SeasonNumber, - String.Join("-", EpisodeNumbers), Quality); + String.Join("-", EpisodeNumbers.Select(c => c.ToString("00"))), Quality); - return OriginalString; + return "[Invalid format]"; } } diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 796fbbb2c..7d1525891 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -195,7 +195,6 @@ ..\Libraries\TvdbLib.dll - False ..\packages\twitterizer.2.4.0.26532\lib\net40\Twitterizer2.dll @@ -222,6 +221,7 @@ + diff --git a/NzbDrone.Core/Parser.cs b/NzbDrone.Core/Parser.cs index 6bcfa1c5e..763459755 100644 --- a/NzbDrone.Core/Parser.cs +++ b/NzbDrone.Core/Parser.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using NLog; +using NzbDrone.Common; using NzbDrone.Core.Model; using NzbDrone.Core.Repository.Quality; @@ -113,12 +114,13 @@ namespace NzbDrone.Core } } } - Logger.Warn("Unable to parse episode info. {0}", title); } catch (Exception e) { Logger.ErrorException("An error has occurred while trying to parse " + title, e); } + Logger.Warn("Unable to parse episode info. {0}", title); + ReportingService.ReportParseError(title); return null; } @@ -399,12 +401,6 @@ namespace NzbDrone.Core return LanguageType.English; } - /// - /// Normalizes the title. removing all non-word characters as well as common tokens - /// such as 'the' and 'and' - /// - /// title - /// public static string NormalizeTitle(string title) { long number = 0; diff --git a/NzbDrone.Core/Properties/AssemblyInfo.cs b/NzbDrone.Core/Properties/AssemblyInfo.cs index 0647faa1b..9c41cc91f 100644 --- a/NzbDrone.Core/Properties/AssemblyInfo.cs +++ b/NzbDrone.Core/Properties/AssemblyInfo.cs @@ -16,5 +16,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.0.*")] -[assembly: AssemblyFileVersion("0.0.0.*")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.0.*")] +[assembly: AssemblyFileVersion("1.0.0.*")] \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Core/ConfigProvider.cs b/NzbDrone.Core/Providers/Core/ConfigProvider.cs index 1718037e3..3ab30dc0a 100644 --- a/NzbDrone.Core/Providers/Core/ConfigProvider.cs +++ b/NzbDrone.Core/Providers/Core/ConfigProvider.cs @@ -409,6 +409,11 @@ namespace NzbDrone.Core.Providers.Core set { SetValue("AutoIgnorePreviouslyDownloadedEpisodes", value); } } + public Guid UGuid + { + get { return Guid.Parse(GetValue("UGuid", Guid.NewGuid().ToString(), persist: true)); } + } + public virtual DownloadClientType DownloadClient { get { return (DownloadClientType)GetValueInt("DownloadClient"); } @@ -498,4 +503,4 @@ namespace NzbDrone.Core.Providers.Core } } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core/Providers/Core/HttpProvider.cs b/NzbDrone.Core/Providers/Core/HttpProvider.cs index c3856f354..46bfc1a26 100644 --- a/NzbDrone.Core/Providers/Core/HttpProvider.cs +++ b/NzbDrone.Core/Providers/Core/HttpProvider.cs @@ -113,5 +113,40 @@ namespace NzbDrone.Core.Providers.Core return responseFromServer.Replace(" ", " "); } + + public virtual string Post(string address, string command, string username, string password) + { + Logger.Trace("Posting command: {0}, to {1}", command, address); + + byte[] byteArray = Encoding.ASCII.GetBytes(command); + + var request = (HttpWebRequest)WebRequest.Create(address); + request.Method = "POST"; + request.Credentials = new NetworkCredential(username, password); + request.ContentType = "application/json"; + request.Timeout = 2000; + request.KeepAlive = false; + + //Used to hold the JSON response + string responseFromServer; + + using (var requestStream = request.GetRequestStream()) + { + requestStream.Write(byteArray, 0, byteArray.Length); + + using (var response = request.GetResponse()) + { + using (var responseStream = response.GetResponseStream()) + { + using (var reader = new StreamReader(responseStream)) + { + responseFromServer = reader.ReadToEnd(); + } + } + } + } + + return responseFromServer.Replace(" ", " "); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs index 615433493..9265dbb3e 100644 --- a/NzbDrone.Core/Providers/DiskScanProvider.cs +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -6,9 +6,7 @@ using Ninject; using NLog; using NzbDrone.Common; using NzbDrone.Core.Model; -using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Repository; -using PetaPoco; namespace NzbDrone.Core.Providers { diff --git a/NzbDrone.Core/Providers/Indexer/Newzbin.cs b/NzbDrone.Core/Providers/Indexer/Newzbin.cs index f925fc4e9..6a5f6089b 100644 --- a/NzbDrone.Core/Providers/Indexer/Newzbin.cs +++ b/NzbDrone.Core/Providers/Indexer/Newzbin.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Providers.Indexer { return new[] { - "https://www.newzbin.com/browse/category/p/tv?" + URL_PARAMS + "https://www.newzbin2.es/browse/category/p/tv?" + URL_PARAMS }; } } @@ -50,7 +50,7 @@ namespace NzbDrone.Core.Providers.Indexer return new List { String.Format( - @"http://www.newzbin.com/search/query/?q={0}+{1}x{2:00}&fpn=p&searchaction=Go&category=8&{3}", + @"https://www.newzbin.es/search/query/?q={0}+{1}x{2:00}&fpn=p&searchaction=Go&category=8&{3}", seriesTitle, seasonNumber,episodeNumber, URL_PARAMS) }; } @@ -60,7 +60,7 @@ namespace NzbDrone.Core.Providers.Indexer return new List { String.Format( - @"http://www.newzbin.com/search/query/?q={0}+Season+{1}&fpn=p&searchaction=Go&category=8&{2}", + @"https://www.newzbin.es/search/query/?q={0}+Season+{1}&fpn=p&searchaction=Go&category=8&{2}", seriesTitle, seasonNumber, URL_PARAMS) }; } @@ -70,7 +70,7 @@ namespace NzbDrone.Core.Providers.Indexer return new List { String.Format( - @"http://www.newzbin.com/search/query/?q={0}+{1:yyyy-MM-dd}&fpn=p&searchaction=Go&category=8&{2}", + @"https://www.newzbin.es/search/query/?q={0}+{1:yyyy-MM-dd}&fpn=p&searchaction=Go&category=8&{2}", seriesTitle, date, URL_PARAMS) }; } @@ -80,7 +80,7 @@ namespace NzbDrone.Core.Providers.Indexer return new List { String.Format( - @"http://www.newzbin.com/search/query/?q={0}+{1}x{2}&fpn=p&searchaction=Go&category=8&{3}", + @"https://www.newzbin.es/search/query/?q={0}+{1}x{2}&fpn=p&searchaction=Go&category=8&{3}", seriesTitle, seasonNumber, episodeWildcard, URL_PARAMS) }; } diff --git a/NzbDrone.Core/Providers/UpdateProvider.cs b/NzbDrone.Core/Providers/UpdateProvider.cs index 70b425bdd..224420c3a 100644 --- a/NzbDrone.Core/Providers/UpdateProvider.cs +++ b/NzbDrone.Core/Providers/UpdateProvider.cs @@ -61,13 +61,13 @@ namespace NzbDrone.Core.Providers return updateList; } - public virtual UpdatePackage GetAvilableUpdate() + public virtual UpdatePackage GetAvilableUpdate(Version currentVersion) { var latestAvailable = GetAvailablePackages().OrderByDescending(c => c.Version).FirstOrDefault(); - if (latestAvailable != null && latestAvailable.Version > _enviromentProvider.Version) + if (latestAvailable != null && latestAvailable.Version > currentVersion) { - logger.Debug("An update is available ({0}) => ({1})", _enviromentProvider.Version, latestAvailable.Version); + logger.Debug("An update is available ({0}) => ({1})", currentVersion, latestAvailable.Version); return latestAvailable; } diff --git a/NzbDrone.Services/NzbDrone.Services.Service/App_Readme/Elmah.SqlServer.sql b/NzbDrone.Services/NzbDrone.Services.Service/App_Readme/Elmah.SqlServer.sql new file mode 100644 index 000000000..ce0575351 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/App_Readme/Elmah.SqlServer.sql @@ -0,0 +1,299 @@ +/* + + ELMAH - Error Logging Modules and Handlers for ASP.NET + Copyright (c) 2004-9 Atif Aziz. All rights reserved. + + Author(s): + + Atif Aziz, http://www.raboof.com + Phil Haacked, http://haacked.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +-- ELMAH DDL script for Microsoft SQL Server 2000 or later. + +-- $Id: SQLServer.sql 677 2009-09-29 18:02:39Z azizatif $ + +DECLARE @DBCompatibilityLevel INT +DECLARE @DBCompatibilityLevelMajor INT +DECLARE @DBCompatibilityLevelMinor INT + +SELECT + @DBCompatibilityLevel = cmptlevel +FROM + master.dbo.sysdatabases +WHERE + name = DB_NAME() + +IF @DBCompatibilityLevel <> 80 +BEGIN + + SELECT @DBCompatibilityLevelMajor = @DBCompatibilityLevel / 10, + @DBCompatibilityLevelMinor = @DBCompatibilityLevel % 10 + + PRINT N' + =========================================================================== + WARNING! + --------------------------------------------------------------------------- + + This script is designed for Microsoft SQL Server 2000 (8.0) but your + database is set up for compatibility with version ' + + CAST(@DBCompatibilityLevelMajor AS NVARCHAR(80)) + + N'.' + + CAST(@DBCompatibilityLevelMinor AS NVARCHAR(80)) + + N'. Although + the script should work with later versions of Microsoft SQL Server, + you can ensure compatibility by executing the following statement: + + ALTER DATABASE [' + + DB_NAME() + + N'] + SET COMPATIBILITY_LEVEL = 80 + + If you are hosting ELMAH in the same database as your application + database and do not wish to change the compatibility option then you + should create a separate database to host ELMAH where you can set the + compatibility level more freely. + + If you continue with the current setup, please report any compatibility + issues you encounter over at: + + http://code.google.com/p/elmah/issues/list + + =========================================================================== +' +END +GO + +/* ------------------------------------------------------------------------ + TABLES + ------------------------------------------------------------------------ */ + +CREATE TABLE [dbo].[ELMAH_Error] +( + [ErrorId] UNIQUEIDENTIFIER NOT NULL, + [Application] NVARCHAR(60) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [Host] NVARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [Type] NVARCHAR(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [Source] NVARCHAR(60) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [Message] NVARCHAR(500) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [User] NVARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [StatusCode] INT NOT NULL, + [TimeUtc] DATETIME NOT NULL, + [Sequence] INT IDENTITY (1, 1) NOT NULL, + [AllXml] NTEXT COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL +) +ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] + +GO + +ALTER TABLE [dbo].[ELMAH_Error] WITH NOCHECK ADD + CONSTRAINT [PK_ELMAH_Error] PRIMARY KEY NONCLUSTERED ([ErrorId]) ON [PRIMARY] +GO + +ALTER TABLE [dbo].[ELMAH_Error] ADD + CONSTRAINT [DF_ELMAH_Error_ErrorId] DEFAULT (NEWID()) FOR [ErrorId] +GO + +CREATE NONCLUSTERED INDEX [IX_ELMAH_Error_App_Time_Seq] ON [dbo].[ELMAH_Error] +( + [Application] ASC, + [TimeUtc] DESC, + [Sequence] DESC +) +ON [PRIMARY] +GO + +/* ------------------------------------------------------------------------ + STORED PROCEDURES + ------------------------------------------------------------------------ */ + +SET QUOTED_IDENTIFIER ON +GO +SET ANSI_NULLS ON +GO + +CREATE PROCEDURE [dbo].[ELMAH_GetErrorXml] +( + @Application NVARCHAR(60), + @ErrorId UNIQUEIDENTIFIER +) +AS + + SET NOCOUNT ON + + SELECT + [AllXml] + FROM + [ELMAH_Error] + WHERE + [ErrorId] = @ErrorId + AND + [Application] = @Application + +GO +SET QUOTED_IDENTIFIER OFF +GO +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO +SET ANSI_NULLS ON +GO + +CREATE PROCEDURE [dbo].[ELMAH_GetErrorsXml] +( + @Application NVARCHAR(60), + @PageIndex INT = 0, + @PageSize INT = 15, + @TotalCount INT OUTPUT +) +AS + + SET NOCOUNT ON + + DECLARE @FirstTimeUTC DATETIME + DECLARE @FirstSequence INT + DECLARE @StartRow INT + DECLARE @StartRowIndex INT + + SELECT + @TotalCount = COUNT(1) + FROM + [ELMAH_Error] + WHERE + [Application] = @Application + + -- Get the ID of the first error for the requested page + + SET @StartRowIndex = @PageIndex * @PageSize + 1 + + IF @StartRowIndex <= @TotalCount + BEGIN + + SET ROWCOUNT @StartRowIndex + + SELECT + @FirstTimeUTC = [TimeUtc], + @FirstSequence = [Sequence] + FROM + [ELMAH_Error] + WHERE + [Application] = @Application + ORDER BY + [TimeUtc] DESC, + [Sequence] DESC + + END + ELSE + BEGIN + + SET @PageSize = 0 + + END + + -- Now set the row count to the requested page size and get + -- all records below it for the pertaining application. + + SET ROWCOUNT @PageSize + + SELECT + errorId = [ErrorId], + application = [Application], + host = [Host], + type = [Type], + source = [Source], + message = [Message], + [user] = [User], + statusCode = [StatusCode], + time = CONVERT(VARCHAR(50), [TimeUtc], 126) + 'Z' + FROM + [ELMAH_Error] error + WHERE + [Application] = @Application + AND + [TimeUtc] <= @FirstTimeUTC + AND + [Sequence] <= @FirstSequence + ORDER BY + [TimeUtc] DESC, + [Sequence] DESC + FOR + XML AUTO + +GO +SET QUOTED_IDENTIFIER OFF +GO +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO +SET ANSI_NULLS ON +GO + +CREATE PROCEDURE [dbo].[ELMAH_LogError] +( + @ErrorId UNIQUEIDENTIFIER, + @Application NVARCHAR(60), + @Host NVARCHAR(30), + @Type NVARCHAR(100), + @Source NVARCHAR(60), + @Message NVARCHAR(500), + @User NVARCHAR(50), + @AllXml NTEXT, + @StatusCode INT, + @TimeUtc DATETIME +) +AS + + SET NOCOUNT ON + + INSERT + INTO + [ELMAH_Error] + ( + [ErrorId], + [Application], + [Host], + [Type], + [Source], + [Message], + [User], + [AllXml], + [StatusCode], + [TimeUtc] + ) + VALUES + ( + @ErrorId, + @Application, + @Host, + @Type, + @Source, + @Message, + @User, + @AllXml, + @StatusCode, + @TimeUtc + ) + +GO +SET QUOTED_IDENTIFIER OFF +GO +SET ANSI_NULLS ON +GO + diff --git a/NzbDrone.Services/NzbDrone.Services.Service/App_Readme/Elmah.SqlServer.txt b/NzbDrone.Services/NzbDrone.Services.Service/App_Readme/Elmah.SqlServer.txt new file mode 100644 index 000000000..c2ff0a457 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/App_Readme/Elmah.SqlServer.txt @@ -0,0 +1,4 @@ +Please note that in order to complete the installation of ELMAH.SqlServer you will have to do the following: + +1) Run the Elmah.SqlServer.sql script against your database +2) Edit your web.config with the correct settings in the elmah to connect to your database diff --git a/NzbDrone.Services/NzbDrone.Services.Service/App_Start/NinjectMVC3.cs b/NzbDrone.Services/NzbDrone.Services.Service/App_Start/NinjectMVC3.cs new file mode 100644 index 000000000..0b67ace74 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/App_Start/NinjectMVC3.cs @@ -0,0 +1,43 @@ +using NzbDrone.Services.Service.Datastore; +using PetaPoco; +using Microsoft.Web.Infrastructure.DynamicModuleHelper; +using Ninject; +using Ninject.Web.Mvc; + +[assembly: WebActivator.PreApplicationStartMethod(typeof(NzbDrone.Services.Service.App_Start.NinjectMVC3), "Start")] +[assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(NzbDrone.Services.Service.App_Start.NinjectMVC3), "Stop")] + +namespace NzbDrone.Services.Service.App_Start +{ + + public static class NinjectMVC3 + { + private static readonly Bootstrapper bootstrapper = new Bootstrapper(); + + public static void Start() + { + DynamicModuleUtility.RegisterModule(typeof(OnePerRequestModule)); + DynamicModuleUtility.RegisterModule(typeof(HttpApplicationInitializationModule)); + bootstrapper.Initialize(CreateKernel); + } + + public static void Stop() + { + bootstrapper.ShutDown(); + } + + private static IKernel CreateKernel() + { + var kernel = new StandardKernel(); + InitDb(kernel); + return kernel; + } + + + private static void InitDb(IKernel kernel) + { + var db = Connection.GetPetaPocoDb(); + kernel.Bind().ToConstant(db); + } + } +} diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Controllers/DailySeriesController.cs b/NzbDrone.Services/NzbDrone.Services.Service/Controllers/DailySeriesController.cs new file mode 100644 index 000000000..d0dc5cb5a --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Controllers/DailySeriesController.cs @@ -0,0 +1,43 @@ +using System.Linq; +using System.Web.Mvc; +using NzbDrone.Services.Service.Providers; + +namespace NzbDrone.Services.Service.Controllers +{ + public class DailySeriesController : Controller + { + private readonly DailySeriesProvider _dailySeriesProvider; + + public DailySeriesController(DailySeriesProvider dailySeriesProvider) + { + _dailySeriesProvider = dailySeriesProvider; + } + + [HttpGet] + [OutputCache(CacheProfile = "Cache1Hour")] + public JsonResult All() + { + var all = _dailySeriesProvider.All(); + + return Json(all, JsonRequestBehavior.AllowGet); + } + + [HttpGet] + [OutputCache(CacheProfile = "Cache1Hour")] + public JsonResult AllIds() + { + var all = _dailySeriesProvider.AllSeriesIds(); + + return Json(all, JsonRequestBehavior.AllowGet); + } + + [HttpGet] + [OutputCache(CacheProfile = "Cache1HourVaryBySeriesId")] + public JsonResult Check(int seriesId) + { + var result = _dailySeriesProvider.IsDaily(seriesId); + + return Json(result, JsonRequestBehavior.AllowGet); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Controllers/HealthController.cs b/NzbDrone.Services/NzbDrone.Services.Service/Controllers/HealthController.cs new file mode 100644 index 000000000..1e1e24523 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Controllers/HealthController.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using System.Web.Mvc; +using NzbDrone.Common; +using NzbDrone.Core.Datastore.Migrations; +using PetaPoco; + +namespace NzbDrone.Services.Service.Controllers +{ + public class HealthController : Controller + { + private readonly EnviromentProvider _enviromentProvider; + private readonly IDatabase _database; + + public HealthController(EnviromentProvider enviromentProvider, IDatabase database) + { + _enviromentProvider = enviromentProvider; + _database = database; + } + + [HttpGet] + public JsonResult Echo() + { + var stat = new + { + Service = _enviromentProvider.Version.ToString(), + Schema = _database.Fetch().OrderByDescending(c => c.Version).First() + }; + + return Json(stat, JsonRequestBehavior.AllowGet); + } + + [HttpGet] + public JsonResult Exception() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Controllers/ReportingController.cs b/NzbDrone.Services/NzbDrone.Services.Service/Controllers/ReportingController.cs new file mode 100644 index 000000000..1042501ab --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Controllers/ReportingController.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Web.Mvc; +using NzbDrone.Common.Contract; +using NzbDrone.Services.Service.Repository.Reporting; +using PetaPoco; + + +namespace NzbDrone.Services.Service.Controllers +{ + public class ReportingController : Controller + { + private readonly IDatabase _database; + + private const string OK = "OK"; + + public ReportingController(IDatabase database) + { + _database = database; + } + + [HttpPost] + public JsonResult ParseError(ParseErrorReport parseErrorReport) + { + if (ParseErrorExists(parseErrorReport.Title)) + return Json(OK); + + var row = new ParseErrorRow(); + row.LoadBase(parseErrorReport); + row.Title = parseErrorReport.Title; + + _database.Insert(row); + + return Json(OK); + } + + + private bool ParseErrorExists(string title) + { + return _database.Exists("WHERE Title = @0", title); + } + + [HttpPost] + public JsonResult ReportException(ExceptionReport exceptionReport) + { + var row = new ExceptionRow(); + row.LoadBase(exceptionReport); + row.LogMessage = exceptionReport.LogMessage; + row.Logger = exceptionReport.Logger; + row.String = exceptionReport.String; + row.Type = exceptionReport.Type; + + _database.Insert(row); + + return Json(OK); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Controllers/SceneMappingController.cs b/NzbDrone.Services/NzbDrone.Services.Service/Controllers/SceneMappingController.cs new file mode 100644 index 000000000..3b2ab0174 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Controllers/SceneMappingController.cs @@ -0,0 +1,33 @@ +using System.Linq; +using System.Web.Mvc; +using NzbDrone.Services.Service.Providers; + +namespace NzbDrone.Services.Service.Controllers +{ + public class SceneMappingController : Controller + { + private readonly SceneMappingProvider _sceneMappingProvider; + + public SceneMappingController(SceneMappingProvider sceneMappingProvider) + { + _sceneMappingProvider = sceneMappingProvider; + } + + [HttpGet] + [OutputCache(CacheProfile = "Cache1Hour")] + public JsonResult Active() + { + var mappings = _sceneMappingProvider.AllLive(); + + return Json(mappings, JsonRequestBehavior.AllowGet); + } + + [HttpGet] + public JsonResult Pending() + { + var mappings = _sceneMappingProvider.AllPending(); + + return Json(mappings, JsonRequestBehavior.AllowGet); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Datastore/Connection.cs b/NzbDrone.Services/NzbDrone.Services.Service/Datastore/Connection.cs new file mode 100644 index 000000000..9a00fc77e --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Datastore/Connection.cs @@ -0,0 +1,29 @@ +using System.Configuration; +using System.Linq; +using NzbDrone.Services.Service.Migrations; +using PetaPoco; + +namespace NzbDrone.Services.Service.Datastore +{ + public static class Connection + { + public static string GetConnectionString + { + get { return ConfigurationManager.ConnectionStrings["SqlExpress"].ConnectionString; } + } + + public static IDatabase GetPetaPocoDb() + { + + MigrationsHelper.Run(GetConnectionString); + + var db = new Database("SqlExpress") + { + KeepConnectionAlive = true, + ForceDateTimesToUtc = false, + }; + + return db; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Global.asax b/NzbDrone.Services/NzbDrone.Services.Service/Global.asax new file mode 100644 index 000000000..6db8843ba --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="NzbDrone.Services.Service.MvcApplication" Language="C#" %> diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Global.asax.cs b/NzbDrone.Services/NzbDrone.Services.Service/Global.asax.cs new file mode 100644 index 000000000..3c2632fad --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Global.asax.cs @@ -0,0 +1,71 @@ +using System; +using System.Data.Common; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using NLog; + +namespace NzbDrone.Services.Service +{ + public class MvcApplication : HttpApplication + { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + private static void RegisterRoutes(RouteCollection routes) + { + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + + routes.MapRoute( + "Default", // Route name + "{controller}/{action}", // URL with parameters + new { controller = "Health", action = "Echo" } // Parameter default + ); + + } + + private static void RegisterGlobalFilters(GlobalFilterCollection filters) + { + filters.Add(new HandleErrorAttribute()); + } + + protected void Application_Start() + { + AreaRegistration.RegisterAllAreas(); + + RegisterGlobalFilters(GlobalFilters.Filters); + RegisterRoutes(RouteTable.Routes); + + ViewEngines.Engines.Clear(); + + ModelBinders.Binders.DefaultBinder = new JsonModelBinder(); + } + + // ReSharper disable InconsistentNaming + protected void Application_Error(object sender, EventArgs e) + { + var lastError = Server.GetLastError(); + + if (lastError is HttpException && lastError.InnerException == null) + { + logger.WarnException(String.Format("{0}. URL[{1}]", lastError.Message, Request.Path), lastError); + return; + } + + logger.FatalException(lastError.Message + Environment.NewLine + Request.Url.PathAndQuery, lastError); + + if (lastError is DbException) + { + logger.Warn("Restarting application"); + HttpRuntime.UnloadAppDomain(); + } + } + + protected void Application_BeginRequest() + { + } + + protected void Application_EndRequest() + { + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/JsonModelBinder.cs b/NzbDrone.Services/NzbDrone.Services.Service/JsonModelBinder.cs new file mode 100644 index 000000000..ac9fd6a1d --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/JsonModelBinder.cs @@ -0,0 +1,40 @@ +using System.IO; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using Newtonsoft.Json; + +namespace NzbDrone.Services.Service +{ + public class JsonModelBinder : DefaultModelBinder + { + private static readonly JsonSerializer serializer = new JsonSerializer(); + + public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) + { + var request = controllerContext.HttpContext.Request; + + if (!IsJsonRequest(request)) + { + return base.BindModel(controllerContext, bindingContext); + } + + object deserializedObject; + using (var stream = request.InputStream) + { + stream.Seek(0, SeekOrigin.Begin); + using (var reader = new StreamReader(stream)) + { + deserializedObject = serializer.Deserialize(reader, bindingContext.ModelMetadata.ModelType); + } + } + + return deserializedObject; + } + + private static bool IsJsonRequest(HttpRequestBase request) + { + return request.ContentType.ToLower().Contains("application/json"); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Migrations/Migration20120201.cs b/NzbDrone.Services/NzbDrone.Services.Service/Migrations/Migration20120201.cs new file mode 100644 index 000000000..e633c3573 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Migrations/Migration20120201.cs @@ -0,0 +1,50 @@ +using System; +using System.Data; +using Migrator.Framework; + +namespace NzbDrone.Services.Service.Migrations +{ + + [Migration(20120201)] + public class Migration20120201 : Migration + { + public override void Up() + { + + Database.AddTable("SceneMappings", new[] + { + new Column("CleanTitle", DbType.String, ColumnProperty.PrimaryKey), + new Column("Id", DbType.Int32, ColumnProperty.NotNull), + new Column("Title", DbType.String, ColumnProperty.NotNull) + }); + + Database.AddTable("PendingSceneMappings", new[] + { + new Column("CleanTitle", DbType.String, ColumnProperty.PrimaryKey), + new Column("Id", DbType.Int32, ColumnProperty.NotNull), + new Column("Title", DbType.String, ColumnProperty.NotNull) + }); + + Database.AddTable("DailySeries", new[] + { + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKey), + new Column("Title", DbType.String, ColumnProperty.NotNull) + }); + + Database.AddTable("ParseErrorReports", new[] + { + new Column("Title", DbType.String,1000, ColumnProperty.PrimaryKey), + MigrationsHelper.UGuidColumn, + MigrationsHelper.TimestampColumn, + MigrationsHelper.VersionColumn, + MigrationsHelper.ProductionColumn + }); + } + + + public override void Down() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Migrations/Migration20120203.cs b/NzbDrone.Services/NzbDrone.Services.Service/Migrations/Migration20120203.cs new file mode 100644 index 000000000..57ced15f5 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Migrations/Migration20120203.cs @@ -0,0 +1,31 @@ +using System; +using System.Data; +using Migrator.Framework; + +namespace NzbDrone.Services.Service.Migrations +{ + + [Migration(20120203)] + public class Migration20120203 : Migration + { + public override void Up() + { + Database.AddTable("ExceptionReports", new[] + { + new Column("Type", DbType.String, ColumnProperty.PrimaryKey), + new Column("Logger", DbType.String, ColumnProperty.PrimaryKey), + new Column("LogMessage", DbType.String, ColumnProperty.PrimaryKey), + new Column("String", DbType.String, 4000, ColumnProperty.PrimaryKey), + MigrationsHelper.UGuidColumn, + MigrationsHelper.TimestampColumn, + MigrationsHelper.VersionColumn, + MigrationsHelper.ProductionColumn + }); + } + + public override void Down() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Migrations/MigrationHelper.cs b/NzbDrone.Services/NzbDrone.Services.Service/Migrations/MigrationHelper.cs new file mode 100644 index 000000000..162847025 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Migrations/MigrationHelper.cs @@ -0,0 +1,55 @@ +using System; +using System.Data; +using System.Reflection; +using System.Web.Hosting; +using Migrator.Framework; +using NLog; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Services.Service.Migrations +{ + public class MigrationsHelper + { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + public static void Run(string connetionString) + { + logger.Info("Preparing to run database migration"); + + VerifyConnectionString(connetionString); + + try + { + var migrator = new Migrator.Migrator("sqlserver", connetionString, Assembly.GetAssembly(typeof(MigrationsHelper)), true, new MigrationLogger()); + migrator.MigrateToLastVersion(); + logger.Info("Database migration completed"); + } + catch (Exception e) + { + logger.FatalException("An error has occurred while migrating database", e); + } + } + + private static void VerifyConnectionString(string connectionString) + { + if(connectionString == null) throw new ArgumentNullException("connectionString"); + + if (HostingEnvironment.ApplicationPhysicalPath != null && HostingEnvironment.ApplicationPhysicalPath.ToLower().Contains("stage") && + !connectionString.ToLower().Contains("stage")) + { + throw new InvalidOperationException("Attempting to migrate production database from staging environment"); + } + } + + public static string GetIndexName(string tableName, params string[] columns) + { + return String.Format("IX_{0}_{1}", tableName, String.Join("_", columns)); + } + + public static readonly Column VersionColumn = new Column("Version", DbType.String, 10, ColumnProperty.NotNull); + public static readonly Column ProductionColumn = new Column("IsProduction", DbType.Boolean, ColumnProperty.NotNull); + public static readonly Column TimestampColumn = new Column("TimeStamp", DbType.DateTime, ColumnProperty.NotNull); + public static readonly Column UGuidColumn = new Column("UGuid", DbType.Guid, ColumnProperty.Null); + + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/NzbDrone.Services.Service.csproj b/NzbDrone.Services/NzbDrone.Services.Service/NzbDrone.Services.Service.csproj new file mode 100644 index 000000000..156c81011 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/NzbDrone.Services.Service.csproj @@ -0,0 +1,175 @@ + + + + Debug + AnyCPU + + + 2.0 + {63B155D7-AE78-4FEB-88BB-2F025ADD1F15} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + NzbDrone.Services.Service + NzbDrone.Services.Service + v4.0 + true + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + x86 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + x86 + + + + ..\..\packages\AutoMapper.2.0.0\lib\net40-client\AutoMapper.dll + + + ..\..\packages\elmah.corelibrary.1.2.1\lib\Elmah.dll + + + + True + ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + False + ..\..\Libraries\Migrator.NET\Migrator.dll + + + False + ..\..\Libraries\Migrator.NET\Migrator.Framework.dll + + + False + ..\..\Libraries\Migrator.NET\Migrator.Providers.dll + + + ..\..\packages\Newtonsoft.Json.4.0.7\lib\net40\Newtonsoft.Json.dll + + + ..\..\packages\Ninject.2.2.1.4\lib\net40-Full\Ninject.dll + + + ..\..\packages\Ninject.MVC3.2.2.2.0\lib\net40-Full\Ninject.Web.Mvc.dll + + + ..\..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll + + + + + + + + + + + + + + + + + + + ..\..\packages\WebActivator.1.5\lib\net40\WebActivator.dll + + + + + + + + Designer + + + Web.config + + + Web.config + + + Web.config + + + + + + + + + + + Global.asax + + + + + + + + + + + + + + + + + + + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} + NzbDrone.Core + + + + + + + + + + False + True + 1542 + / + http://localhost:1542/ + False + False + + + False + + + + + + \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Properties/AssemblyInfo.cs b/NzbDrone.Services/NzbDrone.Services.Service/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8f7e1bcd5 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NzbDrone.Services.Service")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NzbDrone.Services.Service")] +[assembly: AssemblyCopyright("Copyright © 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("28da3fae-e494-41a9-9e9c-4479e13d91ee")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Providers/DailySeriesProvider.cs b/NzbDrone.Services/NzbDrone.Services.Service/Providers/DailySeriesProvider.cs new file mode 100644 index 000000000..efb327e73 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Providers/DailySeriesProvider.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using NzbDrone.Services.Service.Repository; +using PetaPoco; + +namespace NzbDrone.Services.Service.Providers +{ + public class DailySeriesProvider + { + private readonly IDatabase _database; + + public DailySeriesProvider(IDatabase database) + { + _database = database; + } + + public IList All() + { + return _database.Fetch(); + } + + public IList AllSeriesIds() + { + return _database.Fetch("SELECT Id from DailySeries"); + } + + public bool IsDaily(int seriesId) + { + return _database.Exists(seriesId); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Providers/SceneMappingProvider.cs b/NzbDrone.Services/NzbDrone.Services.Service/Providers/SceneMappingProvider.cs new file mode 100644 index 000000000..a8057befe --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Providers/SceneMappingProvider.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using NzbDrone.Services.Service.Repository; +using PetaPoco; + +namespace NzbDrone.Services.Service.Providers +{ + public class SceneMappingProvider + { + private readonly IDatabase _database; + + public SceneMappingProvider(IDatabase database) + { + _database = database; + } + + public IList AllLive() + { + return _database.Fetch(); + } + + public IList AllPending() + { + return _database.Fetch(); + } + + public void Insert(SceneMapping sceneMapping) + { + _database.Insert(sceneMapping); + } + + public void Insert(PendingSceneMapping pendingSceneMapping) + { + _database.Insert(pendingSceneMapping); + } + + public void DeleteLive(string cleanTitle) + { + _database.Delete(cleanTitle); + } + + public void DeletePending(string cleanTitle) + { + _database.Delete(cleanTitle); + } + + public bool Promote(string cleanTitle) + { + try + { + var pendingItem = _database.Single(cleanTitle); + + var mapping = new SceneMapping + { + CleanTitle = pendingItem.CleanTitle, + Id = pendingItem.Id, + Title = pendingItem.Title + }; + + _database.Insert(mapping); + _database.Delete(pendingItem); + } + catch (Exception ex) + { + return false; + } + + return true; + } + + public bool PromoteAll() + { + try + { + var pendingItems = _database.Fetch(); + + foreach (var pendingItem in pendingItems) + { + Promote(pendingItem.Title); + } + } + catch (Exception ex) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Repository/DailySeries.cs b/NzbDrone.Services/NzbDrone.Services.Service/Repository/DailySeries.cs new file mode 100644 index 000000000..c5bf05c25 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Repository/DailySeries.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using PetaPoco; + +namespace NzbDrone.Services.Service.Repository +{ + [TableName("DailySeries")] + [PrimaryKey("Id", autoIncrement = false)] + public class DailySeries + { + public int Id { get; set; } + + public string Title { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Repository/PendingSceneMapping.cs b/NzbDrone.Services/NzbDrone.Services.Service/Repository/PendingSceneMapping.cs new file mode 100644 index 000000000..de0c11caf --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Repository/PendingSceneMapping.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using PetaPoco; + +namespace NzbDrone.Services.Service.Repository +{ + [TableName("PendingSceneMappings")] + [PrimaryKey("CleanTitle", autoIncrement = false)] + public class PendingSceneMapping + { + public string CleanTitle { get; set; } + public int Id { get; set; } + public string Title { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ExceptionRow.cs b/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ExceptionRow.cs new file mode 100644 index 000000000..db138f439 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ExceptionRow.cs @@ -0,0 +1,14 @@ +using System.Linq; +using PetaPoco; + +namespace NzbDrone.Services.Service.Repository.Reporting +{ + [TableName("ExceptionReports")] + public class ExceptionRow : ReportRowBase + { + public string Type { get; set; } + public string Logger { get; set; } + public string LogMessage { get; set; } + public string String { get; set; } + } +} diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ParseErrorRow.cs b/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ParseErrorRow.cs new file mode 100644 index 000000000..c2e3d076f --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ParseErrorRow.cs @@ -0,0 +1,11 @@ +using System.Linq; +using PetaPoco; + +namespace NzbDrone.Services.Service.Repository.Reporting +{ + [TableName("ParseErrorReports")] + public class ParseErrorRow : ReportRowBase + { + public string Title { get; set; } + } +} diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ReportRowBase.cs b/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ReportRowBase.cs new file mode 100644 index 000000000..3ad9a7346 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Repository/Reporting/ReportRowBase.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using NzbDrone.Common.Contract; + +namespace NzbDrone.Services.Service.Repository.Reporting +{ + public abstract class ReportRowBase + { + public void LoadBase(ReportBase report) + { + Timestamp = DateTime.Now; + Version = report.Version; + IsProduction = report.IsProduction; + UGuid = report.UGuid; + } + + public string Version { get; set; } + + public DateTime Timestamp { get; set; } + + public bool IsProduction { get; set; } + + public Guid UGuid { get; set; } + } +} diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Repository/SceneMapping.cs b/NzbDrone.Services/NzbDrone.Services.Service/Repository/SceneMapping.cs new file mode 100644 index 000000000..b04a32d86 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Repository/SceneMapping.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using PetaPoco; + +namespace NzbDrone.Services.Service.Repository +{ + [TableName("SceneMappings")] + [PrimaryKey("CleanTitle", autoIncrement = false)] + public class SceneMapping + { + public string CleanTitle { get; set; } + public int Id { get; set; } + public string Title { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Web.Debug.config b/NzbDrone.Services/NzbDrone.Services.Service/Web.Debug.config new file mode 100644 index 000000000..2c6dd51a7 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Web.Release.config b/NzbDrone.Services/NzbDrone.Services.Service/Web.Release.config new file mode 100644 index 000000000..06e1f61a3 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Web.Release.config @@ -0,0 +1,32 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Web.Stage.config b/NzbDrone.Services/NzbDrone.Services.Service/Web.Stage.config new file mode 100644 index 000000000..6575f8380 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Web.Stage.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/Web.config b/NzbDrone.Services/NzbDrone.Services.Service/Web.config new file mode 100644 index 000000000..eece4679f --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/Web.config @@ -0,0 +1,57 @@ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Service/packages.config b/NzbDrone.Services/NzbDrone.Services.Service/packages.config new file mode 100644 index 000000000..055828268 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Service/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Tests/App.config b/NzbDrone.Services/NzbDrone.Services.Tests/App.config new file mode 100644 index 000000000..67a5234df --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Tests/App.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Tests/Framework/ServicesTestBase.cs b/NzbDrone.Services/NzbDrone.Services.Tests/Framework/ServicesTestBase.cs new file mode 100644 index 000000000..727bdcca6 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Tests/Framework/ServicesTestBase.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using Migrator.Providers.SqlServer; +using NzbDrone.Services.Service.Migrations; +using NzbDrone.Test.Common; +using NzbDrone.Test.Common.AutoMoq; +using PetaPoco; + +namespace NzbDrone.Services.Tests.Framework +{ + public abstract class ServicesTestBase : LoggingTest + { + static readonly string connectionString = ConfigurationManager.ConnectionStrings["SqlExpress"].ConnectionString; + + static ServicesTestBase() + { + + } + + private static void ResetDb() + { + var transformationProvider = new SqlServerTransformationProvider(new SqlServerDialect(), connectionString); + + var tables = transformationProvider.GetTables(); + + foreach (var table in tables) + { + transformationProvider.RemoveTable(table); + } + + + MigrationsHelper.Run(connectionString); + } + + public IDatabase Db { get; private set; } + + private AutoMoqer _mocker; + protected AutoMoqer Mocker + { + get + { + if (_mocker == null) + { + _mocker = new AutoMoqer(); + } + + return _mocker; + } + } + + protected void WithRealDb() + { + ResetDb(); + + Db = new Database("SqlExpress"); + Mocker.SetConstant(Db); + } + + } +} diff --git a/NzbDrone.Services/NzbDrone.Services.Tests/Framework/TestDbHelper.cs b/NzbDrone.Services/NzbDrone.Services.Tests/Framework/TestDbHelper.cs new file mode 100644 index 000000000..5149b2e63 --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Tests/Framework/TestDbHelper.cs @@ -0,0 +1,25 @@ +// ReSharper disable RedundantUsingDirective + +using System.Configuration; +using System.Linq; +using System; +using System.IO; +using Migrator.Providers; +using Migrator.Providers.SqlServer; +using NzbDrone.Core.Datastore; +using PetaPoco; +using MigrationsHelper = NzbDrone.Services.Service.Migrations.MigrationsHelper; + +namespace NzbDrone.Services.Tests.Framework +{ + internal static class TestDbHelper + { + + + public static void WhipeDb() + { + + + } + } +} \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Tests/NzbDrone.Services.Tests.csproj b/NzbDrone.Services/NzbDrone.Services.Tests/NzbDrone.Services.Tests.csproj new file mode 100644 index 000000000..93971becc --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Tests/NzbDrone.Services.Tests.csproj @@ -0,0 +1,110 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {12261AE5-BCC4-4DC7-A218-0764B9C30230} + Library + Properties + NzbDrone.Services.Tests + NzbDrone.Services.Tests + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x86 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll + + + ..\..\packages\FluentAssertions.1.7.0\Lib\net40\FluentAssertions.dll + + + ..\..\Libraries\Migrator.NET\Migrator.dll + + + ..\..\Libraries\Migrator.NET\Migrator.Framework.dll + + + ..\..\Libraries\Migrator.NET\Migrator.Providers.dll + + + ..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + + ..\..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll + + + ..\..\packages\NUnit.2.5.10.11092\lib\nunit.mocks.dll + + + ..\..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll + + + + + + + + + + + + + + + + + + + + Designer + + + + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} + NzbDrone.Core + + + {CADDFCE0-7509-4430-8364-2074E1EEFCA2} + NzbDrone.Test.Common + + + {63B155D7-AE78-4FEB-88BB-2F025ADD1F15} + NzbDrone.Services.Service + + + + + \ No newline at end of file diff --git a/NzbDrone.Services/NzbDrone.Services.Tests/Properties/AssemblyInfo.cs b/NzbDrone.Services/NzbDrone.Services.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3d7a5ac3a --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NzbDrone.Services.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NzbDrone.Services.Tests")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0f63c407-65ef-4cd1-b660-78499e432daa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NzbDrone.Services/NzbDrone.Services.Tests/ReportingControllerFixture.cs b/NzbDrone.Services/NzbDrone.Services.Tests/ReportingControllerFixture.cs new file mode 100644 index 000000000..4bacfcb1f --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Tests/ReportingControllerFixture.cs @@ -0,0 +1,123 @@ +using System; +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Contract; +using NzbDrone.Services.Service.Controllers; +using NzbDrone.Services.Service.Repository.Reporting; +using NzbDrone.Services.Tests.Framework; + +namespace NzbDrone.Services.Tests +{ + [TestFixture] + public class ReportingControllerFixture : ServicesTestBase + { + + ReportingController Controller + { + get + { + return Mocker.Resolve(); + } + } + + + private static ParseErrorReport CreateParseErrorReport() + { + return new ParseErrorReport + { + IsProduction = true, + Title = "MyTitle", + Version = "1.1.2.3", + UGuid = Guid.NewGuid() + }; + } + + private static ExceptionReport CreateExceptionReport() + { + return new ExceptionReport + { + IsProduction = true, + Version = "1.1.2.3", + UGuid = Guid.NewGuid(), + Logger = "NzbDrone.Logger.Name", + LogMessage = "Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message", + String = "Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message", + Type = typeof(InvalidOperationException).Name + }; + } + + + [Test] + public void parse_report_should_be_saved() + { + var parseReport = CreateParseErrorReport(); + + WithRealDb(); + + Controller.ParseError(parseReport); + + var reports = Db.Fetch(); + reports.Should().HaveCount(1); + reports.Single().Title.Should().Be(parseReport.Title); + reports.Single().IsProduction.Should().Be(parseReport.IsProduction); + reports.Single().Version.Should().Be(parseReport.Version); + reports.Single().Timestamp.Should().BeWithin(TimeSpan.FromSeconds(4)).Before(DateTime.Now); + reports.Single().UGuid.Should().Be(parseReport.UGuid); + } + + [Test] + public void parse_report_should_save_report_if_title_doesnt_exist() + { + var parseReport1 = CreateParseErrorReport(); + var parseReport2 = CreateParseErrorReport(); + + parseReport1.Title = Guid.NewGuid().ToString(); + + WithRealDb(); + + Controller.ParseError(parseReport1); + Controller.ParseError(parseReport2); + + var reports = Db.Fetch(); + reports.Should().HaveCount(2); + } + + [Test] + public void parse_report_should_not_save_report_if_title_exist() + { + var parseReport1 = CreateParseErrorReport(); + var parseReport2 = CreateParseErrorReport(); + + WithRealDb(); + + Controller.ParseError(parseReport1); + Controller.ParseError(parseReport2); + + var reports = Db.Fetch(); + reports.Should().HaveCount(1); + } + + [Test] + public void exception_report_should_be_saved() + { + var exceptionReport = CreateExceptionReport(); + + WithRealDb(); + + Controller.ReportException(exceptionReport); + + var exceptionRows = Db.Fetch(); + exceptionRows.Should().HaveCount(1); + exceptionRows.Single().IsProduction.Should().Be(exceptionReport.IsProduction); + exceptionRows.Single().Version.Should().Be(exceptionReport.Version); + exceptionRows.Single().Timestamp.Should().BeWithin(TimeSpan.FromSeconds(4)).Before(DateTime.Now); + exceptionRows.Single().UGuid.Should().Be(exceptionReport.UGuid); + + exceptionRows.Single().Logger.Should().Be(exceptionReport.Logger); + exceptionRows.Single().LogMessage.Should().Be(exceptionReport.LogMessage); + exceptionRows.Single().String.Should().Be(exceptionReport.String); + exceptionRows.Single().Type.Should().Be(exceptionReport.Type); + } + } +} diff --git a/NzbDrone.Services/NzbDrone.Services.Tests/packages.config b/NzbDrone.Services/NzbDrone.Services.Tests/packages.config new file mode 100644 index 000000000..2e46099aa --- /dev/null +++ b/NzbDrone.Services/NzbDrone.Services.Tests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/NzbDrone.Test.Common/TestBase.cs b/NzbDrone.Test.Common/TestBase.cs index 72d0bf571..f868f5896 100644 --- a/NzbDrone.Test.Common/TestBase.cs +++ b/NzbDrone.Test.Common/TestBase.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Test.Common protected const string INTEGRATION_TEST = "Integration Test"; + private AutoMoqer _mocker; protected AutoMoqer Mocker { @@ -27,6 +28,9 @@ namespace NzbDrone.Test.Common } } + protected Mock MockedRestProvider { get; private set; } + + protected string VirtualPath { get @@ -41,6 +45,9 @@ namespace NzbDrone.Test.Common [SetUp] public void TestBaseSetup() { + MockedRestProvider = new Mock(); + ReportingService.RestProvider = MockedRestProvider.Object; + if (Directory.Exists(TempFolder)) { Directory.Delete(TempFolder, true); diff --git a/NzbDrone.Update/Program.cs b/NzbDrone.Update/Program.cs index e9dd1a97a..d159133fe 100644 --- a/NzbDrone.Update/Program.cs +++ b/NzbDrone.Update/Program.cs @@ -62,7 +62,7 @@ namespace NzbDrone.Update private static void InitLoggers() { - LogConfiguration.RegisterExceptioneer(); + LogConfiguration.RegisterRemote(); LogConfiguration.RegisterConsoleLogger(LogLevel.Trace); LogConfiguration.RegisterUdpLogger(); diff --git a/NzbDrone.Update/Properties/AssemblyInfo.cs b/NzbDrone.Update/Properties/AssemblyInfo.cs index 0c2c44382..acd538675 100644 --- a/NzbDrone.Update/Properties/AssemblyInfo.cs +++ b/NzbDrone.Update/Properties/AssemblyInfo.cs @@ -12,5 +12,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.0.*")] -[assembly: AssemblyFileVersion("0.0.0.*")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.0.*")] +[assembly: AssemblyFileVersion("1.0.0.*")] \ No newline at end of file diff --git a/NzbDrone.Web.UI.Test/NzbDrone.Web.UI.Automation.csproj b/NzbDrone.Web.UI.Test/NzbDrone.Web.UI.Automation.csproj index e9b595d7e..f47492927 100644 --- a/NzbDrone.Web.UI.Test/NzbDrone.Web.UI.Automation.csproj +++ b/NzbDrone.Web.UI.Test/NzbDrone.Web.UI.Automation.csproj @@ -39,7 +39,7 @@ ..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll - ..\packages\Newtonsoft.Json.4.0.4\lib\net40\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.4.0.6\lib\net40\Newtonsoft.Json.dll ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll @@ -52,9 +52,8 @@ - - False - ..\packages\Selenium.WebDriver.2.17.0\lib\net40\WebDriver.dll + + ..\packages\Selenium.WebDriver.2.18.0\lib\net40\WebDriver.dll @@ -65,9 +64,6 @@ - - - PreserveNewest @@ -87,6 +83,10 @@ NzbDrone + + + + + + + \ No newline at end of file diff --git a/packages/elmah.sqlserver.1.2/elmah.sqlserver.1.2.nupkg b/packages/elmah.sqlserver.1.2/elmah.sqlserver.1.2.nupkg new file mode 100644 index 000000000..9465a6622 Binary files /dev/null and b/packages/elmah.sqlserver.1.2/elmah.sqlserver.1.2.nupkg differ diff --git a/packages/repositories.config b/packages/repositories.config index d4c939a97..e3e759655 100644 --- a/packages/repositories.config +++ b/packages/repositories.config @@ -1,14 +1,16 @@  - + + + - - + + + - - - + + \ No newline at end of file diff --git a/service_deploy_production.bat b/service_deploy_production.bat new file mode 100644 index 000000000..3a977d22b --- /dev/null +++ b/service_deploy_production.bat @@ -0,0 +1,6 @@ +rd C:\inetpub\services /S /Q + +xcopy C:\inetpub\services_stage\*.* C:\inetpub\services\ /E /V /I /Y /F /C /o +xcopy C:\inetpub\services\web.production.config c:\inetpub\services\web.config /o /y + +pause \ No newline at end of file diff --git a/service_stage.bat b/service_stage.bat new file mode 100644 index 000000000..0b0ba93d7 --- /dev/null +++ b/service_stage.bat @@ -0,0 +1,28 @@ +SET TARGET=_rawPackage_service + +rd %TARGET% /S /Q + +xcopy NzbDrone.Services\NzbDrone.Services.Service\bin\*.* %TARGET%\bin\ /E /V /I /Y /F /O +xcopy NzbDrone.Services\NzbDrone.Services.Service\log.config %TARGET% /S /V /I /Y /F /O +xcopy NzbDrone.Services\NzbDrone.Services.Service\Global.asax %TARGET% /S /V /I /Y /F /O +xcopy service_deploy_production.bat %TARGET% /O /Y + +Libraries\CTT\ctt.exe source:"NzbDrone.Services\NzbDrone.Services.Service\Web.config" transform:"NzbDrone.Services\NzbDrone.Services.Service\Web.Stage.config" destination:"%TARGET%\Web.config" +Libraries\CTT\ctt.exe source:"NzbDrone.Services\NzbDrone.Services.Service\Web.config" transform:"NzbDrone.Services\NzbDrone.Services.Service\Web.Release.config" destination:"%TARGET%\Web.production.config" + +CD %TARGET% + +del nlog.xml /Q /F /S +del nlog.pdb /Q /F /S +del ninject*.pdb /Q /F /S +del ninject*.xml /Q /F /S +del Mvc*.pdb /Q /F /S +del bin\*.xml /Q /F /S + +cd .. + +rd C:\inetpub\services_stage /S /Q + +xcopy _rawPackage_service\*.* C:\inetpub\services_stage /E /V /I /Y + +