From 33a97b148f4a9f8b626a03153b09fd68aa71db14 Mon Sep 17 00:00:00 2001 From: KZ Date: Sun, 19 Jul 2015 01:27:41 +0100 Subject: [PATCH] Refactor --- src/Jackett.Console/App.config | 34 ++ src/Jackett.Console/Jackett.Console.csproj | 130 ++++++ src/Jackett.Console/Program.cs | 78 ++++ .../Properties/AssemblyInfo.cs | 36 ++ .../jackett.ico} | Bin src/Jackett.Console/packages.config | 18 + src/Jackett.Service/App.config | 6 + src/Jackett.Service/Jackett.Service.csproj | 73 ++++ src/Jackett.Service/Program.cs | 25 ++ .../Properties/AssemblyInfo.cs | 36 ++ src/Jackett.Service/Service.Designer.cs | 37 ++ src/Jackett.Service/Service.cs | 28 ++ src/Jackett.Service/jackett.ico | Bin 0 -> 370070 bytes src/Jackett.Tray/App.config | 6 + src/Jackett.Tray/Jackett.Tray.csproj | 96 ++++ .../Main.Designer.cs | 7 +- src/{Jackett => Jackett.Tray}/Main.cs | 12 +- src/{Jackett => Jackett.Tray}/Main.resx | 0 src/Jackett.Tray/Program.cs | 22 + src/Jackett.Tray/Properties/AssemblyInfo.cs | 36 ++ .../Properties/Resources.Designer.cs | 71 +++ src/Jackett.Tray/Properties/Resources.resx | 117 +++++ .../Properties/Settings.Designer.cs | 30 ++ src/Jackett.Tray/Properties/Settings.settings | 7 + src/Jackett.Tray/jackett.ico | Bin 0 -> 370070 bytes src/Jackett.sln | 22 +- src/Jackett/App.config | 22 +- src/Jackett/Controllers/AdminController.cs | 265 ++++++++++++ src/Jackett/CookieContainerExtensions.cs | 5 +- src/Jackett/ExceptionWithConfigData.cs | 3 +- src/Jackett/IndexerInterface.cs | 3 +- src/Jackett/IndexerManager.cs | 10 +- src/Jackett/Indexers/AlphaRatio.cs | 15 +- src/Jackett/Indexers/AnimeBytes.cs | 9 +- src/Jackett/Indexers/BeyondHD.cs | 11 +- src/Jackett/Indexers/BitHdtv.cs | 9 +- src/Jackett/Indexers/BitMeTV.cs | 9 +- src/Jackett/Indexers/FrenchTorrentDb.cs | 2 + src/Jackett/Indexers/Freshon.cs | 9 +- src/Jackett/Indexers/HDTorrents.cs | 9 +- src/Jackett/Indexers/IPTorrents.cs | 9 +- src/Jackett/{TVRage.cs => Indexers/Master.cs} | 10 +- src/Jackett/Indexers/MoreThanTV.cs | 14 +- src/Jackett/Indexers/Rarbg.cs | 2 + src/Jackett/Indexers/SceneAccess.cs | 15 +- src/Jackett/Indexers/SceneTime.cs | 9 +- src/Jackett/Indexers/ShowRSS.cs | 4 +- src/Jackett/Indexers/Strike.cs | 3 +- src/Jackett/Indexers/T411.cs | 4 +- src/Jackett/Indexers/ThePirateBay.cs | 4 +- src/Jackett/Indexers/TorrentDay.cs | 9 +- src/Jackett/Indexers/TorrentLeech.cs | 9 +- src/Jackett/Indexers/TorrentShack.cs | 9 +- src/Jackett/Indexers/Torrentz.cs | 4 +- src/Jackett/Jackett.csproj | 108 +++-- src/Jackett/JackettModule.cs | 29 ++ src/Jackett/{ => Models}/ApiKey.cs | 2 +- src/Jackett/{ => Models}/CachedResult.cs | 2 +- src/Jackett/{ => Models}/ChannelInfo.cs | 2 +- src/Jackett/{ => Models}/ConfigurationData.cs | 2 +- .../ConfigurationDataBasicLogin.cs | 2 +- .../{ => Models}/ConfigurationDataCookie.cs | 2 +- .../{ => Models}/ConfigurationDataUrl.cs | 2 +- src/Jackett/{ => Models}/ReleaseInfo.cs | 2 +- src/Jackett/{ => Models}/ResultPage.cs | 2 +- src/Jackett/{ => Models}/TorznabQuery.cs | 5 +- src/Jackett/Program.cs | 179 -------- src/Jackett/Server.cs | 346 +++------------ src/Jackett/Services/ConfigurationService.cs | 84 ++++ src/Jackett/Services/IndexerManagerService.cs | 68 +++ src/Jackett/{ => Services}/SonarApi.cs | 28 +- src/Jackett/Startup.cs | 52 +++ src/Jackett/{ => Utils}/BrowserUtil.cs | 2 +- src/Jackett/{ => Utils}/ParseUtil.cs | 2 +- src/Jackett/{ => Utils}/ServerUtil.cs | 2 +- src/Jackett/WebApi.cs | 20 +- src/Jackett/WebContent/custom.js | 22 +- src/Jackett/WebServer.cs | 409 ++++++++++++++++++ src/Jackett/packages.config | 23 +- 79 files changed, 2205 insertions(+), 605 deletions(-) create mode 100644 src/Jackett.Console/App.config create mode 100644 src/Jackett.Console/Jackett.Console.csproj create mode 100644 src/Jackett.Console/Program.cs create mode 100644 src/Jackett.Console/Properties/AssemblyInfo.cs rename src/{Jackett/jacket_large.ico => Jackett.Console/jackett.ico} (100%) create mode 100644 src/Jackett.Console/packages.config create mode 100644 src/Jackett.Service/App.config create mode 100644 src/Jackett.Service/Jackett.Service.csproj create mode 100644 src/Jackett.Service/Program.cs create mode 100644 src/Jackett.Service/Properties/AssemblyInfo.cs create mode 100644 src/Jackett.Service/Service.Designer.cs create mode 100644 src/Jackett.Service/Service.cs create mode 100644 src/Jackett.Service/jackett.ico create mode 100644 src/Jackett.Tray/App.config create mode 100644 src/Jackett.Tray/Jackett.Tray.csproj rename src/{Jackett => Jackett.Tray}/Main.Designer.cs (98%) rename src/{Jackett => Jackett.Tray}/Main.cs (89%) rename src/{Jackett => Jackett.Tray}/Main.resx (100%) create mode 100644 src/Jackett.Tray/Program.cs create mode 100644 src/Jackett.Tray/Properties/AssemblyInfo.cs create mode 100644 src/Jackett.Tray/Properties/Resources.Designer.cs create mode 100644 src/Jackett.Tray/Properties/Resources.resx create mode 100644 src/Jackett.Tray/Properties/Settings.Designer.cs create mode 100644 src/Jackett.Tray/Properties/Settings.settings create mode 100644 src/Jackett.Tray/jackett.ico create mode 100644 src/Jackett/Controllers/AdminController.cs rename src/Jackett/{TVRage.cs => Indexers/Master.cs} (54%) create mode 100644 src/Jackett/JackettModule.cs rename src/Jackett/{ => Models}/ApiKey.cs (96%) rename src/Jackett/{ => Models}/CachedResult.cs (96%) rename src/Jackett/{ => Models}/ChannelInfo.cs (96%) rename src/Jackett/{ => Models}/ConfigurationData.cs (99%) rename src/Jackett/{ => Models}/ConfigurationDataBasicLogin.cs (96%) rename src/Jackett/{ => Models}/ConfigurationDataCookie.cs (98%) rename src/Jackett/{ => Models}/ConfigurationDataUrl.cs (96%) rename src/Jackett/{ => Models}/ReleaseInfo.cs (99%) rename src/Jackett/{ => Models}/ResultPage.cs (99%) rename src/Jackett/{ => Models}/TorznabQuery.cs (97%) delete mode 100644 src/Jackett/Program.cs create mode 100644 src/Jackett/Services/ConfigurationService.cs create mode 100644 src/Jackett/Services/IndexerManagerService.cs rename src/Jackett/{ => Services}/SonarApi.cs (87%) create mode 100644 src/Jackett/Startup.cs rename src/Jackett/{ => Utils}/BrowserUtil.cs (94%) rename src/Jackett/{ => Utils}/ParseUtil.cs (98%) rename src/Jackett/{ => Utils}/ServerUtil.cs (99%) create mode 100644 src/Jackett/WebServer.cs diff --git a/src/Jackett.Console/App.config b/src/Jackett.Console/App.config new file mode 100644 index 000000000..da396d090 --- /dev/null +++ b/src/Jackett.Console/App.config @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Jackett.Console/Jackett.Console.csproj b/src/Jackett.Console/Jackett.Console.csproj new file mode 100644 index 000000000..54ba94594 --- /dev/null +++ b/src/Jackett.Console/Jackett.Console.csproj @@ -0,0 +1,130 @@ + + + + + Debug + AnyCPU + {4E2A81DA-E235-4A88-AD20-38AABBFBF33C} + Exe + Properties + Jackett.Console + JackettConsole + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + jackett.ico + + + JackettConsole.Program + + + + False + ..\packages\Autofac.3.5.0\lib\net40\Autofac.dll + + + ..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll + + + False + ..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll + + + ..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\Microsoft.Owin.FileSystems.3.0.1\lib\net45\Microsoft.Owin.FileSystems.dll + True + + + ..\packages\Microsoft.Owin.Host.HttpListener.2.0.2\lib\net45\Microsoft.Owin.Host.HttpListener.dll + True + + + ..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll + True + + + ..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll + True + + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll + True + + + + + + + + + + + + + + + + + + + {e636d5f8-68b4-4903-b4ed-ccfd9c9e899f} + Jackett + + + + + + + + \ No newline at end of file diff --git a/src/Jackett.Console/Program.cs b/src/Jackett.Console/Program.cs new file mode 100644 index 000000000..d1d4c9429 --- /dev/null +++ b/src/Jackett.Console/Program.cs @@ -0,0 +1,78 @@ +using Jackett; +using Jackett.Indexers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace JackettConsole +{ + public class Program + { + static void Main(string[] args) + { + + + Server.Start(); + Console.ReadKey(); + + + /* var serverTask = Task.Run(async () => + { + ServerInstance = new Server(); + await ServerInstance.Start(); + }); + + try + { + if (Program.IsWindows) + { +#if !__MonoCS__ + Application.Run(new Main()); +#endif + } + } + catch (Exception) + { + + }*/ + + Console.WriteLine("Running in headless mode."); + + + + // Task.WaitAll(serverTask); + Console.WriteLine("Server thread exit"); + } + + /* public static void RestartServer() + { + + ServerInstance.Stop(); + ServerInstance = null; + var serverTask = Task.Run(async () => + { + ServerInstance = new Server(); + await ServerInstance.Start(); + }); + Task.WaitAll(serverTask); + }*/ + + + + + + static public void RestartAsAdmin() + { + // var startInfo = new ProcessStartInfo(Application.ExecutablePath.ToString()) { Verb = "runas" }; + // Process.Start(startInfo); + Environment.Exit(0); + } + } +} + diff --git a/src/Jackett.Console/Properties/AssemblyInfo.cs b/src/Jackett.Console/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0c11610e1 --- /dev/null +++ b/src/Jackett.Console/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("Jackett.Console")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Jackett.Console")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[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("4e2a81da-e235-4a88-ad20-38aabbfbf33c")] + +// 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/src/Jackett/jacket_large.ico b/src/Jackett.Console/jackett.ico similarity index 100% rename from src/Jackett/jacket_large.ico rename to src/Jackett.Console/jackett.ico diff --git a/src/Jackett.Console/packages.config b/src/Jackett.Console/packages.config new file mode 100644 index 000000000..4878b92aa --- /dev/null +++ b/src/Jackett.Console/packages.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Jackett.Service/App.config b/src/Jackett.Service/App.config new file mode 100644 index 000000000..88fa4027b --- /dev/null +++ b/src/Jackett.Service/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Jackett.Service/Jackett.Service.csproj b/src/Jackett.Service/Jackett.Service.csproj new file mode 100644 index 000000000..6e2370814 --- /dev/null +++ b/src/Jackett.Service/Jackett.Service.csproj @@ -0,0 +1,73 @@ + + + + + Debug + AnyCPU + {BF611F7B-4658-4CB8-AA9E-0736FADAA3BA} + WinExe + Properties + Jackett.Service + JackettService + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + jackett.ico + + + + + + + + + + + + + + + Component + + + Service.cs + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Jackett.Service/Program.cs b/src/Jackett.Service/Program.cs new file mode 100644 index 000000000..22c25af4b --- /dev/null +++ b/src/Jackett.Service/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Service +{ + static class Program + { + /// + /// The main entry point for the application. + /// + static void Main() + { + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + new Service() + }; + ServiceBase.Run(ServicesToRun); + } + } +} diff --git a/src/Jackett.Service/Properties/AssemblyInfo.cs b/src/Jackett.Service/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..246a632ba --- /dev/null +++ b/src/Jackett.Service/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("Jackett.Service")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Jackett.Service")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[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("bf611f7b-4658-4cb8-aa9e-0736fadaa3ba")] + +// 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/src/Jackett.Service/Service.Designer.cs b/src/Jackett.Service/Service.Designer.cs new file mode 100644 index 000000000..c0310557f --- /dev/null +++ b/src/Jackett.Service/Service.Designer.cs @@ -0,0 +1,37 @@ +namespace Jackett.Service +{ + partial class Service + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "Service1"; + } + + #endregion + } +} diff --git a/src/Jackett.Service/Service.cs b/src/Jackett.Service/Service.cs new file mode 100644 index 000000000..d1e29db74 --- /dev/null +++ b/src/Jackett.Service/Service.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Service +{ + public partial class Service : ServiceBase + { + public Service() + { + InitializeComponent(); + } + + protected override void OnStart(string[] args) + { + } + + protected override void OnStop() + { + } + } +} diff --git a/src/Jackett.Service/jackett.ico b/src/Jackett.Service/jackett.ico new file mode 100644 index 0000000000000000000000000000000000000000..6392acc61b943dadd0a1cbd56fca7dc5de0b7791 GIT binary patch literal 370070 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 + + + + + \ No newline at end of file diff --git a/src/Jackett.Tray/Jackett.Tray.csproj b/src/Jackett.Tray/Jackett.Tray.csproj new file mode 100644 index 000000000..0e3f38375 --- /dev/null +++ b/src/Jackett.Tray/Jackett.Tray.csproj @@ -0,0 +1,96 @@ + + + + + Debug + AnyCPU + {FF9025B1-EC14-4AA9-8081-9F69C5E35B63} + WinExe + Properties + Jackett.Tray + JackettTray + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + jackett.ico + + + + + + + + + + + + + + + + + Form + + + Main.cs + + + + + Main.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + \ No newline at end of file diff --git a/src/Jackett/Main.Designer.cs b/src/Jackett.Tray/Main.Designer.cs similarity index 98% rename from src/Jackett/Main.Designer.cs rename to src/Jackett.Tray/Main.Designer.cs index d881a2821..71457c20d 100644 --- a/src/Jackett/Main.Designer.cs +++ b/src/Jackett.Tray/Main.Designer.cs @@ -1,6 +1,4 @@ -#if !__MonoCS__ - -namespace Jackett +namespace JackettTray { partial class Main { @@ -99,5 +97,4 @@ namespace Jackett private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemShutdown; } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/Jackett/Main.cs b/src/Jackett.Tray/Main.cs similarity index 89% rename from src/Jackett/Main.cs rename to src/Jackett.Tray/Main.cs index 250aff2ce..a1a51b09f 100644 --- a/src/Jackett/Main.cs +++ b/src/Jackett.Tray/Main.cs @@ -13,7 +13,7 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Forms; -namespace Jackett +namespace JackettTray { public partial class Main : Form { @@ -28,13 +28,13 @@ namespace Jackett toolStripMenuItemWebUI.Click += toolStripMenuItemWebUI_Click; toolStripMenuItemShutdown.Click += toolStripMenuItemShutdown_Click; - if (Program.IsFirstRun) - AutoStart = true; + //if (Server.IsFirstRun) + // AutoStart = true; } void toolStripMenuItemWebUI_Click(object sender, EventArgs e) { - Process.Start("http://127.0.0.1:" + Server.Port); + // Process.Start("http://127.0.0.1:" + Server.Port); } void toolStripMenuItemShutdown_Click(object sender, EventArgs e) @@ -84,12 +84,12 @@ namespace Jackett private void CreateShortcut() { - var appPath = Assembly.GetExecutingAssembly().Location; + /* var appPath = Assembly.GetExecutingAssembly().Location; var shell = new IWshRuntimeLibrary.WshShell(); var shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(ShortcutPath); shortcut.Description = Assembly.GetExecutingAssembly().GetName().Name; shortcut.TargetPath = appPath; - shortcut.Save(); + shortcut.Save();*/ } } } diff --git a/src/Jackett/Main.resx b/src/Jackett.Tray/Main.resx similarity index 100% rename from src/Jackett/Main.resx rename to src/Jackett.Tray/Main.resx diff --git a/src/Jackett.Tray/Program.cs b/src/Jackett.Tray/Program.cs new file mode 100644 index 000000000..ddcd715a9 --- /dev/null +++ b/src/Jackett.Tray/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Jackett.Tray +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + // Application.Run(new Form1()); + } + } +} diff --git a/src/Jackett.Tray/Properties/AssemblyInfo.cs b/src/Jackett.Tray/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0f6590c99 --- /dev/null +++ b/src/Jackett.Tray/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("Jackett.Tray")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Jackett.Tray")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[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("ff9025b1-ec14-4aa9-8081-9f69c5e35b63")] + +// 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/src/Jackett.Tray/Properties/Resources.Designer.cs b/src/Jackett.Tray/Properties/Resources.Designer.cs new file mode 100644 index 000000000..b6ce33f0b --- /dev/null +++ b/src/Jackett.Tray/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Jackett.Tray.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Jackett.Tray.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/src/Jackett.Tray/Properties/Resources.resx b/src/Jackett.Tray/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/src/Jackett.Tray/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Jackett.Tray/Properties/Settings.Designer.cs b/src/Jackett.Tray/Properties/Settings.Designer.cs new file mode 100644 index 000000000..c110e9e5e --- /dev/null +++ b/src/Jackett.Tray/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Jackett.Tray.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/src/Jackett.Tray/Properties/Settings.settings b/src/Jackett.Tray/Properties/Settings.settings new file mode 100644 index 000000000..39645652a --- /dev/null +++ b/src/Jackett.Tray/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Jackett.Tray/jackett.ico b/src/Jackett.Tray/jackett.ico new file mode 100644 index 0000000000000000000000000000000000000000..6392acc61b943dadd0a1cbd56fca7dc5de0b7791 GIT binary patch literal 370070 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 - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Jackett/Controllers/AdminController.cs b/src/Jackett/Controllers/AdminController.cs new file mode 100644 index 000000000..bac229054 --- /dev/null +++ b/src/Jackett/Controllers/AdminController.cs @@ -0,0 +1,265 @@ +using Autofac; +using Jackett.Models; +using Jackett.Services; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web.Http; + +namespace Jackett.Controllers +{ + [RoutePrefix("admin")] + public class AdminController : ApiController + { + private IConfigurationService config; + private ISonarrApi sonarrApi; + private IIndexerManagerService indexerService; + + public AdminController(IConfigurationService config, ISonarrApi s, IIndexerManagerService i) + { + this.config = config; + sonarrApi = s; + indexerService = i; + } + + private async Task ReadPostDataJson() + { + var content = await Request.Content.ReadAsStringAsync(); + return JObject.Parse(content); + } + + [Route("get_config_form")] + [HttpGet] + public async Task GetConfigForm() + { + var jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + var indexer = indexerService.GetIndexer((string)postData["indexer"]); + var config = await indexer.GetConfigurationForSetup(); + jsonReply["config"] = config.ToJson(); + jsonReply["name"] = indexer.DisplayName; + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("configure_indexer")] + [HttpPost] + public async Task Configure() + { + JToken jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + string indexerString = (string)postData["indexer"]; + var indexer = indexerService.GetIndexer((string)postData["indexer"]); + jsonReply["name"] = indexer.DisplayName; + await indexer.ApplyConfiguration(postData["config"]); + indexerService.TestIndexer((string)postData["indexer"]); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + if (ex is ExceptionWithConfigData) + { + jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson(); + } + } + return Json(jsonReply); + } + + + + [Route("get_indexers")] + [HttpGet] + public IHttpActionResult Indexers() + { + var jsonReply = new JObject(); + try + { + jsonReply["result"] = "success"; + jsonReply["api_key"] = ApiKey.CurrentKey; + jsonReply["app_version"] = config.GetVersion(); + JArray items = new JArray(); + + foreach (var indexer in indexerService.GetAllIndexers()) + { + var item = new JObject(); + item["id"] = indexer.GetType().Name.ToLowerInvariant(); + item["name"] = indexer.DisplayName; + item["description"] = indexer.DisplayDescription; + item["configured"] = indexer.IsConfigured; + item["site_link"] = indexer.SiteLink; + items.Add(item); + } + jsonReply["items"] = items; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("test_indexer")] + [HttpPost] + public async Task Test() + { + JToken jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + string indexerString = (string)postData["indexer"]; + indexerService.TestIndexer(indexerString); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("delete_indexer")] + [HttpPost] + public async Task Delete() + { + var jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + string indexerString = (string)postData["indexer"]; + indexerService.DeleteIndexer(indexerString); + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("get_sonarr_config")] + [HttpGet] + public IHttpActionResult Sonarr() + { + JObject jsonReply = new JObject(); + try + { + jsonReply["config"] = sonarrApi.GetConfiguration().ToJson(); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("apply_sonarr_config")] + [HttpPost] + public async Task SonarrEdit() + { + var jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + await sonarrApi.ApplyConfiguration(postData); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("test_sonarr")] + [HttpPost] + public async Task SonarrTest() + { + JToken jsonReply = new JObject(); + try + { + await sonarrApi.TestConnection(); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("get_jackett_config")] + [HttpGet] + public IHttpActionResult GetConfig() + { + var jsonReply = new JObject(); + try + { + jsonReply["config"] = config.ReadServerSettingsFile(); + jsonReply["result"] = "success"; + } + catch (CustomException ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("apply_jackett_config")] + [HttpPost] + public async Task SetConfig() + { + var jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + // int port = await WebServer.ApplyPortConfiguration(postData); + jsonReply["result"] = "success"; + // jsonReply["port"] = port; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + + [Route("jackett_restart")] + [HttpPost] + public IHttpActionResult Restart() + { + return null; + } + } +} + diff --git a/src/Jackett/CookieContainerExtensions.cs b/src/Jackett/CookieContainerExtensions.cs index 447c3be30..8d7343a35 100644 --- a/src/Jackett/CookieContainerExtensions.cs +++ b/src/Jackett/CookieContainerExtensions.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Linq; @@ -12,7 +13,7 @@ namespace Jackett public static class CookieContainerExtensions { - public static void FillFromJson(this CookieContainer cookies, Uri uri, JToken json) + public static void FillFromJson(this CookieContainer cookies, Uri uri, JToken json, Logger logger) { if (json["cookies"] != null) { @@ -43,7 +44,7 @@ namespace Jackett } catch (CookieException ex) { - Program.LoggerInstance.Info("(Non-critical) Problem loading cookie {0}, {1}, {2}", uri, c, ex.Message); + logger.Info("(Non-critical) Problem loading cookie {0}, {1}, {2}", uri, c, ex.Message); } } } diff --git a/src/Jackett/ExceptionWithConfigData.cs b/src/Jackett/ExceptionWithConfigData.cs index 60cf8deae..9280d5f14 100644 --- a/src/Jackett/ExceptionWithConfigData.cs +++ b/src/Jackett/ExceptionWithConfigData.cs @@ -1,4 +1,5 @@ -using System; +using Jackett.Models; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/Jackett/IndexerInterface.cs b/src/Jackett/IndexerInterface.cs index 1e4e2e583..f8629b4c4 100644 --- a/src/Jackett/IndexerInterface.cs +++ b/src/Jackett/IndexerInterface.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using Jackett.Models; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Jackett/IndexerManager.cs b/src/Jackett/IndexerManager.cs index e5f3d8f62..edb59d674 100644 --- a/src/Jackett/IndexerManager.cs +++ b/src/Jackett/IndexerManager.cs @@ -8,10 +8,10 @@ using System.Threading.Tasks; namespace Jackett { - public class IndexerManager + /*public class IndexerManager { - static string IndexerConfigDirectory = Path.Combine(Program.AppConfigDirectory, "Indexers"); + static string IndexerConfigDirectory = Path.Combine(WebServer.AppConfigDirectory, "Indexers"); public Dictionary Indexers { get; private set; } @@ -60,7 +60,7 @@ namespace Jackett var fileName = string.Format("Error on {0} for {1}.txt", DateTime.Now.ToString("yyyyMMddHHmmss"), indexer.DisplayName); var spacing = string.Join("", Enumerable.Repeat(Environment.NewLine, 5)); var fileContents = string.Format("{0}{1}{2}", ex, spacing, results); - File.WriteAllText(Path.Combine(Program.AppConfigDirectory, fileName), fileContents); + File.WriteAllText(Path.Combine(WebServer.AppConfigDirectory, fileName), fileContents); } string GetIndexerConfigFilePath(IndexerInterface indexer) @@ -97,11 +97,11 @@ namespace Jackett { var browseQuery = new TorznabQuery(); var results = await indexer.PerformQuery(browseQuery); - Program.LoggerInstance.Debug(string.Format("Found {0} releases from {1}", results.Length, indexer.DisplayName)); + WebServer.LoggerInstance.Debug(string.Format("Found {0} releases from {1}", results.Length, indexer.DisplayName)); if (results.Length == 0) throw new Exception("Found no results while trying to browse this tracker"); } - } + }*/ } diff --git a/src/Jackett/Indexers/AlphaRatio.cs b/src/Jackett/Indexers/AlphaRatio.cs index e4c3c93ec..b75fc7539 100644 --- a/src/Jackett/Indexers/AlphaRatio.cs +++ b/src/Jackett/Indexers/AlphaRatio.cs @@ -9,6 +9,9 @@ using System.Text; using System.Threading.Tasks; using System.Web; using System.Net.Http.Headers; +using Jackett.Utils; +using Jackett.Models; +using NLog; namespace Jackett.Indexers { @@ -50,11 +53,13 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; string cookieHeader; - public AlphaRatio() + public AlphaRatio(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -98,7 +103,7 @@ namespace Jackett.Indexers configSaveData = new JObject(); - if (Program.IsWindows) + if (WebServer.IsWindows) { // If Windows use .net http var response = await client.SendAsync(message); @@ -142,7 +147,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(SiteLink, jsonConfig); + cookies.FillFromJson(SiteLink, jsonConfig, logger); cookieHeader = cookies.GetCookieHeader(SiteLink); IsConfigured = true; } @@ -169,7 +174,7 @@ namespace Jackett.Indexers var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString); string results; - if (Program.IsWindows) + if (WebServer.IsWindows) { var request = CreateHttpRequest(new Uri(episodeSearchUrl)); request.Method = HttpMethod.Get; @@ -237,7 +242,7 @@ namespace Jackett.Indexers public async Task Download(Uri link) { - if (Program.IsWindows) + if (WebServer.IsWindows) { return await client.GetByteArrayAsync(link); } diff --git a/src/Jackett/Indexers/AnimeBytes.cs b/src/Jackett/Indexers/AnimeBytes.cs index 46b744bc6..9726e32e3 100644 --- a/src/Jackett/Indexers/AnimeBytes.cs +++ b/src/Jackett/Indexers/AnimeBytes.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -69,9 +72,11 @@ namespace Jackett.Indexers CookieContainer cookieContainer; HttpClientHandler handler; HttpClient client; + Logger logger; - public AnimeBytes() + public AnimeBytes(Logger l) { + logger = l; IsConfigured = false; cookieContainer = new CookieContainer(); handler = new HttpClientHandler @@ -162,7 +167,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookieContainer.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookieContainer.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; AllowRaws = jsonConfig["raws"].Value(); } diff --git a/src/Jackett/Indexers/BeyondHD.cs b/src/Jackett/Indexers/BeyondHD.cs index f8c4fe955..f7578bf21 100644 --- a/src/Jackett/Indexers/BeyondHD.cs +++ b/src/Jackett/Indexers/BeyondHD.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Linq; @@ -41,9 +44,11 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; - public BeyondHD() + public BeyondHD(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -68,7 +73,7 @@ namespace Jackett.Indexers var jsonCookie = new JObject(); jsonCookie["cookie_header"] = config.CookieHeader; - cookies.FillFromJson(new Uri(BaseUrl), jsonCookie); + cookies.FillFromJson(new Uri(BaseUrl), jsonCookie, logger); var responseContent = await client.GetStringAsync(BaseUrl); @@ -92,7 +97,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/BitHdtv.cs b/src/Jackett/Indexers/BitHdtv.cs index fc16b16f9..b22259c2b 100644 --- a/src/Jackett/Indexers/BitHdtv.cs +++ b/src/Jackett/Indexers/BitHdtv.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -39,9 +42,11 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger loggger; - public BitHdtv() + public BitHdtv(Logger l) { + loggger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -101,7 +106,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, loggger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/BitMeTV.cs b/src/Jackett/Indexers/BitMeTV.cs index 109ff8c03..abed435a9 100644 --- a/src/Jackett/Indexers/BitMeTV.cs +++ b/src/Jackett/Indexers/BitMeTV.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -48,12 +51,14 @@ namespace Jackett CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; public event Action OnSaveConfigurationRequested; public event Action OnResultParsingError; - public BitMeTV() + public BitMeTV(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -122,7 +127,7 @@ namespace Jackett public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/FrenchTorrentDb.cs b/src/Jackett/Indexers/FrenchTorrentDb.cs index 6a2d92528..c2fa7abfc 100644 --- a/src/Jackett/Indexers/FrenchTorrentDb.cs +++ b/src/Jackett/Indexers/FrenchTorrentDb.cs @@ -1,4 +1,6 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; diff --git a/src/Jackett/Indexers/Freshon.cs b/src/Jackett/Indexers/Freshon.cs index 29fcca3f2..936194e19 100644 --- a/src/Jackett/Indexers/Freshon.cs +++ b/src/Jackett/Indexers/Freshon.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -38,9 +41,11 @@ namespace Jackett public Uri SiteLink { get { return new Uri(BaseUrl); } } public event Action OnSaveConfigurationRequested; + Logger logger; - public Freshon() + public Freshon(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -101,7 +106,7 @@ namespace Jackett public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/HDTorrents.cs b/src/Jackett/Indexers/HDTorrents.cs index 6d691d256..342f00053 100644 --- a/src/Jackett/Indexers/HDTorrents.cs +++ b/src/Jackett/Indexers/HDTorrents.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -29,9 +32,11 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; - public HDTorrents() + public HDTorrents(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -122,7 +127,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(SiteLink, jsonConfig); + cookies.FillFromJson(SiteLink, jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/IPTorrents.cs b/src/Jackett/Indexers/IPTorrents.cs index bcf43b311..d72a1fbb9 100644 --- a/src/Jackett/Indexers/IPTorrents.cs +++ b/src/Jackett/Indexers/IPTorrents.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -36,9 +39,11 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; - public IPTorrents() + public IPTorrents(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -110,7 +115,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/TVRage.cs b/src/Jackett/Indexers/Master.cs similarity index 54% rename from src/Jackett/TVRage.cs rename to src/Jackett/Indexers/Master.cs index ed871114e..b563b69cd 100644 --- a/src/Jackett/TVRage.cs +++ b/src/Jackett/Indexers/Master.cs @@ -1,12 +1,16 @@ -using System; +using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Indexers { - class TVRage + class Master { + + + } } diff --git a/src/Jackett/Indexers/MoreThanTV.cs b/src/Jackett/Indexers/MoreThanTV.cs index a05c7ae9f..e2ef6459f 100644 --- a/src/Jackett/Indexers/MoreThanTV.cs +++ b/src/Jackett/Indexers/MoreThanTV.cs @@ -1,5 +1,7 @@ using CsQuery; +using Jackett.Models; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.IO; @@ -48,12 +50,14 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; string cookieHeader; int retries = 3; - public MoreThanTV() + public MoreThanTV(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -89,7 +93,7 @@ namespace Jackett.Indexers var configSaveData = new JObject(); - if (Program.IsWindows) + if (WebServer.IsWindows) { // If Windows use .net http var response = await client.PostAsync(LoginUrl, content); @@ -125,7 +129,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(SiteLink, jsonConfig); + cookies.FillFromJson(SiteLink, jsonConfig, logger); cookieHeader = cookies.GetCookieHeader(SiteLink); IsConfigured = true; } @@ -152,7 +156,7 @@ namespace Jackett.Indexers var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString); string results; - if (Program.IsWindows) + if (WebServer.IsWindows) { results = await client.GetStringAsync(episodeSearchUrl, retries); } @@ -220,7 +224,7 @@ namespace Jackett.Indexers public async Task Download(Uri link) { - if (Program.IsWindows) + if (WebServer.IsWindows) { return await client.GetByteArrayAsync(link); } diff --git a/src/Jackett/Indexers/Rarbg.cs b/src/Jackett/Indexers/Rarbg.cs index ce40972c7..61bf3929c 100644 --- a/src/Jackett/Indexers/Rarbg.cs +++ b/src/Jackett/Indexers/Rarbg.cs @@ -1,4 +1,6 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; diff --git a/src/Jackett/Indexers/SceneAccess.cs b/src/Jackett/Indexers/SceneAccess.cs index d29372658..f4f9053db 100644 --- a/src/Jackett/Indexers/SceneAccess.cs +++ b/src/Jackett/Indexers/SceneAccess.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -46,9 +49,11 @@ namespace Jackett.Indexers HttpClientHandler handler; HttpClient client; string cookieHeader; + Logger logger; - public SceneAccess() + public SceneAccess(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -82,7 +87,7 @@ namespace Jackett.Indexers string responseContent; var configSaveData = new JObject(); - if (Program.IsWindows) + if (WebServer.IsWindows) { // If Windows use .net http var response = await client.PostAsync(LoginUrl, content); @@ -116,7 +121,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); cookieHeader = cookies.GetCookieHeader(SiteLink); IsConfigured = true; } @@ -134,7 +139,7 @@ namespace Jackett.Indexers var searchUrl = string.Format(SearchUrl, searchSection, searchCategory, searchString); string results; - if (Program.IsWindows) + if (WebServer.IsWindows) { results = await client.GetStringAsync(searchUrl); } @@ -190,7 +195,7 @@ namespace Jackett.Indexers public async Task Download(Uri link) { - if (Program.IsWindows) + if (WebServer.IsWindows) { return await client.GetByteArrayAsync(link); } diff --git a/src/Jackett/Indexers/SceneTime.cs b/src/Jackett/Indexers/SceneTime.cs index 853faaffc..4f192bad1 100644 --- a/src/Jackett/Indexers/SceneTime.cs +++ b/src/Jackett/Indexers/SceneTime.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -43,10 +46,12 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; - public SceneTime() + public SceneTime(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -99,7 +104,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/ShowRSS.cs b/src/Jackett/Indexers/ShowRSS.cs index 3702c4e65..415c7bc32 100644 --- a/src/Jackett/Indexers/ShowRSS.cs +++ b/src/Jackett/Indexers/ShowRSS.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using Jackett.Models; +using Jackett.Utils; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; diff --git a/src/Jackett/Indexers/Strike.cs b/src/Jackett/Indexers/Strike.cs index b30eac578..a8e39f06f 100644 --- a/src/Jackett/Indexers/Strike.cs +++ b/src/Jackett/Indexers/Strike.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using Jackett.Models; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; diff --git a/src/Jackett/Indexers/T411.cs b/src/Jackett/Indexers/T411.cs index 450a65867..d763581d2 100644 --- a/src/Jackett/Indexers/T411.cs +++ b/src/Jackett/Indexers/T411.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using Jackett.Models; +using Jackett.Utils; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; diff --git a/src/Jackett/Indexers/ThePirateBay.cs b/src/Jackett/Indexers/ThePirateBay.cs index fa248b68c..0c50851bb 100644 --- a/src/Jackett/Indexers/ThePirateBay.cs +++ b/src/Jackett/Indexers/ThePirateBay.cs @@ -1,4 +1,6 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -108,7 +110,7 @@ namespace Jackett.Indexers string results; - if (Program.IsWindows) + if (WebServer.IsWindows) { results = await client.GetStringAsync(episodeSearchUrl); } diff --git a/src/Jackett/Indexers/TorrentDay.cs b/src/Jackett/Indexers/TorrentDay.cs index a270bc3bc..f71ed7903 100644 --- a/src/Jackett/Indexers/TorrentDay.cs +++ b/src/Jackett/Indexers/TorrentDay.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -45,9 +48,11 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; - public TorrentDay() + public TorrentDay(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -118,7 +123,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/TorrentLeech.cs b/src/Jackett/Indexers/TorrentLeech.cs index c865059a3..95dfe08da 100644 --- a/src/Jackett/Indexers/TorrentLeech.cs +++ b/src/Jackett/Indexers/TorrentLeech.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -43,9 +46,11 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; - public TorrentLeech() + public TorrentLeech(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -101,7 +106,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/TorrentShack.cs b/src/Jackett/Indexers/TorrentShack.cs index d7b88c705..3492cae86 100644 --- a/src/Jackett/Indexers/TorrentShack.cs +++ b/src/Jackett/Indexers/TorrentShack.cs @@ -1,5 +1,8 @@ using CsQuery; +using Jackett.Models; +using Jackett.Utils; using Newtonsoft.Json.Linq; +using NLog; using System; using System.Collections.Generic; using System.Globalization; @@ -41,11 +44,13 @@ namespace Jackett.Indexers CookieContainer cookies; HttpClientHandler handler; HttpClient client; + Logger logger; public bool IsConfigured { get; private set; } - public TorrentShack() + public TorrentShack(Logger l) { + logger = l; IsConfigured = false; cookies = new CookieContainer(); handler = new HttpClientHandler @@ -103,7 +108,7 @@ namespace Jackett.Indexers public void LoadFromSavedConfiguration(JToken jsonConfig) { - cookies.FillFromJson(new Uri(BaseUrl), jsonConfig); + cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger); IsConfigured = true; } diff --git a/src/Jackett/Indexers/Torrentz.cs b/src/Jackett/Indexers/Torrentz.cs index d822f93a8..3c3cbe1de 100644 --- a/src/Jackett/Indexers/Torrentz.cs +++ b/src/Jackett/Indexers/Torrentz.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using Jackett.Models; +using Jackett.Utils; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index c0044dbf4..b04a292f6 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -5,7 +5,7 @@ Debug AnyCPU {E636D5F8-68B4-4903-B4ED-CCFD9C9E899F} - WinExe + Library Properties Jackett Jackett @@ -48,25 +48,77 @@ 4 - jacket_large.ico + + - Jackett.Program + + + + ..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + True + + + ..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll + + + False + ..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll + + + ..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll + ..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\Microsoft.Owin.FileSystems.3.0.1\lib\net45\Microsoft.Owin.FileSystems.dll + True + + + ..\packages\Microsoft.Owin.Host.HttpListener.2.0.2\lib\net45\Microsoft.Owin.Host.HttpListener.dll + True + + + ..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll + True + + + ..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll + True + ..\packages\NLog.Windows.Forms.2.0.0.0\lib\net35\NLog.Windows.Forms.dll True + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll + True + @@ -81,14 +133,15 @@ - - - - - - - - + + + + + + + + + @@ -102,6 +155,7 @@ + @@ -115,30 +169,27 @@ - - Form - - - Main.cs - - - + + True True Resources.resx - - + + - - - - + + + + + + + @@ -151,9 +202,6 @@ - - Main.cs - ResXFileCodeGenerator Resources.Designer.cs @@ -196,7 +244,6 @@ PreserveNewest - PreserveNewest @@ -242,7 +289,6 @@ PreserveNewest - PreserveNewest @@ -327,4 +373,4 @@ - + \ No newline at end of file diff --git a/src/Jackett/JackettModule.cs b/src/Jackett/JackettModule.cs new file mode 100644 index 000000000..1aa607ebb --- /dev/null +++ b/src/Jackett/JackettModule.cs @@ -0,0 +1,29 @@ +using Autofac; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Autofac.Integration.WebApi; + +namespace Jackett +{ + public class JackettModule: Module + { + protected override void Load(ContainerBuilder builder) + { + // Just register everything! + var thisAssembly = typeof(JackettModule).Assembly; + builder.RegisterAssemblyTypes(thisAssembly).AsImplementedInterfaces().SingleInstance(); + builder.RegisterApiControllers(thisAssembly).InstancePerRequest(); + + // Register indexers + foreach(var indexer in thisAssembly.GetTypes() + .Where(p => typeof(IndexerInterface).IsAssignableFrom(p) && !p.IsInterface) + .ToArray()) + { + builder.RegisterType(indexer).Named(indexer.Name.ToLowerInvariant()).SingleInstance(); + } + } + } +} diff --git a/src/Jackett/ApiKey.cs b/src/Jackett/Models/ApiKey.cs similarity index 96% rename from src/Jackett/ApiKey.cs rename to src/Jackett/Models/ApiKey.cs index 86848899a..aec36e33e 100644 --- a/src/Jackett/ApiKey.cs +++ b/src/Jackett/Models/ApiKey.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public class ApiKey { diff --git a/src/Jackett/CachedResult.cs b/src/Jackett/Models/CachedResult.cs similarity index 96% rename from src/Jackett/CachedResult.cs rename to src/Jackett/Models/CachedResult.cs index 925d9a139..24feea319 100644 --- a/src/Jackett/CachedResult.cs +++ b/src/Jackett/Models/CachedResult.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public class CachedResult { diff --git a/src/Jackett/ChannelInfo.cs b/src/Jackett/Models/ChannelInfo.cs similarity index 96% rename from src/Jackett/ChannelInfo.cs rename to src/Jackett/Models/ChannelInfo.cs index a42421e01..e9afa5112 100644 --- a/src/Jackett/ChannelInfo.cs +++ b/src/Jackett/Models/ChannelInfo.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public class ChannelInfo { diff --git a/src/Jackett/ConfigurationData.cs b/src/Jackett/Models/ConfigurationData.cs similarity index 99% rename from src/Jackett/ConfigurationData.cs rename to src/Jackett/Models/ConfigurationData.cs index 971698b0c..41b6b0055 100644 --- a/src/Jackett/ConfigurationData.cs +++ b/src/Jackett/Models/ConfigurationData.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public abstract class ConfigurationData { diff --git a/src/Jackett/ConfigurationDataBasicLogin.cs b/src/Jackett/Models/ConfigurationDataBasicLogin.cs similarity index 96% rename from src/Jackett/ConfigurationDataBasicLogin.cs rename to src/Jackett/Models/ConfigurationDataBasicLogin.cs index 19a2ab0ff..66afbffe3 100644 --- a/src/Jackett/ConfigurationDataBasicLogin.cs +++ b/src/Jackett/Models/ConfigurationDataBasicLogin.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public class ConfigurationDataBasicLogin : ConfigurationData { diff --git a/src/Jackett/ConfigurationDataCookie.cs b/src/Jackett/Models/ConfigurationDataCookie.cs similarity index 98% rename from src/Jackett/ConfigurationDataCookie.cs rename to src/Jackett/Models/ConfigurationDataCookie.cs index c184dfc5a..afa3e46e5 100644 --- a/src/Jackett/ConfigurationDataCookie.cs +++ b/src/Jackett/Models/ConfigurationDataCookie.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public class ConfigurationDataCookie : ConfigurationData diff --git a/src/Jackett/ConfigurationDataUrl.cs b/src/Jackett/Models/ConfigurationDataUrl.cs similarity index 96% rename from src/Jackett/ConfigurationDataUrl.cs rename to src/Jackett/Models/ConfigurationDataUrl.cs index e302cf2fd..08e80a76a 100644 --- a/src/Jackett/ConfigurationDataUrl.cs +++ b/src/Jackett/Models/ConfigurationDataUrl.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public class ConfigurationDataUrl : ConfigurationData { diff --git a/src/Jackett/ReleaseInfo.cs b/src/Jackett/Models/ReleaseInfo.cs similarity index 99% rename from src/Jackett/ReleaseInfo.cs rename to src/Jackett/Models/ReleaseInfo.cs index a6845102a..b81d810e7 100644 --- a/src/Jackett/ReleaseInfo.cs +++ b/src/Jackett/Models/ReleaseInfo.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public class ReleaseInfo: ICloneable diff --git a/src/Jackett/ResultPage.cs b/src/Jackett/Models/ResultPage.cs similarity index 99% rename from src/Jackett/ResultPage.cs rename to src/Jackett/Models/ResultPage.cs index f9d4b9b2e..fe238e225 100644 --- a/src/Jackett/ResultPage.cs +++ b/src/Jackett/Models/ResultPage.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; -namespace Jackett +namespace Jackett.Models { public class ResultPage { diff --git a/src/Jackett/TorznabQuery.cs b/src/Jackett/Models/TorznabQuery.cs similarity index 97% rename from src/Jackett/TorznabQuery.cs rename to src/Jackett/Models/TorznabQuery.cs index 06a5651d9..40e63f794 100644 --- a/src/Jackett/TorznabQuery.cs +++ b/src/Jackett/Models/TorznabQuery.cs @@ -1,4 +1,5 @@ -using System; +using Jackett.Utils; +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; @@ -6,7 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Models { public class TorznabQuery { diff --git a/src/Jackett/Program.cs b/src/Jackett/Program.cs deleted file mode 100644 index 4d7134dbd..000000000 --- a/src/Jackett/Program.cs +++ /dev/null @@ -1,179 +0,0 @@ -using Jackett.Indexers; -using Newtonsoft.Json.Linq; -using NLog; -using NLog.Config; -using NLog.Targets; -using NLog.Windows.Forms; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace Jackett -{ - class Program - { - public static string AppConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jackett"); - - public static Server ServerInstance { get; private set; } - - public static bool IsFirstRun { get; private set; } - - public static Logger LoggerInstance { get; private set; } - - public static ManualResetEvent ExitEvent { get; private set; } - - public static bool IsWindows { get { return Environment.OSVersion.Platform == PlatformID.Win32NT; } } - - - - static void Main(string[] args) - { - ExitEvent = new ManualResetEvent(false); - - MigrateSettingsDirectory(); - - try - { - if (!Directory.Exists(AppConfigDirectory)) - { - IsFirstRun = true; - Directory.CreateDirectory(AppConfigDirectory); - } - Console.WriteLine("App config/log directory: " + AppConfigDirectory); - } - catch (Exception ex) - { - MessageBox.Show("Could not create settings directory. " + ex.Message); - Application.Exit(); - return; - } - - var logConfig = new LoggingConfiguration(); - - var logFile = new FileTarget(); - logConfig.AddTarget("file", logFile); - logFile.Layout = "${longdate} ${level} ${message} \n ${exception:format=ToString}\n"; - logFile.FileName = Path.Combine(AppConfigDirectory, "log.txt"); - logFile.ArchiveFileName = "log.{#####}.txt"; - logFile.ArchiveAboveSize = 500000; - logFile.MaxArchiveFiles = 1; - logFile.KeepFileOpen = false; - logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence; - var logFileRule = new LoggingRule("*", LogLevel.Debug, logFile); - logConfig.LoggingRules.Add(logFileRule); - - if (Program.IsWindows) - { -#if !__MonoCS__ - var logAlert = new MessageBoxTarget(); - logConfig.AddTarget("alert", logAlert); - logAlert.Layout = "${message}"; - logAlert.Caption = "Alert"; - var logAlertRule = new LoggingRule("*", LogLevel.Fatal, logAlert); - logConfig.LoggingRules.Add(logAlertRule); -#endif - } - - var logConsole = new ConsoleTarget(); - logConfig.AddTarget("console", logConsole); - logConsole.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}"; - var logConsoleRule = new LoggingRule("*", LogLevel.Debug, logConsole); - logConfig.LoggingRules.Add(logConsoleRule); - - LogManager.Configuration = logConfig; - LoggerInstance = LogManager.GetCurrentClassLogger(); - - ReadSettingsFile(); - - var serverTask = Task.Run(async () => - { - ServerInstance = new Server(); - await ServerInstance.Start(); - }); - - try - { - if (Program.IsWindows) - { -#if !__MonoCS__ - Application.Run(new Main()); -#endif - } - } - catch (Exception) - { - - } - - Console.WriteLine("Running in headless mode."); - - - - Task.WaitAll(serverTask); - Console.WriteLine("Server thread exit"); - } - - public static void RestartServer() - { - - ServerInstance.Stop(); - ServerInstance = null; - var serverTask = Task.Run(async () => - { - ServerInstance = new Server(); - await ServerInstance.Start(); - }); - Task.WaitAll(serverTask); - } - - static void MigrateSettingsDirectory() - { - try - { - string oldDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett"); - if (Directory.Exists(oldDir) && !Directory.Exists(AppConfigDirectory)) - { - Directory.Move(oldDir, AppConfigDirectory); - } - } - catch (Exception ex) - { - Console.WriteLine("ERROR could not migrate settings directory " + ex); - } - } - - static void ReadSettingsFile() - { - var path = Path.Combine(AppConfigDirectory, "config.json"); - if (!File.Exists(path)) - { - JObject f = new JObject(); - f.Add("port", Server.DefaultPort); - f.Add("public", true); - File.WriteAllText(path, f.ToString()); - } - - var configJson = JObject.Parse(File.ReadAllText(path)); - int port = (int)configJson.GetValue("port"); - Server.Port = port; - - Server.ListenPublic = (bool)configJson.GetValue("public"); - - Console.WriteLine("Config file path: " + path); - } - - static public void RestartAsAdmin() - { - var startInfo = new ProcessStartInfo(Application.ExecutablePath.ToString()) { Verb = "runas" }; - Process.Start(startInfo); - Environment.Exit(0); - } - } -} diff --git a/src/Jackett/Server.cs b/src/Jackett/Server.cs index ed4b26111..8d7732ab0 100644 --- a/src/Jackett/Server.cs +++ b/src/Jackett/Server.cs @@ -1,305 +1,93 @@ -using Newtonsoft.Json.Linq; +using Autofac; +using Jackett.Services; +using Microsoft.Owin.Hosting; +using NLog; +using NLog.Config; +using NLog.Targets; +using NLog.Windows.Forms; using System; using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Net; using System.Text; -using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Web; -using System.Windows.Forms; namespace Jackett { public class Server { - public const int DefaultPort = 9117; - public static int Port = DefaultPort; - public static bool ListenPublic = true; + private static IContainer container = null; + private static string baseAddress = "http://localhost:9000/"; + private static IDisposable _server = null; - HttpListener listener; - IndexerManager indexerManager; - WebApi webApi; - SonarrApi sonarrApi; - - - public Server() + static Server() { - // Allow all SSL.. sucks I know but mono on linux is having problems without it.. - ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - - ReadServerSettingsFile(); - LoadApiKey(); + var builder = new ContainerBuilder(); + builder.RegisterModule(); + container = builder.Build(); - indexerManager = new IndexerManager(); - sonarrApi = new SonarrApi(); - webApi = new WebApi(indexerManager, sonarrApi); - + // Register the container in itself to allow for late resolves + var secondaryBuilder = new ContainerBuilder(); + secondaryBuilder.RegisterInstance(container); + SetupLogging(secondaryBuilder, container.Resolve()); + secondaryBuilder.Update(container); } - void LoadApiKey() + private static void SetupLogging(ContainerBuilder builder, IConfigurationService config) { - var apiKeyFile = Path.Combine(Program.AppConfigDirectory, "api_key.txt"); - if (File.Exists(apiKeyFile)) - ApiKey.CurrentKey = File.ReadAllText(apiKeyFile).Trim(); - else + var logConfig = new LoggingConfiguration(); + + var logFile = new FileTarget(); + logConfig.AddTarget("file", logFile); + logFile.Layout = "${longdate} ${level} ${message} \n ${exception:format=ToString}\n"; + logFile.FileName = Path.Combine(config.GetAppDataFolder(), "log.txt"); + logFile.ArchiveFileName = "log.{#####}.txt"; + logFile.ArchiveAboveSize = 500000; + logFile.MaxArchiveFiles = 1; + logFile.KeepFileOpen = false; + logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence; + var logFileRule = new LoggingRule("*", LogLevel.Debug, logFile); + logConfig.LoggingRules.Add(logFileRule); + + /* if (WebServer.IsWindows) { - ApiKey.CurrentKey = ApiKey.Generate(); - File.WriteAllText(apiKeyFile, ApiKey.CurrentKey); +#if !__MonoCS__ + var logAlert = new MessageBoxTarget(); + logConfig.AddTarget("alert", logAlert); + logAlert.Layout = "${message}"; + logAlert.Caption = "Alert"; + var logAlertRule = new LoggingRule("*", LogLevel.Fatal, logAlert); + logConfig.LoggingRules.Add(logAlertRule); +#endif + }*/ + + var logConsole = new ConsoleTarget(); + logConfig.AddTarget("console", logConsole); + logConsole.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}"; + var logConsoleRule = new LoggingRule("*", LogLevel.Debug, logConsole); + logConfig.LoggingRules.Add(logConsoleRule); + + LogManager.Configuration = logConfig; + builder.RegisterInstance(LogManager.GetCurrentClassLogger()).SingleInstance(); + } + + public static void Start() + { + _server = WebApp.Start(url: baseAddress); + } + + public static void Stop() + { + if (_server != null) + { + _server.Dispose(); } } - public async Task Start() + public static IContainer GetContainer() { - Program.LoggerInstance.Info("Starting HTTP server on port " + Port + " listening " + (ListenPublic ? "publicly" : "privately")); - - try - { - listener = new HttpListener(); - - if (ListenPublic) - { - listener.Prefixes.Add(string.Format("http://*:{0}/", Port)); - } - else - { - listener.Prefixes.Add(string.Format("http://127.0.0.1:{0}/", Port)); - } - - listener.Start(); - webApi.server = this; - } - catch (HttpListenerException ex) - { - if (ex.ErrorCode == 5) - { - var errorStr = "App must be ran as admin for permission to use port " - + Port + Environment.NewLine + "Restart app with admin privileges?"; - if (Program.IsWindows) - { - var dialogResult = MessageBox.Show(errorStr, "Error", MessageBoxButtons.YesNo); - if (dialogResult == DialogResult.No) - { - Application.Exit(); - return; - } - else - { - Program.RestartAsAdmin(); - } - } - } - else - { - Program.LoggerInstance.FatalException("Failed to start HTTP server. " + ex.Message, ex); - } - } - catch (Exception ex) - { - Program.LoggerInstance.ErrorException("Error starting HTTP server: " + ex.Message, ex); - return; - } - - Program.LoggerInstance.Info("Server started on port " + Port); - Program.LoggerInstance.Info("Accepting only requests from local system: " + (!ListenPublic)); - - while (true) - { - Exception error = null; - try - { - error = null; - var context = await listener.GetContextAsync(); - ProcessHttpRequest(context); - } - catch (ObjectDisposedException ex) - { - Program.LoggerInstance.ErrorException("Critical error, HTTP listener was destroyed", ex); - Process.GetCurrentProcess().Kill(); - } - catch (Exception ex) - { - error = ex; - Program.LoggerInstance.ErrorException("Error processing HTTP request", ex); - } - - if (error != null) - await Task.Delay(TimeSpan.FromSeconds(5)); - } + return container; } - - public void Stop() - { - listener.Stop(); - listener.Abort(); - } - - async void ProcessHttpRequest(HttpListenerContext context) - { - Program.LoggerInstance.Trace("Received request: " + context.Request.Url.ToString()); - Exception exception = null; - try - { - if (await webApi.HandleRequest(context)) - { - - } - else if (context.Request.Url.AbsolutePath.StartsWith("/api/")) - { - await ProcessTorznab(context); - } - else - { - var responseBytes = Encoding.UTF8.GetBytes("Invalid request"); - await context.Response.OutputStream.WriteAsync(responseBytes, 0, responseBytes.Length); - } - } - catch (Exception ex) - { - exception = ex; - Program.LoggerInstance.ErrorException(ex.Message + ex.ToString(), ex); - } - - if (exception != null) - { - try - { - var errorBytes = Encoding.UTF8.GetBytes(exception.Message); - await context.Response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length); - } - catch (Exception) - { - } - } - - try - { - context.Response.Close(); - } - catch (Exception) - { - } - - } - - async Task ProcessTorznab(HttpListenerContext context) - { - - var query = HttpUtility.ParseQueryString(context.Request.Url.Query); - var inputStream = context.Request.InputStream; - - var indexerId = context.Request.Url.Segments[2].TrimEnd('/').ToLower(); - var indexer = indexerManager.GetIndexer(indexerId); - - if (context.Request.Url.Segments.Length > 4 && context.Request.Url.Segments[3] == "download/") - { - var downloadSegment = HttpServerUtility.UrlTokenDecode(context.Request.Url.Segments[4].TrimEnd('/')); - var downloadLink = Encoding.UTF8.GetString(downloadSegment); - var downloadBytes = await indexer.Download(new Uri(downloadLink)); - await context.Response.OutputStream.WriteAsync(downloadBytes, 0, downloadBytes.Length); - return; - } - - var torznabQuery = TorznabQuery.FromHttpQuery(query); - - if (torznabQuery.RageID != 0) - torznabQuery.ShowTitles = await sonarrApi.GetShowTitle(torznabQuery.RageID); - else if (!string.IsNullOrEmpty(torznabQuery.SearchTerm)) - torznabQuery.ShowTitles = new string[] { torznabQuery.SearchTerm }; - - var releases = await indexer.PerformQuery(torznabQuery); - - Program.LoggerInstance.Debug(string.Format("Found {0} releases from {1}", releases.Length, indexer.DisplayName)); - - var severUrl = string.Format("{0}://{1}:{2}/", context.Request.Url.Scheme, context.Request.Url.Host, context.Request.Url.Port); - - var resultPage = new ResultPage(new ChannelInfo - { - Title = indexer.DisplayName, - Description = indexer.DisplayDescription, - Link = indexer.SiteLink, - ImageUrl = new Uri(severUrl + "logos/" + indexerId + ".png"), - ImageTitle = indexer.DisplayName, - ImageLink = indexer.SiteLink, - ImageDescription = indexer.DisplayName - }); - - // add Jackett proxy to download links... - foreach (var release in releases) - { - if (release.Link == null || release.Link.Scheme == "magnet") - continue; - var originalLink = release.Link; - var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/download.torrent"; - var proxyLink = string.Format("{0}api/{1}/download/{2}", severUrl, indexerId, encodedLink); - release.Link = new Uri(proxyLink); - } - - resultPage.Releases.AddRange(releases); - - var xml = resultPage.ToXml(new Uri(severUrl)); - - var responseBytes = Encoding.UTF8.GetBytes(xml); - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.ContentLength64 = responseBytes.LongLength; - context.Response.ContentType = "application/rss+xml"; - await context.Response.OutputStream.WriteAsync(responseBytes, 0, responseBytes.Length); - - } - - - - - public JObject ReadServerSettingsFile() - { - var path = ServerConfigFile; - JObject jsonReply = new JObject(); - if (File.Exists(path)) - { - jsonReply = JObject.Parse(File.ReadAllText(path)); - Port = (int)jsonReply["port"]; - ListenPublic = (bool)jsonReply["public"]; - } - else - { - jsonReply["port"] = Port; - jsonReply["public"] = ListenPublic; - } - return jsonReply; - } - - public Task ApplyPortConfiguration(JToken json) - { - JObject jsonObject = (JObject)json; - JToken jJackettPort = jsonObject.GetValue("port"); - int jackettPort; - if (!ServerUtil.IsPort(jJackettPort.ToString())) - throw new CustomException("The value entered is not a valid port"); - else - jackettPort = int.Parse(jJackettPort.ToString()); - - if (jackettPort == Port) - throw new CustomException("The current port is the same as the one being used now."); - else if (ServerUtil.RestrictedPorts.Contains(jackettPort)) - throw new CustomException("This port is not allowed due to it not being safe."); - SaveSettings(jackettPort); - - return Task.FromResult(jackettPort); - } - - private static string ServerConfigFile = Path.Combine(Program.AppConfigDirectory, "config.json"); - - private void SaveSettings(int jacketPort) - { - JObject json = new JObject(); - json["port"] = jacketPort; - json["public"] = ListenPublic; - File.WriteAllText(ServerConfigFile, json.ToString()); - } - } } diff --git a/src/Jackett/Services/ConfigurationService.cs b/src/Jackett/Services/ConfigurationService.cs new file mode 100644 index 000000000..d01cc6477 --- /dev/null +++ b/src/Jackett/Services/ConfigurationService.cs @@ -0,0 +1,84 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Jackett.Services +{ + public interface IConfigurationService + { + string GetContentFolder(); + string GetVersion(); + string GetIndexerConfigDir(); + string GetAppDataFolder(); + JObject ReadServerSettingsFile(); + string GetSonarrConfigFile(); + } + + public class ConfigurationService: IConfigurationService + { + public string GetContentFolder() + { + var baseDir = Path.GetDirectoryName(Application.ExecutablePath); + // If we are debugging we can use the non copied content. + if (Debugger.IsAttached) + { + return Path.Combine(baseDir, "..\\..\\..\\Jackett\\WebContent"); + } + else + { + return Path.Combine(baseDir, "WebContent"); + } + } + + public string GetVersion() + { + return Assembly.GetExecutingAssembly().GetName().Version.ToString(); + } + + public string GetAppDataFolder() + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jackett"); ; + } + + public string GetIndexerConfigDir() + { + return Path.Combine(GetAppDataFolder(), "Indexers"); + } + + public string GetConfigFile() + { + return Path.Combine(GetAppDataFolder(), "config.json"); + } + + public string GetSonarrConfigFile() + { + return Path.Combine(GetAppDataFolder(), "sonarr_api.json"); + } + + + public JObject ReadServerSettingsFile() + { + var path = GetConfigFile(); + JObject jsonReply = new JObject(); + if (File.Exists(path)) + { + jsonReply = JObject.Parse(File.ReadAllText(path)); + // Port = (int)jsonReply["port"]; + // ListenPublic = (bool)jsonReply["public"]; + } + else + { + // jsonReply["port"] = Port; + // jsonReply["public"] = ListenPublic; + } + return jsonReply; + } + } +} diff --git a/src/Jackett/Services/IndexerManagerService.cs b/src/Jackett/Services/IndexerManagerService.cs new file mode 100644 index 000000000..b5e1fa966 --- /dev/null +++ b/src/Jackett/Services/IndexerManagerService.cs @@ -0,0 +1,68 @@ +using Autofac; +using Jackett.Models; +using NLog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Services +{ + public interface IIndexerManagerService + { + void TestIndexer(string name); + void DeleteIndexer(string name); + IndexerInterface GetIndexer(string name); + IEnumerable GetAllIndexers(); + } + + public class IndexerManagerService : IIndexerManagerService + { + private IContainer container; + private IConfigurationService configService; + private Logger logger; + + public IndexerManagerService(IContainer c, IConfigurationService config, Logger l) + { + container = c; + configService = config; + logger = l; + } + + public IndexerInterface GetIndexer(string name) + { + return container.ResolveNamed(name.ToLowerInvariant()); + } + + public IEnumerable GetAllIndexers() + { + return container.Resolve>().OrderBy(_ => _.DisplayName); + } + + public async void TestIndexer(string name) + { + var indexer = GetIndexer(name); + var browseQuery = new TorznabQuery(); + var results = await indexer.PerformQuery(browseQuery); + logger.Debug(string.Format("Found {0} releases from {1}", results.Length, indexer.DisplayName)); + if (results.Length == 0) + throw new Exception("Found no results while trying to browse this tracker"); + } + + public void DeleteIndexer(string name) + { + var indexer = GetIndexer(name); + var configPath = configService.GetIndexerConfigDir(); + File.Delete(configPath); + //Indexers.Remove(name); + //LoadMissingIndexers(); + } + + private string GetIndexerConfigFilePath(IndexerInterface indexer) + { + return Path.Combine(configService.GetIndexerConfigDir(), indexer.GetType().Name.ToLower() + ".json"); + } + } +} diff --git a/src/Jackett/SonarApi.cs b/src/Jackett/Services/SonarApi.cs similarity index 87% rename from src/Jackett/SonarApi.cs rename to src/Jackett/Services/SonarApi.cs index cf7178c91..ed54e6b2c 100644 --- a/src/Jackett/SonarApi.cs +++ b/src/Jackett/Services/SonarApi.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json.Linq; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -11,7 +14,14 @@ using System.Threading.Tasks; namespace Jackett { - public class SonarrApi + public interface ISonarrApi + { + Task TestConnection(); + SonarrApi.ConfigurationSonarr GetConfiguration(); + Task ApplyConfiguration(JToken configJson); + } + + public class SonarrApi: ISonarrApi { public class ConfigurationSonarr : ConfigurationData { @@ -36,7 +46,7 @@ namespace Jackett } - static string SonarrConfigFile = Path.Combine(Program.AppConfigDirectory, "sonarr_api.json"); + string Host; int Port; @@ -48,7 +58,9 @@ namespace Jackett ConcurrentDictionary IdNameMappings; - public SonarrApi() + private IConfigurationService configService; + + public SonarrApi(IConfigurationService c) { LoadSettings(); @@ -62,6 +74,8 @@ namespace Jackett client = new HttpClient(handler); IdNameMappings = new ConcurrentDictionary(); + + configService = c; } async Task ReloadNameMappings(string host, int port, string apiKey) @@ -101,9 +115,9 @@ namespace Jackett { try { - if (File.Exists(SonarrConfigFile)) + if (File.Exists(configService.GetSonarrConfigFile())) { - var json = JObject.Parse(File.ReadAllText(SonarrConfigFile)); + var json = JObject.Parse(File.ReadAllText(configService.GetSonarrConfigFile())); Host = (string)json["host"]; Port = (int)json["port"]; ApiKey = (string)json["api_key"]; @@ -118,7 +132,7 @@ namespace Jackett json["host"] = Host; json["port"] = Port; json["api_key"] = ApiKey; - File.WriteAllText(SonarrConfigFile, json.ToString()); + File.WriteAllText(configService.GetSonarrConfigFile(), json.ToString()); } public ConfigurationSonarr GetConfiguration() diff --git a/src/Jackett/Startup.cs b/src/Jackett/Startup.cs new file mode 100644 index 000000000..e30cfb85d --- /dev/null +++ b/src/Jackett/Startup.cs @@ -0,0 +1,52 @@ +using Owin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Web.Http; +using Autofac.Integration.WebApi; +using Microsoft.Owin; +using Jackett; +using Microsoft.Owin.StaticFiles; +using Microsoft.Owin.FileSystems; +using Autofac; +using Jackett.Services; + +[assembly: OwinStartup(typeof(Startup))] +namespace Jackett +{ + public class Startup + { + public void Configuration(IAppBuilder appBuilder) + { + // Configure Web API for self-host. + var config = new HttpConfiguration(); + + config.DependencyResolver = new AutofacWebApiDependencyResolver(Server.GetContainer()); + + + + // Enable attribute based routing + // http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2 + config.MapHttpAttributeRoutes(); + + config.Routes.MapHttpRoute( + name: "Content", + routeTemplate: "{controller}/{action}", + defaults: new { controller = "Admin"} + ); + + appBuilder.UseFileServer(new FileServerOptions + { + RequestPath = new PathString(string.Empty), + FileSystem = new PhysicalFileSystem(Server.GetContainer().Resolve().GetContentFolder()), + EnableDirectoryBrowsing = true, + }); + + appBuilder.UseWebApi(config); + + } + } +} diff --git a/src/Jackett/BrowserUtil.cs b/src/Jackett/Utils/BrowserUtil.cs similarity index 94% rename from src/Jackett/BrowserUtil.cs rename to src/Jackett/Utils/BrowserUtil.cs index 82154b176..669b1e346 100644 --- a/src/Jackett/BrowserUtil.cs +++ b/src/Jackett/Utils/BrowserUtil.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Utils { public static class BrowserUtil { diff --git a/src/Jackett/ParseUtil.cs b/src/Jackett/Utils/ParseUtil.cs similarity index 98% rename from src/Jackett/ParseUtil.cs rename to src/Jackett/Utils/ParseUtil.cs index 9b62c0027..c96e2be7a 100644 --- a/src/Jackett/ParseUtil.cs +++ b/src/Jackett/Utils/ParseUtil.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Utils { public static class ParseUtil { diff --git a/src/Jackett/ServerUtil.cs b/src/Jackett/Utils/ServerUtil.cs similarity index 99% rename from src/Jackett/ServerUtil.cs rename to src/Jackett/Utils/ServerUtil.cs index 8f3e6c02f..a65f1f2f7 100644 --- a/src/Jackett/ServerUtil.cs +++ b/src/Jackett/Utils/ServerUtil.cs @@ -5,7 +5,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace Jackett +namespace Jackett.Utils { public static class ServerUtil { diff --git a/src/Jackett/WebApi.cs b/src/Jackett/WebApi.cs index ef8f46c0e..8ec3ca645 100644 --- a/src/Jackett/WebApi.cs +++ b/src/Jackett/WebApi.cs @@ -12,11 +12,11 @@ using System.Web; namespace Jackett { - public class WebApi + /*public class WebApi { static string WebContentFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebContent"); static string[] StaticFiles = Directory.EnumerateFiles(WebContentFolder, "*", SearchOption.AllDirectories).ToArray(); - public Server server; + public WebServer server; public enum WebApiMethod { @@ -140,8 +140,8 @@ namespace Jackett case WebApiMethod.GetJackettConfig: handlerTask = HandleJackettConfig; break; - case WebApiMethod.JackettRestart: - handlerTask = HandleJackettRestart; + // case WebApiMethod.JackettRestart: + // handlerTask = HandleJackettRestart; break; default: handlerTask = HandleInvalidApiMethod; @@ -341,7 +341,7 @@ namespace Jackett JObject jsonReply = new JObject(); try { - jsonReply["config"] = server.ReadServerSettingsFile(); + jsonReply["config"] = WebServer.ReadServerSettingsFile(); jsonReply["result"] = "success"; } catch (CustomException ex) @@ -364,7 +364,7 @@ namespace Jackett try { var postData = await ReadPostDataJson(context.Request.InputStream); - int port = await server.ApplyPortConfiguration(postData); + int port = await WebServer.ApplyPortConfiguration(postData); jsonReply["result"] = "success"; jsonReply["port"] = port; } @@ -378,10 +378,8 @@ namespace Jackett async Task HandleJackettRestart(HttpListenerContext context) { - Program.RestartServer(); - return null; + // WebServer.RestartServer(); + * null; } - - - } + }*/ } diff --git a/src/Jackett/WebContent/custom.js b/src/Jackett/WebContent/custom.js index 35dd07e04..74dc7a529 100644 --- a/src/Jackett/WebContent/custom.js +++ b/src/Jackett/WebContent/custom.js @@ -14,14 +14,14 @@ $("#change-jackett-port").click(function () { var jackett_port = $("#jackett-port").val(); var jsonObject = JSON.parse('{"port":"' + jackett_port + '"}'); - var jqxhr = $.post("apply_jackett_config", JSON.stringify(jsonObject), function (data) { + var jqxhr = $.post("admin/apply_jackett_config", JSON.stringify(jsonObject), function (data) { if (data.result == "error") { doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); return; } else { doNotify("The port has been changed. Jackett will now restart...", "success", "glyphicon glyphicon-ok"); - var jqxhr0 = $.post("jackett_restart", null, function (data_restart) { }); + var jqxhr0 = $.post("admin/jackett_restart", null, function (data_restart) { }); window.setTimeout(function () { url = window.location.href; @@ -36,7 +36,7 @@ $("#change-jackett-port").click(function () { }); function getJackettConfig(callback) { - var jqxhr = $.get("get_jackett_config", function (data) { + var jqxhr = $.get("admin/get_jackett_config", function (data) { callback(data); }).fail(function () { @@ -66,7 +66,7 @@ function loadSonarrInfo() { } function getSonarrConfig(callback) { - var jqxhr = $.get("get_sonarr_config", function (data) { + var jqxhr = $.get("admin/get_sonarr_config", function (data) { callback(data); }).fail(function () { doNotify("Error loading Sonarr API configuration, request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); @@ -74,7 +74,7 @@ function getSonarrConfig(callback) { } $("#sonarr-test").click(function () { - var jqxhr = $.get("get_indexers", function (data) { + var jqxhr = $.get("admin/get_indexers", function (data) { if (data.result == "error") doNotify("Test failed for Sonarr API\n" + data.error, "danger", "glyphicon glyphicon-alert"); else @@ -98,7 +98,7 @@ $("#sonarr-settings").click(function () { $goButton.prop('disabled', true); $goButton.html($('#templates > .spinner')[0].outerHTML); - var jqxhr = $.post("apply_sonarr_config", JSON.stringify(data), function (data) { + var jqxhr = $.post("admin/apply_sonarr_config", JSON.stringify(data), function (data) { if (data.result == "error") { if (data.config) { populateSetupForm(data.indexer, data.name, data.config); @@ -128,7 +128,7 @@ function reloadIndexers() { $('#indexers').hide(); $('#indexers > .indexer').remove(); $('#unconfigured-indexers').empty(); - var jqxhr = $.get("get_indexers", function (data) { + var jqxhr = $.get("admin/get_indexers", function (data) { $("#api-key-input").val(data.api_key); $("#app-version").html(data.app_version); displayIndexers(data.items); @@ -163,7 +163,7 @@ function prepareDeleteButtons() { var $btn = $(btn); var id = $btn.data("id"); $btn.click(function () { - var jqxhr = $.post("delete_indexer", JSON.stringify({ indexer: id }), function (data) { + var jqxhr = $.post("admin/delete_indexer", JSON.stringify({ indexer: id }), function (data) { if (data.result == "error") { doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert"); } @@ -195,7 +195,7 @@ function prepareTestButtons() { var id = $btn.data("id"); $btn.click(function () { doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer"); - var jqxhr = $.post("test_indexer", JSON.stringify({ indexer: id }), function (data) { + var jqxhr = $.post("admin/test_indexer", JSON.stringify({ indexer: id }), function (data) { if (data.result == "error") { doNotify("Test failed for " + data.name + "\n" + data.error, "danger", "glyphicon glyphicon-alert"); } @@ -211,7 +211,7 @@ function prepareTestButtons() { function displayIndexerSetup(id) { - var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: id }), function (data) { + var jqxhr = $.post("admin/get_config_form", JSON.stringify({ indexer: id }), function (data) { if (data.result == "error") { doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); return; @@ -281,7 +281,7 @@ function populateSetupForm(indexerId, name, config) { $goButton.prop('disabled', true); $goButton.html($('#templates > .spinner')[0].outerHTML); - var jqxhr = $.post("configure_indexer", JSON.stringify(data), function (data) { + var jqxhr = $.post("admin/configure_indexer", JSON.stringify(data), function (data) { if (data.result == "error") { if (data.config) { populateConfigItems(configForm, data.config); diff --git a/src/Jackett/WebServer.cs b/src/Jackett/WebServer.cs new file mode 100644 index 000000000..da9374600 --- /dev/null +++ b/src/Jackett/WebServer.cs @@ -0,0 +1,409 @@ +using Newtonsoft.Json.Linq; +using NLog; +using NLog.Config; +using NLog.Targets; +using NLog.Windows.Forms; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using System.Windows.Forms; + +namespace Jackett +{ + public class WebServer + { + public static bool IsWindows { get { return Environment.OSVersion.Platform == PlatformID.Win32NT; } } + } + /* + public const int DefaultPort = 9117; + public static int Port = DefaultPort; + public static bool ListenPublic = true; + + public static Server ServerInstance { get; private set; } + public static bool IsFirstRun { get; private set; } + public static Logger LoggerInstance { get; private set; } + public static bool IsWindows { get { return Environment.OSVersion.Platform == PlatformID.Win32NT; } } + public static string AppConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jackett"); + + HttpListener listener; + IndexerManager indexerManager; + WebApi webApi; + SonarrApi sonarrApi; + + + public WebServer() + { + // Allow all SSL.. sucks I know but mono on linux is having problems without it.. + ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + + ReadServerSettingsFile(); + LoadApiKey(); + + + indexerManager = new IndexerManager(); + sonarrApi = new SonarrApi(); + webApi = new WebApi(indexerManager, sonarrApi); + + } + + + public static void SetupLogging() + { + var logConfig = new LoggingConfiguration(); + + var logFile = new FileTarget(); + logConfig.AddTarget("file", logFile); + logFile.Layout = "${longdate} ${level} ${message} \n ${exception:format=ToString}\n"; + logFile.FileName = Path.Combine(AppConfigDirectory, "log.txt"); + logFile.ArchiveFileName = "log.{#####}.txt"; + logFile.ArchiveAboveSize = 500000; + logFile.MaxArchiveFiles = 1; + logFile.KeepFileOpen = false; + logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence; + var logFileRule = new LoggingRule("*", LogLevel.Debug, logFile); + logConfig.LoggingRules.Add(logFileRule); + + if (WebServer.IsWindows) + { +#if !__MonoCS__ + var logAlert = new MessageBoxTarget(); + logConfig.AddTarget("alert", logAlert); + logAlert.Layout = "${message}"; + logAlert.Caption = "Alert"; + var logAlertRule = new LoggingRule("*", LogLevel.Fatal, logAlert); + logConfig.LoggingRules.Add(logAlertRule); +#endif + } + + var logConsole = new ConsoleTarget(); + logConfig.AddTarget("console", logConsole); + logConsole.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}"; + var logConsoleRule = new LoggingRule("*", LogLevel.Debug, logConsole); + logConfig.LoggingRules.Add(logConsoleRule); + + LogManager.Configuration = logConfig; + LoggerInstance = LogManager.GetCurrentClassLogger(); + } + + void LoadApiKey() + { + var apiKeyFile = Path.Combine(WebServer.AppConfigDirectory, "api_key.txt"); + if (File.Exists(apiKeyFile)) + ApiKey.CurrentKey = File.ReadAllText(apiKeyFile).Trim(); + else + { + ApiKey.CurrentKey = ApiKey.Generate(); + File.WriteAllText(apiKeyFile, ApiKey.CurrentKey); + } + } + + static void CreateOrMigrateSettings() + { + try + { + if (!Directory.Exists(AppConfigDirectory)) + { + IsFirstRun = true; + Directory.CreateDirectory(AppConfigDirectory); + } + Console.WriteLine("App config/log directory: " + AppConfigDirectory); + } + catch (Exception ex) + { + MessageBox.Show("Could not create settings directory. " + ex.Message); + Application.Exit(); + return; + } + + try + { + string oldDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett"); + if (Directory.Exists(oldDir) && !Directory.Exists(AppConfigDirectory)) + { + Directory.Move(oldDir, AppConfigDirectory); + } + } + catch (Exception ex) + { + Console.WriteLine("ERROR could not migrate settings directory " + ex); + } + } + + + public async Task Start() + { + CreateOrMigrateSettings(); + WebServer.LoggerInstance.Info("Starting HTTP server on port " + Port + " listening " + (ListenPublic ? "publicly" : "privately")); + + try + { + listener = new HttpListener(); + + if (ListenPublic) + { + listener.Prefixes.Add(string.Format("http://*:{0}/", Port)); + } + else + { + listener.Prefixes.Add(string.Format("http://127.0.0.1:{0}/", Port)); + } + + listener.Start(); + webApi.server = this; + } + catch (HttpListenerException ex) + { + if (ex.ErrorCode == 5) + { + var errorStr = "App must be ran as admin for permission to use port " + + Port + Environment.NewLine + "Restart app with admin privileges?"; + if (WebServer.IsWindows) + { + var dialogResult = MessageBox.Show(errorStr, "Error", MessageBoxButtons.YesNo); + if (dialogResult == DialogResult.No) + { + Application.Exit(); + return; + } + else + { + // WebServer.RestartAsAdmin(); + } + } + } + else + { + WebServer.LoggerInstance.Fatal("Failed to start HTTP WebServer. " + ex.Message, ex); + } + } + catch (Exception ex) + { + WebServer.LoggerInstance.Error(ex, "Error starting HTTP server: " + ex.Message); + return; + } + + WebServer.LoggerInstance.Info("Server started on port " + Port); + WebServer.LoggerInstance.Info("Accepting only requests from local system: " + (!ListenPublic)); + + while (true) + { + Exception error = null; + try + { + error = null; + var context = await listener.GetContextAsync(); + ProcessHttpRequest(context); + } + catch (ObjectDisposedException ex) + { + WebServer.LoggerInstance.Error(ex, "Critical error, HTTP listener was destroyed"); + Process.GetCurrentProcess().Kill(); + } + catch (Exception ex) + { + error = ex; + WebServer.LoggerInstance.Error(ex, "Error processing HTTP request"); + } + + if (error != null) + await Task.Delay(TimeSpan.FromSeconds(5)); + } + } + + + static void ReadSettingsFile() + { + var path = Path.Combine(AppConfigDirectory, "config.json"); + if (!File.Exists(path)) + { + JObject f = new JObject(); + f.Add("port", WebServer.DefaultPort); + f.Add("public", true); + File.WriteAllText(path, f.ToString()); + } + + var configJson = JObject.Parse(File.ReadAllText(path)); + int port = (int)configJson.GetValue("port"); + WebServer.Port = port; + + WebServer.ListenPublic = (bool)configJson.GetValue("public"); + + Console.WriteLine("Config file path: " + path); + } + + public void Stop() + { + listener.Stop(); + listener.Abort(); + } + + async void ProcessHttpRequest(HttpListenerContext context) + { + WebServer.LoggerInstance.Trace("Received request: " + context.Request.Url.ToString()); + Exception exception = null; + try + { + if (await webApi.HandleRequest(context)) + { + + } + else if (context.Request.Url.AbsolutePath.StartsWith("/api/")) + { + await ProcessTorznab(context); + } + else + { + var responseBytes = Encoding.UTF8.GetBytes("Invalid request"); + await context.Response.OutputStream.WriteAsync(responseBytes, 0, responseBytes.Length); + } + } + catch (Exception ex) + { + exception = ex; + WebServer.LoggerInstance.Error(ex, ex.Message + ex.ToString()); + } + + if (exception != null) + { + try + { + var errorBytes = Encoding.UTF8.GetBytes(exception.Message); + await context.Response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length); + } + catch (Exception) + { + } + } + + try + { + context.Response.Close(); + } + catch (Exception) + { + } + + } + + async Task ProcessTorznab(HttpListenerContext context) + { + + var query = HttpUtility.ParseQueryString(context.Request.Url.Query); + var inputStream = context.Request.InputStream; + + var indexerId = context.Request.Url.Segments[2].TrimEnd('/').ToLower(); + var indexer = indexerManager.GetIndexer(indexerId); + + if (context.Request.Url.Segments.Length > 4 && context.Request.Url.Segments[3] == "download/") + { + var downloadSegment = HttpServerUtility.UrlTokenDecode(context.Request.Url.Segments[4].TrimEnd('/')); + var downloadLink = Encoding.UTF8.GetString(downloadSegment); + var downloadBytes = await indexer.Download(new Uri(downloadLink)); + await context.Response.OutputStream.WriteAsync(downloadBytes, 0, downloadBytes.Length); + return; + } + + var torznabQuery = TorznabQuery.FromHttpQuery(query); + + if (torznabQuery.RageID != 0) + torznabQuery.ShowTitles = await sonarrApi.GetShowTitle(torznabQuery.RageID); + else if (!string.IsNullOrEmpty(torznabQuery.SearchTerm)) + torznabQuery.ShowTitles = new string[] { torznabQuery.SearchTerm }; + + var releases = await indexer.PerformQuery(torznabQuery); + + WebServer.LoggerInstance.Debug(string.Format("Found {0} releases from {1}", releases.Length, indexer.DisplayName)); + + var severUrl = string.Format("{0}://{1}:{2}/", context.Request.Url.Scheme, context.Request.Url.Host, context.Request.Url.Port); + + var resultPage = new ResultPage(new ChannelInfo + { + Title = indexer.DisplayName, + Description = indexer.DisplayDescription, + Link = indexer.SiteLink, + ImageUrl = new Uri(severUrl + "logos/" + indexerId + ".png"), + ImageTitle = indexer.DisplayName, + ImageLink = indexer.SiteLink, + ImageDescription = indexer.DisplayName + }); + + // add Jackett proxy to download links... + foreach (var release in releases) + { + if (release.Link == null || release.Link.Scheme == "magnet") + continue; + var originalLink = release.Link; + var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/download.torrent"; + var proxyLink = string.Format("{0}api/{1}/download/{2}", severUrl, indexerId, encodedLink); + release.Link = new Uri(proxyLink); + } + + resultPage.Releases.AddRange(releases); + + var xml = resultPage.ToXml(new Uri(severUrl)); + + var responseBytes = Encoding.UTF8.GetBytes(xml); + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.ContentLength64 = responseBytes.LongLength; + context.Response.ContentType = "application/rss+xml"; + await context.Response.OutputStream.WriteAsync(responseBytes, 0, responseBytes.Length); + + } + + public static JObject ReadServerSettingsFile() + { + var path = ServerConfigFile; + JObject jsonReply = new JObject(); + if (File.Exists(path)) + { + jsonReply = JObject.Parse(File.ReadAllText(path)); + Port = (int)jsonReply["port"]; + ListenPublic = (bool)jsonReply["public"]; + } + else + { + jsonReply["port"] = Port; + jsonReply["public"] = ListenPublic; + } + return jsonReply; + } + + public static Task ApplyPortConfiguration(JToken json) + { + JObject jsonObject = (JObject)json; + JToken jJackettPort = jsonObject.GetValue("port"); + int jackettPort; + if (!ServerUtil.IsPort(jJackettPort.ToString())) + throw new CustomException("The value entered is not a valid port"); + else + jackettPort = int.Parse(jJackettPort.ToString()); + + if (jackettPort == Port) + throw new CustomException("The current port is the same as the one being used now."); + else if (ServerUtil.RestrictedPorts.Contains(jackettPort)) + throw new CustomException("This port is not allowed due to it not being safe."); + SaveSettings(jackettPort); + + return Task.FromResult(jackettPort); + } + + private static string ServerConfigFile = Path.Combine(WebServer.AppConfigDirectory, "config.json"); + + private static void SaveSettings(int jacketPort) + { + JObject json = new JObject(); + json["port"] = jacketPort; + json["public"] = ListenPublic; + File.WriteAllText(ServerConfigFile, json.ToString()); + } + + }*/ + } diff --git a/src/Jackett/packages.config b/src/Jackett/packages.config index efab650c9..a4f77e87c 100644 --- a/src/Jackett/packages.config +++ b/src/Jackett/packages.config @@ -1,7 +1,22 @@  - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file