Refactor done

This commit is contained in:
KZ 2015-07-19 14:22:50 +01:00
parent e518a3b58e
commit 1359ad16f0
94 changed files with 1338 additions and 1407 deletions

15
Build.bat Normal file
View File

@ -0,0 +1,15 @@
rmdir /s /q build
cd src
Msbuild Jackett.sln /t:Clean,Build /p:Configuration=Release
cd ..
xcopy src\Jackett.Console\bin\Release Build\ /e /y
copy /Y src\Jackett.Service\bin\Release\JackettService.exe build\JackettService.exe
copy /Y src\Jackett.Service\bin\Release\JackettService.exe.config build\JackettService.exe.config
copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe build\JackettTray.exe
copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe.config build\JackettTray.exe.config
cd build
del *.pdb
del *.xml
cd ..

58
Installer.iss Normal file
View File

@ -0,0 +1,58 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Jackett"
#define MyAppVersion "0.5"
#define MyAppPublisher "Jackett Inc."
#define MyAppURL "https://github.com/zone117x/Jackett"
#define MyAppExeName "JackettTray.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{C2A9FC00-AA48-4F17-9A72-62FBCEE2785B}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
OutputBaseFilename=setup
SetupIconFile=O:\Documents\JackettKayo\src\Jackett.Console\jackett.ico
Compression=lzma
SolidCompression=yes
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "windowsService"; Description: "Install as a Windows Service"
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "O:\Documents\JackettKayo\Build\JackettTray.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "O:\Documents\JackettKayo\Build\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[Run]
Filename: "{app}\JackettConsole.exe"; Parameters: "/u"; Flags: waituntilterminated;
Filename: "{app}\JackettConsole.exe"; Parameters: "/r"; Flags: waituntilterminated;
Filename: "{app}\JackettConsole.exe"; Parameters: "/i"; Flags: waituntilterminated; Tasks: windowsService
[UninstallRun]
Filename: "{app}\JackettConsole.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist

View File

@ -77,6 +77,10 @@
<HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>

View File

@ -16,62 +16,34 @@ namespace JackettConsole
{
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 (args.Length > 0)
{
#if !__MonoCS__
Application.Run(new Main());
#endif
switch (args[0].ToLowerInvariant())
{
case "/i":
Engine.ServiceConfig.Install();
return;
case "/r":
Engine.Server.ReserveUrls();
return;
case "/u":
Engine.Server.ReserveUrls(false);
Engine.ServiceConfig.Uninstall();
return;
}
}
Engine.Server.Start();
Engine.Logger.Info("Running in headless mode.");
Engine.RunTime.Spin();
Engine.Logger.Info("Server thread exit");
}
catch (Exception)
catch(Exception e)
{
}*/
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);
Engine.Logger.Error(e, "Top level exception");
}
}
}
}

View File

@ -14,5 +14,6 @@
<package id="Microsoft.Owin.Hosting" version="2.0.2" targetFramework="net452" userInstalled="true" />
<package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" userInstalled="true" />
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" userInstalled="true" />
<package id="NLog" version="4.0.1" targetFramework="net452" />
<package id="Owin" version="1.0" targetFramework="net452" userInstalled="true" />
</packages>

View File

@ -1,6 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Http.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -36,8 +36,65 @@
<ApplicationIcon>jackett.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Autofac.3.5.0\lib\net40\Autofac.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.Owin">
<HintPath>..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.WebApi, Version=3.4.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.WebApi.Owin">
<HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.FileSystems, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.FileSystems.3.0.1\lib\net45\Microsoft.Owin.FileSystems.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Host.HttpListener">
<HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Hosting, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.StaticFiles, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -58,10 +115,17 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="jackett.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jackett\Jackett.csproj">
<Project>{e636d5f8-68b4-4903-b4ed-ccfd9c9e899f}</Project>
<Name>Jackett</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -29,7 +29,7 @@
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.ServiceName = "Service1";
this.ServiceName = "Jackett";
}
#endregion

View File

@ -19,10 +19,15 @@ namespace Jackett.Service
protected override void OnStart(string[] args)
{
Engine.Logger.Info("Service starting");
Engine.Server.Start();
Engine.Logger.Info("Service started");
}
protected override void OnStop()
{
Engine.Logger.Info("Service stopping");
Engine.Server.Stop();
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Autofac" version="3.5.0" targetFramework="net452" />
<package id="Autofac.Owin" version="3.1.0" targetFramework="net452" />
<package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" />
<package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Owin.Hosting" version="2.0.2" targetFramework="net452" />
<package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" />
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" />
<package id="NLog" version="4.0.1" targetFramework="net452" />
<package id="Owin" version="1.0" targetFramework="net452" />
</packages>

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

View File

@ -1,170 +1,170 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
<script src="jquery-2.1.3.min.js"></script>
<script src="handlebars-v3.0.1.js"></script>
<script src="bootstrap/bootstrap.min.js"></script>
<script src="bootstrap-notify.js"></script>
<link href="bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="animate.css" rel="stylesheet">
<link href="custom.css" rel="stylesheet">
<title>Jackett</title>
</head>
<body>
<div id="page">
<img id="logo" src="jacket_medium.png" /><span id="header-title">Jackett</span>
<hr />
<div class="input-area">
<span class="input-header">Sonarr API Host: </span>
<input id="sonarr-host" class="form-control input-right" type="text" readonly />
<button id="sonarr-settings" class="btn btn-primary btn-sm">
Settings <span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
</button>
<button id="sonarr-test" class="btn btn-warning btn-sm">
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
</button>
<p id="sonarr-warning" class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
Sonarr API must be configured
</p>
</div>
<hr />
<div class="input-area">
<p>
To add a Jackett indexer in Sonarr go to <b>Settings > Indexers > Add > Torznab > Custom</b>.
</p>
<span class="input-header">Jackett API Key: </span>
<input id="api-key-input" class="form-control input-right" type="text" value="" placeholder="API Key" readonly="">
<p>Use this key when adding indexers to Sonarr. This key works for all indexers.</p>
<span class="input-header">Jackett port: </span>
<input id="jackett-port" class="form-control input-right" type="text" value="" placeholder="9117">
<button id="change-jackett-port" class="btn btn-primary btn-sm">
Configure <span class="glyphicon glyphicon-ok-wrench" aria-hidden="true"></span>
</button>
<span title="Jackett will restart after changing the port" class="glyphicon glyphicon-info-sign"></span>
</div>
<hr />
<h3>Configured Indexers</h3>
<div id="indexers"> </div>
<hr />
<div id="footer">
Jackett Version <span id="app-version"></span>
</div>
</div>
<div id="select-indexer-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Select an indexer to setup</h4>
</div>
<div class="modal-body">
<div id="unconfigured-indexers">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="modals"></div>
<div id="templates">
<div class="config-setup-modal modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{title}}</h4>
</div>
<div class="modal-body">
<form class="config-setup-form"></form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary setup-indexer-go">Okay</button>
</div>
</div>
</div>
</div>
<button class="indexer card add-indexer" data-toggle="modal" data-target="#select-indexer-modal">
<div class="indexer-add-content">
<span class="glyphicon glyphicon glyphicon-plus" aria-hidden="true"></span>
<div class="light-text">Add</div>
</div>
</button>
<div class="configured-indexer indexer card">
<div class="indexer-logo"><img alt="{{name}}" title="{{name}}" src="logos/{{id}}.png" /></div>
<div class="indexer-buttons">
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}">
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
</button>
<button class="btn btn-danger btn-sm indexer-button-delete" data-id="{{id}}">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
<a class="btn btn-info btn-sm" target="_blank" href="{{site_link}}">
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
</a>
<button class="btn btn-warning btn-sm indexer-button-test" data-id="{{id}}">
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
</button>
</div>
<div class="indexer-host">
<b>Torznab Host:</b>
<input class="form-control" type="text" value="{{torznab_host}}" placeholder="Torznab Host" readonly="">
</div>
</div>
<div class="unconfigured-indexer card">
<div class="indexer-logo"><img alt="{{name}}" title="{{name}}" src="logos/{{id}}.png" /></div>
<div class="indexer-buttons">
<a class="btn btn-info" target="_blank" href="{{site_link}}">Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span></a>
<button class="indexer-setup btn btn-success" data-id="{{id}}">Setup <span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
</div>
</div>
<div class="setup-item form-group" data-id="{{id}}" data-value="{{value}}" data-type="{{type}}">
<div class="setup-item-label">{{name}}</div>
<div class="setup-item-value">{{{value_element}}}</div>
</div>
<input class="setup-item-inputstring form-control" type="text" value="{{{value}}}" />
<div class="setup-item-inputbool">
{{#if value}}
<input type="checkbox" data-id="{{id}}" class="form-control" checked />
{{else}}
<input type="checkbox" data-id="{{id}}" class="form-control" />
{{/if}}
</div>
<img class="setup-item-displayimage" src="{{{value}}}" />
<div class="setup-item-displayinfo alert alert-info" role="alert">{{{value}}}</div>
<span class="spinner glyphicon glyphicon-refresh"></span>
</div>
<script src="custom.js"></script>
</body>
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
<script src="jquery-2.1.3.min.js"></script>
<script src="handlebars-v3.0.1.js"></script>
<script src="bootstrap/bootstrap.min.js"></script>
<script src="bootstrap-notify.js"></script>
<link href="bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="animate.css" rel="stylesheet">
<link href="custom.css" rel="stylesheet">
<title>Jackett</title>
</head>
<body>
<div id="page">
<img id="logo" src="jacket_medium.png" /><span id="header-title">Jackett</span>
<hr />
<div class="input-area">
<span class="input-header">Sonarr API Host: </span>
<input id="sonarr-host" class="form-control input-right" type="text" readonly />
<button id="sonarr-settings" class="btn btn-primary btn-sm">
Settings <span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
</button>
<button id="sonarr-test" class="btn btn-warning btn-sm">
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
</button>
<p id="sonarr-warning" class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
Sonarr API must be configured
</p>
</div>
<hr />
<div class="input-area">
<p>
To add a Jackett indexer in Sonarr go to <b>Settings > Indexers > Add > Torznab > Custom</b>.
</p>
<span class="input-header">Jackett API Key: </span>
<input id="api-key-input" class="form-control input-right" type="text" value="" placeholder="API Key" readonly="">
<p>Use this key when adding indexers to Sonarr. This key works for all indexers.</p>
<span class="input-header">Jackett port: </span>
<input id="jackett-port" class="form-control input-right" type="text" value="" placeholder="9117">
<button id="change-jackett-port" class="btn btn-primary btn-sm">
Configure <span class="glyphicon glyphicon-ok-wrench" aria-hidden="true"></span>
</button>
<span title="Jackett will restart after changing the port" class="glyphicon glyphicon-info-sign"></span>
</div>
<hr />
<h3>Configured Indexers</h3>
<div id="indexers"> </div>
<hr />
<div id="footer">
Jackett Version <span id="app-version"></span>
</div>
</div>
<div id="select-indexer-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Select an indexer to setup</h4>
</div>
<div class="modal-body">
<div id="unconfigured-indexers">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="modals"></div>
<div id="templates">
<div class="config-setup-modal modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{title}}</h4>
</div>
<div class="modal-body">
<form class="config-setup-form"></form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary setup-indexer-go">Okay</button>
</div>
</div>
</div>
</div>
<button class="indexer card add-indexer" data-toggle="modal" data-target="#select-indexer-modal">
<div class="indexer-add-content">
<span class="glyphicon glyphicon glyphicon-plus" aria-hidden="true"></span>
<div class="light-text">Add</div>
</div>
</button>
<div class="configured-indexer indexer card">
<div class="indexer-logo"><img alt="{{name}}" title="{{name}}" src="logos/{{id}}.png" /></div>
<div class="indexer-buttons">
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}">
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
</button>
<button class="btn btn-danger btn-sm indexer-button-delete" data-id="{{id}}">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
<a class="btn btn-info btn-sm" target="_blank" href="{{site_link}}">
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
</a>
<button class="btn btn-warning btn-sm indexer-button-test" data-id="{{id}}">
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
</button>
</div>
<div class="indexer-host">
<b>Torznab Host:</b>
<input class="form-control" type="text" value="{{torznab_host}}" placeholder="Torznab Host" readonly="">
</div>
</div>
<div class="unconfigured-indexer card">
<div class="indexer-logo"><img alt="{{name}}" title="{{name}}" src="logos/{{id}}.png" /></div>
<div class="indexer-buttons">
<a class="btn btn-info" target="_blank" href="{{site_link}}">Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span></a>
<button class="indexer-setup btn btn-success" data-id="{{id}}">Setup <span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
</div>
</div>
<div class="setup-item form-group" data-id="{{id}}" data-value="{{value}}" data-type="{{type}}">
<div class="setup-item-label">{{name}}</div>
<div class="setup-item-value">{{{value_element}}}</div>
</div>
<input class="setup-item-inputstring form-control" type="text" value="{{{value}}}" />
<div class="setup-item-inputbool">
{{#if value}}
<input type="checkbox" data-id="{{id}}" class="form-control" checked />
{{else}}
<input type="checkbox" data-id="{{id}}" class="form-control" />
{{/if}}
</div>
<img class="setup-item-displayimage" src="{{{value}}}" />
<div class="setup-item-displayinfo alert alert-info" role="alert">{{{value}}}</div>
<span class="spinner glyphicon glyphicon-refresh"></span>
</div>
<script src="custom.js"></script>
</body>
</html>

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -0,0 +1,75 @@
using Jackett.Models;
using Jackett.Services;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace Jackett.Controllers
{
public class APIController : ApiController
{
private IIndexerManagerService indexerService;
private ISonarrApi sonarrService;
private Logger logger;
public APIController(IIndexerManagerService i, ISonarrApi s, Logger l)
{
indexerService = i;
sonarrService = s;
logger = l;
}
[HttpGet]
public async Task<HttpResponseMessage> Call(string indexerName)
{
var indexer = indexerService.GetIndexer(indexerName);
var torznabQuery = TorznabQuery.FromHttpQuery(HttpUtility.ParseQueryString(Request.RequestUri.Query));
if (torznabQuery.RageID != 0)
torznabQuery.ShowTitles = await sonarrService.GetShowTitle(torznabQuery.RageID);
else if (!string.IsNullOrEmpty(torznabQuery.SearchTerm))
torznabQuery.ShowTitles = new string[] { torznabQuery.SearchTerm };
var releases = await indexer.PerformQuery(torznabQuery);
logger.Debug(string.Format("Found {0} releases from {1}", releases.Length, indexer.DisplayName));
var severUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
var resultPage = new ResultPage(new ChannelInfo
{
Title = indexer.DisplayName,
Description = indexer.DisplayDescription,
Link = indexer.SiteLink,
ImageUrl = new Uri(severUrl + "logos/" + indexer.DisplayName + ".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, indexer.DisplayName.ToLowerInvariant(), encodedLink);
release.Link = new Uri(proxyLink);
}
resultPage.Releases.AddRange(releases);
var xml = resultPage.ToXml(new Uri(severUrl));
// Force the return as XML
return new HttpResponseMessage()
{
Content = new StringContent(xml, Encoding.UTF8, "application/rss+xml")
};
}
}
}

View File

@ -19,12 +19,14 @@ namespace Jackett.Controllers
private IConfigurationService config;
private ISonarrApi sonarrApi;
private IIndexerManagerService indexerService;
private IServerService serverService;
public AdminController(IConfigurationService config, ISonarrApi s, IIndexerManagerService i)
public AdminController(IConfigurationService config, ISonarrApi s, IIndexerManagerService i, IServerService ss)
{
this.config = config;
sonarrApi = s;
indexerService = i;
serverService = ss;
}
private async Task<JToken> ReadPostDataJson()
@ -92,7 +94,7 @@ namespace Jackett.Controllers
try
{
jsonReply["result"] = "success";
jsonReply["api_key"] = ApiKey.CurrentKey;
jsonReply["api_key"] = serverService.Config.APIKey;
jsonReply["app_version"] = config.GetVersion();
JArray items = new JArray();

View File

@ -0,0 +1,48 @@
using Jackett.Services;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace Jackett.Controllers
{
public class DownloadController : ApiController
{
private Logger logger;
private IIndexerManagerService indexerService;
public DownloadController(IIndexerManagerService i, Logger l)
{
logger = l;
indexerService = i;
}
[HttpGet]
public async Task<HttpResponseMessage> Download(string indexerName, string path)
{
try
{
var indexer = indexerService.GetIndexer(indexerName);
var remoteFile = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
var downloadBytes = await indexer.Download(new Uri(remoteFile));
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(downloadBytes);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-bittorrent");
return result;
}
catch (Exception e)
{
logger.Error(e, "Error downloading " + indexerName + " " + path);
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
}
}

View File

@ -12,7 +12,6 @@ namespace Jackett
{
public static class CookieContainerExtensions
{
public static void FillFromJson(this CookieContainer cookies, Uri uri, JToken json, Logger logger)
{
if (json["cookies"] != null)

View File

@ -10,7 +10,6 @@ namespace Jackett
{
public class DataUrl
{
public static string ReadFileToDataUrl(string file)
{
string mime = MimeMapping.GetMimeMapping(file);

111
src/Jackett/Engine.cs Normal file
View File

@ -0,0 +1,111 @@
using Autofac;
using Jackett.Services;
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett
{
public class Engine
{
private static IContainer container = null;
static Engine()
{
var builder = new ContainerBuilder();
builder.RegisterModule<JackettModule>();
container = builder.Build();
// Register the container in itself to allow for late resolves
var secondaryBuilder = new ContainerBuilder();
secondaryBuilder.RegisterInstance<IContainer>(container).SingleInstance();
SetupLogging(secondaryBuilder);
secondaryBuilder.Update(container);
Logger.Info("Starting Jackett " + ConfigService.GetVersion());
}
public static IContainer GetContainer()
{
return container;
}
public static bool IsWindows {
get {
return Environment.OSVersion.Platform == PlatformID.Win32NT;
}
}
public static IConfigurationService ConfigService
{
get
{
return container.Resolve<IConfigurationService>();
}
}
public static IServiceConfigService ServiceConfig
{
get
{
return container.Resolve<IServiceConfigService>();
}
}
public static IServerService Server
{
get
{
return container.Resolve<IServerService>();
}
}
public static IRunTimeService RunTime
{
get
{
return container.Resolve<IRunTimeService>();
}
}
public static Logger Logger
{
get
{
return container.Resolve<Logger>();
}
}
private static void SetupLogging(ContainerBuilder builder)
{
var logConfig = new LoggingConfiguration();
var logFile = new FileTarget();
logConfig.AddTarget("file", logFile);
logFile.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}";
logFile.FileName = Path.Combine(ConfigurationService.GetAppDataFolderStatic(), "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);
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<Logger>(LogManager.GetCurrentClassLogger()).SingleInstance();
}
}
}

View File

@ -1,107 +0,0 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett
{
/*public class IndexerManager
{
static string IndexerConfigDirectory = Path.Combine(WebServer.AppConfigDirectory, "Indexers");
public Dictionary<string, IndexerInterface> Indexers { get; private set; }
public IndexerManager()
{
Indexers = new Dictionary<string, IndexerInterface>();
LoadMissingIndexers();
}
void LoadMissingIndexers()
{
var implementedIndexerTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(IndexerInterface).IsAssignableFrom(p) && !p.IsInterface)
.ToArray();
foreach (var t in implementedIndexerTypes)
{
LoadIndexer(t);
}
}
void LoadIndexer(Type indexerType)
{
var name = indexerType.Name.Trim().ToLower();
if (Indexers.ContainsKey(name))
return;
IndexerInterface newIndexer = (IndexerInterface)Activator.CreateInstance(indexerType);
newIndexer.OnSaveConfigurationRequested += newIndexer_OnSaveConfigurationRequested;
newIndexer.OnResultParsingError += newIndexer_OnResultParsingError;
var configFilePath = GetIndexerConfigFilePath(newIndexer);
if (File.Exists(configFilePath))
{
var jsonString = JObject.Parse(File.ReadAllText(configFilePath));
newIndexer.LoadFromSavedConfiguration(jsonString);
}
Indexers.Add(name, newIndexer);
}
void newIndexer_OnResultParsingError(IndexerInterface indexer, string results, Exception ex)
{
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(WebServer.AppConfigDirectory, fileName), fileContents);
}
string GetIndexerConfigFilePath(IndexerInterface indexer)
{
return Path.Combine(IndexerConfigDirectory, indexer.GetType().Name.ToLower() + ".json");
}
void newIndexer_OnSaveConfigurationRequested(IndexerInterface indexer, JToken obj)
{
var configFilePath = GetIndexerConfigFilePath(indexer);
if (!Directory.Exists(IndexerConfigDirectory))
Directory.CreateDirectory(IndexerConfigDirectory);
File.WriteAllText(configFilePath, obj.ToString());
}
public IndexerInterface GetIndexer(string name)
{
IndexerInterface indexer;
if (!Indexers.TryGetValue(name, out indexer))
throw new Exception(string.Format("No indexer with ID '{0}'", name));
return indexer;
}
public void DeleteIndexer(string name)
{
var indexer = GetIndexer(name);
var configPath = GetIndexerConfigFilePath(indexer);
File.Delete(configPath);
Indexers.Remove(name);
LoadMissingIndexers();
}
public async Task TestIndexer(IndexerInterface indexer)
{
var browseQuery = new TorznabQuery();
var results = await indexer.PerformQuery(browseQuery);
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");
}
}*/
}

View File

@ -86,7 +86,7 @@ namespace Jackett.Indexers
configSaveData = new JObject();
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
// If Windows use .net http
var response = await client.SendAsync(message);
@ -155,7 +155,7 @@ namespace Jackett.Indexers
var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
string results;
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
var request = CreateHttpRequest(new Uri(episodeSearchUrl));
request.Method = HttpMethod.Get;
@ -223,7 +223,7 @@ namespace Jackett.Indexers
public override async Task<byte[]> Download(Uri link)
{
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
return await client.GetByteArrayAsync(link);
}

View File

@ -18,7 +18,7 @@ using System.Web;
namespace Jackett.Indexers
{
public class AnimeBytes : IndexerInterface
public class AnimeBytes : IIndexer
{
class ConfigurationDataBasicLoginAnimeBytes : ConfigurationDataBasicLogin
{
@ -41,8 +41,8 @@ namespace Jackett.Indexers
private static List<CachedResult> cache = new List<CachedResult>();
private static readonly TimeSpan cacheTime = new TimeSpan(0, 9, 0);
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;

View File

@ -9,12 +9,12 @@ using NLog;
namespace Jackett.Indexers
{
public abstract class BaseIndexer: IndexerInterface
public abstract class BaseIndexer: IIndexer
{
public string DisplayDescription { get; }
public string DisplayName { get; }
public string DisplayDescription { get; private set; }
public string DisplayName { get; private set; }
public bool IsConfigured { get; protected set; }
public Uri SiteLink { get; }
public Uri SiteLink { get; private set; }
public abstract Task ApplyConfiguration(JToken configJson);
public abstract Task<byte[]> Download(Uri link);

View File

@ -14,11 +14,11 @@ using System.Web;
namespace Jackett.Indexers
{
public class BeyondHD : IndexerInterface
public class BeyondHD : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -15,9 +15,9 @@ using System.Web;
namespace Jackett.Indexers
{
public class BitHdtv : IndexerInterface
public class BitHdtv : IIndexer
{
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{
@ -100,7 +100,7 @@ namespace Jackett.Indexers
}
}
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public bool IsConfigured { get; private set; }

View File

@ -14,9 +14,9 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
namespace Jackett
namespace Jackett.Indexers
{
public class BitMeTV : IndexerInterface
public class BitMeTV : IIndexer
{
class BmtvConfig : ConfigurationData
{
@ -53,8 +53,8 @@ namespace Jackett
HttpClient client;
Logger logger;
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public BitMeTV(Logger l)
{

View File

@ -12,11 +12,11 @@ using System.Web;
namespace Jackett.Indexers
{
class FrenchTorrentDb : IndexerInterface
class FrenchTorrentDb : IIndexer
{
public event Action<IndexerInterface, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
class ConfigurationDataBasicLoginFrenchTorrentDb : ConfigurationData
{

View File

@ -1,4 +1,5 @@
using CsQuery;
using Jackett.Indexers;
using Jackett.Models;
using Jackett.Utils;
using Newtonsoft.Json.Linq;
@ -15,11 +16,11 @@ using System.Threading.Tasks;
using System.Web;
using System.Web.UI.WebControls;
namespace Jackett
namespace Jackett.Indexers
{
public class Freshon : IndexerInterface
public class Freshon : IIndexer
{
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
static string BaseUrl = "https://freshon.tv";
static string LoginUrl = BaseUrl + "/login.php";
@ -40,7 +41,7 @@ namespace Jackett
public Uri SiteLink { get { return new Uri(BaseUrl); } }
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
Logger logger;
public Freshon(Logger l)

View File

@ -15,18 +15,17 @@ using System.Web;
namespace Jackett.Indexers
{
public class HDTorrents : IndexerInterface
public class HDTorrents : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
const string DefaultUrl = "http://hdts.ru"; // Of the accessible domains the .ru seems the most reliable. https://hdts.ru | https://hd-torrents.org | https://hd-torrents.net | https://hd-torrents.me
string BaseUrl = DefaultUrl;
static string chromeUserAgent = BrowserUtil.ChromeUserAgent;
private string SearchUrl = DefaultUrl + "/torrents.php?search={0}&active=1&options=0&category%5B%5D=59&category%5B%5D=60&category%5B%5D=30&category%5B%5D=38&page={1}";
private static string LoginUrl = DefaultUrl + "/login.php";
private static string LoginPostUrl = DefaultUrl + "/login.php?returnto=index.php";
private const int MAXPAGES = 3;
CookieContainer cookies;

View File

@ -7,9 +7,9 @@ using System.Text;
using System.Threading.Tasks;
using System.Web.UI.WebControls;
namespace Jackett
namespace Jackett.Indexers
{
public interface IndexerInterface
public interface IIndexer
{
string DisplayName { get; }
string DisplayDescription { get; }

View File

@ -15,11 +15,11 @@ using System.Web;
namespace Jackett.Indexers
{
public class IPTorrents : IndexerInterface
public class IPTorrents : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName { get { return "IPTorrents"; } }

View File

@ -14,7 +14,7 @@ using System.Web;
namespace Jackett.Indexers
{
public class MoreThanTV : IndexerInterface
public class MoreThanTV : IIndexer
{
public string DisplayName
{
@ -32,8 +32,8 @@ namespace Jackett.Indexers
}
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public bool IsConfigured { get; private set; }
@ -93,7 +93,7 @@ namespace Jackett.Indexers
var configSaveData = new JObject();
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
// If Windows use .net http
var response = await client.PostAsync(LoginUrl, content);
@ -156,7 +156,7 @@ namespace Jackett.Indexers
var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
string results;
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
results = await client.GetStringAsync(episodeSearchUrl, retries);
}
@ -224,7 +224,7 @@ namespace Jackett.Indexers
public async Task<byte[]> Download(Uri link)
{
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
return await client.GetByteArrayAsync(link);
}

View File

@ -12,11 +12,11 @@ using System.Threading.Tasks;
namespace Jackett.Indexers
{
public class Rarbg : IndexerInterface
public class Rarbg : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -13,11 +13,11 @@ using System.Threading.Tasks;
namespace Jackett.Indexers
{
class SceneAccess : IndexerInterface
class SceneAccess : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{
@ -87,7 +87,7 @@ namespace Jackett.Indexers
string responseContent;
var configSaveData = new JObject();
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
// If Windows use .net http
var response = await client.PostAsync(LoginUrl, content);
@ -139,7 +139,7 @@ namespace Jackett.Indexers
var searchUrl = string.Format(SearchUrl, searchSection, searchCategory, searchString);
string results;
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
results = await client.GetStringAsync(searchUrl);
}
@ -195,7 +195,7 @@ namespace Jackett.Indexers
public async Task<byte[]> Download(Uri link)
{
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
return await client.GetByteArrayAsync(link);
}

View File

@ -15,11 +15,11 @@ using System.Web;
namespace Jackett.Indexers
{
public class SceneTime : IndexerInterface
public class SceneTime : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -14,11 +14,11 @@ using System.Xml;
namespace Jackett.Indexers
{
public class ShowRSS : IndexerInterface
public class ShowRSS : IIndexer
{
public event Action<IndexerInterface, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -12,11 +12,11 @@ using System.Web;
namespace Jackett.Indexers
{
public class Strike : IndexerInterface
public class Strike : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -14,12 +14,12 @@ using System.Web;
namespace Jackett.Indexers
{
public class T411 : IndexerInterface
public class T411 : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -15,12 +15,12 @@ using System.Web;
namespace Jackett.Indexers
{
public class ThePirateBay : IndexerInterface
public class ThePirateBay : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName { get { return "The Pirate Bay"; } }
@ -110,7 +110,7 @@ namespace Jackett.Indexers
string results;
if (WebServer.IsWindows)
if (Engine.IsWindows)
{
results = await client.GetStringAsync(episodeSearchUrl);
}

View File

@ -15,11 +15,11 @@ using System.Web;
namespace Jackett.Indexers
{
public class TorrentDay : IndexerInterface
public class TorrentDay : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -15,11 +15,11 @@ using System.Web;
namespace Jackett.Indexers
{
public class TorrentLeech : IndexerInterface
public class TorrentLeech : IIndexer
{
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public string DisplayName
{

View File

@ -15,11 +15,11 @@ using System.Web;
namespace Jackett.Indexers
{
public class TorrentShack : IndexerInterface
public class TorrentShack : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -13,11 +13,11 @@ using System.Xml;
namespace Jackett.Indexers
{
public class Torrentz : IndexerInterface
public class Torrentz : IIndexer
{
public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested;
public event Action<IIndexer, JToken> OnSaveConfigurationRequested;
public event Action<IndexerInterface, string, Exception> OnResultParsingError;
public event Action<IIndexer, string, Exception> OnResultParsingError;
public string DisplayName
{

View File

@ -102,6 +102,7 @@
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
@ -110,6 +111,7 @@
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
@ -133,8 +135,15 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Controllers\APIController.cs" />
<Compile Include="Controllers\DownloadController.cs" />
<Compile Include="Engine.cs" />
<Compile Include="Indexers\BaseIndexer.cs" />
<Compile Include="Models\ApiKey.cs" />
<Compile Include="Models\Config\ServerConfig.cs" />
<Compile Include="Services\ProcessService.cs" />
<Compile Include="Services\SerializeService.cs" />
<Compile Include="Services\ServiceConfigService.cs" />
<Compile Include="Services\SpinService.cs" />
<Compile Include="Utils\BrowserUtil.cs" />
<Compile Include="Models\CachedResult.cs" />
<Compile Include="Models\ChannelInfo.cs" />
@ -147,8 +156,7 @@
<Compile Include="DataUrl.cs" />
<Compile Include="ExceptionWithConfigData.cs" />
<Compile Include="HttpClientExtensions.cs" />
<Compile Include="IndexerInterface.cs" />
<Compile Include="IndexerManager.cs" />
<Compile Include="Indexers\IIndexer.cs" />
<Compile Include="Indexers\BeyondHD.cs" />
<Compile Include="Indexers\BitHdtv.cs" />
<Compile Include="Indexers\BitMeTV.cs" />
@ -180,22 +188,20 @@
</Compile>
<Compile Include="Models\ReleaseInfo.cs" />
<Compile Include="Models\ResultPage.cs" />
<Compile Include="Server.cs" />
<Compile Include="Services\ServerService.cs" />
<Compile Include="Utils\ServerUtil.cs" />
<Compile Include="Services\ConfigurationService.cs" />
<Compile Include="Services\IndexerManagerService.cs" />
<Compile Include="Services\SonarApi.cs" />
<Compile Include="Startup.cs" />
<Compile Include="Models\TorznabQuery.cs" />
<Compile Include="WebApi.cs" />
<Compile Include="CurlHelper.cs" />
<Compile Include="Indexers\AlphaRatio.cs" />
<Compile Include="WebServer.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
<None Include="WebContent\fonts\glyphicons-halflings-regular.woff">
<None Include="Content\fonts\glyphicons-halflings-regular.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
@ -209,116 +215,116 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Include="WebContent\custom.css">
<Content Include="Content\custom.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\custom.js">
<Content Include="Content\custom.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\animebytes.png">
<Content Include="Content\logos\animebytes.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\beyondhd.png">
<Content Include="Content\logos\beyondhd.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\frenchtorrentdb.png">
<Content Include="Content\logos\frenchtorrentdb.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\hdtorrents.png">
<Content Include="Content\logos\hdtorrents.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\sceneaccess.png">
<Content Include="Content\logos\sceneaccess.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\scenetime.png">
<Content Include="Content\logos\scenetime.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\showrss.png">
<Content Include="Content\logos\showrss.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\t411.png">
<Content Include="Content\logos\t411.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\torrentday.png">
<Content Include="Content\logos\torrentday.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\torrentshack.png">
<Content Include="Content\logos\torrentshack.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\animate.css">
<Content Include="Content\animate.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\binding_dark.png">
<Content Include="Content\binding_dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\bootstrap-notify.js">
<Content Include="Content\bootstrap-notify.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\congruent_outline.png">
<Content Include="Content\congruent_outline.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\crissXcross.png">
<Content Include="Content\crissXcross.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\favicon.ico">
<Content Include="Content\favicon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\handlebars-v3.0.1.js">
<Content Include="Content\handlebars-v3.0.1.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\jacket_medium.png">
<Content Include="Content\jacket_medium.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\bithdtv.png">
<Content Include="Content\logos\bithdtv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\bitmetv.png">
<Content Include="Content\logos\bitmetv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\bootstrap\bootstrap.min.css">
<Content Include="Content\bootstrap\bootstrap.min.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\bootstrap\bootstrap.min.js">
<Content Include="Content\bootstrap\bootstrap.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\common.js">
<Content Include="Content\common.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\index.html">
<Content Include="Content\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\jquery-2.1.3.min.js">
<Content Include="Content\jquery-2.1.3.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\freshon.png">
<Content Include="Content\logos\freshon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\iptorrents.png">
<Content Include="Content\logos\iptorrents.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\morethantv.png">
<Content Include="Content\logos\morethantv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\rarbg.png">
<Content Include="Content\logos\rarbg.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\strike.png">
<Content Include="Content\logos\strike.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\thepiratebay.png">
<Content Include="Content\logos\thepiratebay.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\torrentleech.png">
<Content Include="Content\logos\torrentleech.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\torrentz.png">
<Content Include="Content\logos\torrentz.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\setup_indexer.html">
<Content Include="Content\setup_indexer.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\validator_reply.xml" />
<Content Include="WebContent\logos\alpharatio.png">
<Content Include="Content\logos\alpharatio.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autofac.Integration.WebApi;
using Jackett.Indexers;
namespace Jackett
{
@ -14,15 +15,15 @@ namespace Jackett
{
// Just register everything!
var thisAssembly = typeof(JackettModule).Assembly;
builder.RegisterAssemblyTypes(thisAssembly).AsImplementedInterfaces().SingleInstance();
builder.RegisterAssemblyTypes(thisAssembly).Except<IIndexer>().AsImplementedInterfaces().SingleInstance();
builder.RegisterApiControllers(thisAssembly).InstancePerRequest();
// Register indexers
foreach(var indexer in thisAssembly.GetTypes()
.Where(p => typeof(IndexerInterface).IsAssignableFrom(p) && !p.IsInterface)
.Where(p => typeof(IIndexer).IsAssignableFrom(p) && !p.IsInterface)
.ToArray())
{
builder.RegisterType(indexer).Named<IndexerInterface>(indexer.Name.ToLowerInvariant()).SingleInstance();
builder.RegisterType(indexer).Named<IIndexer>(indexer.Name.ToLowerInvariant()).SingleInstance();
}
}
}

View File

@ -1,31 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Models
{
public class ApiKey
{
public static string CurrentKey;
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
public static string Generate()
{
var randBytes = new byte[32];
var rngCsp = new RNGCryptoServiceProvider();
rngCsp.GetBytes(randBytes);
var key = "";
foreach (var b in randBytes)
{
key += chars[b % chars.Length];
}
return key;
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Models.Config
{
public class ServerConfig
{
public ServerConfig()
{
Port = 9117;
}
public int Port { get; set; }
public bool AllowExternal { get; set; }
public string APIKey { get; set; }
public string GetListenAddress(bool? external = null)
{
if (external == null)
{
external = AllowExternal;
}
return "http://" + (external.Value ? "*" : "localhost") + ":" + Port + "/";
}
public string GenerateApi()
{
var chars = "abcdefghijklmnopqrstuvwxyz0123456789";
var randBytes = new byte[32];
var rngCsp = new RNGCryptoServiceProvider();
rngCsp.GetBytes(randBytes);
var key = "";
foreach (var b in randBytes)
{
key += chars[b % chars.Length];
}
return key;
}
}
}

View File

@ -1,93 +0,0 @@
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.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett
{
public class Server
{
private static IContainer container = null;
private static string baseAddress = "http://localhost:9000/";
private static IDisposable _server = null;
static Server()
{
var builder = new ContainerBuilder();
builder.RegisterModule<JackettModule>();
container = builder.Build();
// Register the container in itself to allow for late resolves
var secondaryBuilder = new ContainerBuilder();
secondaryBuilder.RegisterInstance<IContainer>(container);
SetupLogging(secondaryBuilder, container.Resolve<IConfigurationService>());
secondaryBuilder.Update(container);
}
private static void SetupLogging(ContainerBuilder builder, IConfigurationService config)
{
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)
{
#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<Logger>(LogManager.GetCurrentClassLogger()).SingleInstance();
}
public static void Start()
{
_server = WebApp.Start<Startup>(url: baseAddress);
}
public static void Stop()
{
if (_server != null)
{
_server.Dispose();
}
}
public static IContainer GetContainer()
{
return container;
}
}
}

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -19,22 +20,115 @@ namespace Jackett.Services
string GetAppDataFolder();
JObject ReadServerSettingsFile();
string GetSonarrConfigFile();
T GetConfig<T>();
void SaveConfig<T>(T config);
string ApplicationFolder();
}
public class ConfigurationService: IConfigurationService
{
private ISerializeService serializeService;
private Logger logger;
public ConfigurationService(ISerializeService s, Logger l)
{
serializeService = s;
logger = l;
CreateOrMigrateSettings();
}
private void CreateOrMigrateSettings()
{
try
{
if (!Directory.Exists(GetAppDataFolder()))
{
Directory.CreateDirectory(GetAppDataFolder());
}
logger.Debug("App config/log directory: " + GetAppDataFolder());
}
catch (Exception ex)
{
throw new Exception("Could not create settings directory. " + ex.Message);
}
try
{
string oldDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jackett");
if (Directory.Exists(oldDir))
{
foreach (var file in Directory.GetFiles(oldDir, "*", SearchOption.AllDirectories))
{
var path = file.Replace(oldDir, "");
var destFolder = GetAppDataFolder()+ path;
if (!Directory.Exists(Path.GetDirectoryName(destFolder)))
{
Directory.CreateDirectory(Path.GetDirectoryName(destFolder));
}
File.Move(file, destFolder);
}
}
}
catch (Exception ex)
{
logger.Error("ERROR could not migrate settings directory " + ex);
}
}
public T GetConfig<T>()
{
var type = typeof(T);
var fullPath = Path.Combine(GetAppDataFolder(), type.Name + ".json");
try
{
if (!File.Exists(fullPath))
{
logger.Debug("Config file does not exist: " + fullPath);
return default(T);
}
return serializeService.DeSerialise<T>(File.ReadAllText(fullPath));
}
catch(Exception e)
{
logger.Error(e, "Error reading config file " + fullPath);
return default(T);
}
}
public void SaveConfig<T>(T config)
{
var type = typeof(T);
var fullPath = Path.Combine(GetAppDataFolder(), type.Name + ".json");
try
{
var json = serializeService.Serialise(config);
if (!Directory.Exists(GetAppDataFolder()))
Directory.CreateDirectory(GetAppDataFolder());
File.WriteAllText(fullPath, json);
}
catch (Exception e)
{
logger.Error(e, "Error reading config file " + fullPath);
}
}
public string ApplicationFolder()
{
return Path.GetDirectoryName(Application.ExecutablePath);
}
public string GetContentFolder()
{
var baseDir = Path.GetDirectoryName(Application.ExecutablePath);
// If we are debugging we can use the non copied content.
if (Debugger.IsAttached)
var dir = Path.Combine(ApplicationFolder(), "Content");
if (!Directory.Exists(dir))
{
return Path.Combine(baseDir, "..\\..\\..\\Jackett\\WebContent");
}
else
{
return Path.Combine(baseDir, "WebContent");
dir = Path.Combine(ApplicationFolder(), "..\\..\\..\\Jackett\\Content");
}
return dir;
}
public string GetVersion()
@ -44,7 +138,16 @@ namespace Jackett.Services
public string GetAppDataFolder()
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jackett"); ;
return GetAppDataFolderStatic();
}
/// <summary>
/// This is needed for the logger prior to ioc setup.
/// </summary>
/// <returns></returns>
public static string GetAppDataFolderStatic()
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett");
}
public string GetIndexerConfigDir()

View File

@ -1,4 +1,5 @@
using Autofac;
using Jackett.Indexers;
using Jackett.Models;
using Newtonsoft.Json.Linq;
using NLog;
@ -15,9 +16,10 @@ namespace Jackett.Services
{
void TestIndexer(string name);
void DeleteIndexer(string name);
IndexerInterface GetIndexer(string name);
IEnumerable<IndexerInterface> GetAllIndexers();
void SaveConfig(IndexerInterface indexer, JToken obj);
IIndexer GetIndexer(string name);
IEnumerable<IIndexer> GetAllIndexers();
void SaveConfig(IIndexer indexer, JToken obj);
void InitIndexers();
}
public class IndexerManagerService : IIndexerManagerService
@ -33,14 +35,35 @@ namespace Jackett.Services
logger = l;
}
public IndexerInterface GetIndexer(string name)
public void InitIndexers()
{
return container.ResolveNamed<IndexerInterface>(name.ToLowerInvariant());
// Load the existing config for each indexer
foreach (var indexer in GetAllIndexers())
{
var configFilePath = GetIndexerConfigFilePath(indexer);
if (File.Exists(configFilePath))
{
var jsonString = JObject.Parse(File.ReadAllText(configFilePath));
indexer.LoadFromSavedConfiguration(jsonString);
}
}
}
public IEnumerable<IndexerInterface> GetAllIndexers()
public IIndexer GetIndexer(string name)
{
return container.Resolve<IEnumerable<IndexerInterface>>().OrderBy(_ => _.DisplayName);
var indexer = GetAllIndexers().Where(i => string.Equals(i.DisplayName, name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
if (indexer == null)
{
logger.Error("Request for unknown indexer: " + name);
throw new Exception("Unknown indexer: " + name);
}
return indexer;
}
public IEnumerable<IIndexer> GetAllIndexers()
{
return container.Resolve<IEnumerable<IIndexer>>().OrderBy(_ => _.DisplayName);
}
public async void TestIndexer(string name)
@ -62,12 +85,12 @@ namespace Jackett.Services
//LoadMissingIndexers();
}
private string GetIndexerConfigFilePath(IndexerInterface indexer)
private string GetIndexerConfigFilePath(IIndexer indexer)
{
return Path.Combine(configService.GetIndexerConfigDir(), indexer.GetType().Name.ToLower() + ".json");
}
public void SaveConfig(IndexerInterface indexer, JToken obj)
public void SaveConfig(IIndexer indexer, JToken obj)
{
var configFilePath = GetIndexerConfigFilePath(indexer);
if (!Directory.Exists(configService.GetIndexerConfigDir()))

View File

@ -0,0 +1,64 @@
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Services
{
public interface IProcessService
{
void StartProcessAndLog(string exe, string args);
}
public class ProcessService : IProcessService
{
private Logger logger;
public ProcessService(Logger l)
{
logger = l;
}
public void StartProcessAndLog(string exe, string args)
{
var startInfo = new ProcessStartInfo()
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = exe,
Arguments = args,
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true
};
var proc = Process.Start(startInfo);
proc.OutputDataReceived += proc_OutputDataReceived;
proc.ErrorDataReceived += proc_ErrorDataReceived;
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
proc.OutputDataReceived -= proc_OutputDataReceived;
proc.ErrorDataReceived -= proc_ErrorDataReceived;
}
void proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.Data))
{
logger.Error(e.Data);
}
}
void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.Data))
{
logger.Debug(e.Data);
}
}
}
}

View File

@ -0,0 +1,35 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Services
{
public interface ISerializeService
{
string Serialise(object obj);
T DeSerialise<T>(string json);
}
class SerializeService : ISerializeService
{
public string Serialise(object obj)
{
return JsonConvert.SerializeObject(obj,Formatting.Indented);
}
public T DeSerialise<T>(string json)
{
try
{
return JsonConvert.DeserializeObject<T>(json);
}
catch
{
return default(T);
}
}
}
}

View File

@ -0,0 +1,136 @@
using Autofac;
using Jackett.Models.Config;
using Jackett.Services;
using Microsoft.Owin.Hosting;
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.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Services
{
public interface IServerService
{
void Start();
void Stop();
void ReserveUrls(bool doInstall = true);
ServerConfig Config { get; }
}
public class ServerService : IServerService
{
private ServerConfig config;
private IDisposable _server = null;
private IIndexerManagerService indexerService;
private IProcessService processService;
private ISerializeService serializeService;
private IConfigurationService configService;
private Logger logger;
public ServerService(IIndexerManagerService i, IProcessService p, ISerializeService s, IConfigurationService c, Logger l)
{
indexerService = i;
processService = p;
serializeService = s;
configService = c;
logger = l;
LoadConfig();
}
public ServerConfig Config
{
get { return config; }
}
private void LoadConfig()
{
// Load config
config = configService.GetConfig<ServerConfig>();
if (config == null)
{
config = new ServerConfig();
}
if (string.IsNullOrWhiteSpace(config.APIKey))
{
// Check for legacy key config
var apiKeyFile = Path.Combine(configService.GetAppDataFolder(), "api_key.txt");
if (File.Exists(apiKeyFile))
{
config.APIKey = File.ReadAllText(apiKeyFile);
}
// Check for legacy settings
var path = Path.Combine(configService.GetAppDataFolder(), "config.json"); ;
var jsonReply = new JObject();
if (File.Exists(path))
{
jsonReply = JObject.Parse(File.ReadAllText(path));
config.Port = (int)jsonReply["port"];
config.AllowExternal = (bool)jsonReply["public"];
}
if (string.IsNullOrWhiteSpace(config.APIKey))
{
config.APIKey = config.GenerateApi();
}
configService.SaveConfig<ServerConfig>(config);
}
}
public void Start()
{
// Allow all SSL.. sucks I know but mono on linux is having problems without it..
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
// Load indexers
indexerService.InitIndexers();
// Start the server
logger.Debug("Starting web server at " + config.GetListenAddress());
_server = WebApp.Start<Startup>(url: config.GetListenAddress());
logger.Debug("Web server started");
}
public void ReserveUrls(bool doInstall = true)
{
logger.Debug("Unreserving Urls");
RunNetSh(string.Format("http delete urlacl {0}", config.GetListenAddress(false)));
RunNetSh(string.Format("http delete urlacl {0}", config.GetListenAddress(true)));
if (doInstall)
{
logger.Debug("Reserving Urls");
RunNetSh(string.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", config.GetListenAddress()));
logger.Debug("Urls reserved");
}
}
private void RunNetSh(string args)
{
processService.StartProcessAndLog("netsh.exe", args);
}
public void Stop()
{
if (_server != null)
{
_server.Dispose();
}
}
}
}

View File

@ -0,0 +1,114 @@
using NLog;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
namespace Jackett.Services
{
public interface IServiceConfigService
{
void Install();
void Uninstall();
}
class ServiceConfigService : IServiceConfigService
{
private const string NAME = "Jackett";
private const string DESCRIPTION = "Additional indexers for Sonarr";
private const string SERVICEEXE = "JackettService.exe";
private IConfigurationService configService;
private Logger logger;
public ServiceConfigService(IConfigurationService c, Logger l)
{
configService = c;
logger = l;
}
public bool Exists()
{
return GetService(NAME) != null;
}
public ServiceController GetService(string serviceName)
{
return ServiceController.GetServices().FirstOrDefault(c => String.Equals(c.ServiceName, serviceName, StringComparison.InvariantCultureIgnoreCase));
}
public void Install()
{
if (Exists())
{
}
else
{
var installer = new ServiceProcessInstaller
{
Account = ServiceAccount.NetworkService
};
var serviceInstaller = new ServiceInstaller();
var exePath = Path.Combine(configService.ApplicationFolder(), SERVICEEXE);
if (!File.Exists(exePath) && Debugger.IsAttached)
{
exePath = Path.Combine(configService.ApplicationFolder(), "..\\..\\..\\Jackett.Service\\bin\\Debug", SERVICEEXE);
}
string[] cmdline = { @"/assemblypath=" + exePath};
var context = new InstallContext("jackettservice_install.log", cmdline);
serviceInstaller.Context = context;
serviceInstaller.DisplayName = NAME;
serviceInstaller.ServiceName = NAME;
serviceInstaller.Description = DESCRIPTION;
serviceInstaller.StartType = ServiceStartMode.Automatic;
serviceInstaller.Parent = installer;
serviceInstaller.Install(new ListDictionary());
}
}
public void Uninstall()
{
Stop();
var serviceInstaller = new ServiceInstaller();
var context = new InstallContext("jackettservice_uninstall.log", null);
serviceInstaller.Context = context;
serviceInstaller.ServiceName = NAME;
serviceInstaller.Uninstall(null);
logger.Info("The service was uninstalled.");
}
public void Stop()
{
var service = GetService(NAME);
if (service.Status != ServiceControllerStatus.Stopped)
{
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(60));
service.Refresh();
if (service.Status == ServiceControllerStatus.Stopped)
logger.Info("Service stopped.");
else
logger.Error("Failed to stop the service");
}
else
logger.Warn("The service was already stopped");
}
}
}

View File

@ -19,6 +19,7 @@ namespace Jackett
Task TestConnection();
SonarrApi.ConfigurationSonarr GetConfiguration();
Task ApplyConfiguration(JToken configJson);
Task<string[]> GetShowTitle(int rid);
}
public class SonarrApi: ISonarrApi
@ -62,6 +63,8 @@ namespace Jackett
public SonarrApi(IConfigurationService c)
{
configService = c;
LoadSettings();
cookies = new CookieContainer();
@ -74,8 +77,6 @@ namespace Jackett
client = new HttpClient(handler);
IdNameMappings = new ConcurrentDictionary<int, string[]>();
configService = c;
}
async Task ReloadNameMappings(string host, int port, string apiKey)

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Jackett.Services
{
public interface IRunTimeService
{
void Spin();
}
class RunTimeService : IRunTimeService
{
private bool isRunning = true;
public void Spin()
{
while (isRunning)
{
Thread.Sleep(2000);
}
}
}
}

View File

@ -23,30 +23,35 @@ namespace Jackett
{
// 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.DependencyResolver = new AutofacWebApiDependencyResolver(Engine.GetContainer());
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "Content",
routeTemplate: "{controller}/{action}",
defaults: new { controller = "Admin"}
name: "Admin",
routeTemplate: "admin/{action}",
defaults: new { controller = "Admin" }
);
config.Routes.MapHttpRoute(
name: "api",
routeTemplate: "api/{indexerName}",
defaults: new { controller = "API", action = "Call" }
);
config.Routes.MapHttpRoute(
name: "download",
routeTemplate: "api/{indexerName}/download/{path}/download.torrent",
defaults: new { controller = "Download", action = "Download" }
);
appBuilder.UseFileServer(new FileServerOptions
{
RequestPath = new PathString(string.Empty),
FileSystem = new PhysicalFileSystem(Server.GetContainer().Resolve<IConfigurationService>().GetContentFolder()),
FileSystem = new PhysicalFileSystem(Engine.ConfigService.GetContentFolder()),
EnableDirectoryBrowsing = true,
});
appBuilder.UseWebApi(config);
}
}
}

View File

@ -1,385 +0,0 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace Jackett
{
/*public class WebApi
{
static string WebContentFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebContent");
static string[] StaticFiles = Directory.EnumerateFiles(WebContentFolder, "*", SearchOption.AllDirectories).ToArray();
public WebServer server;
public enum WebApiMethod
{
GetConfigForm,
ConfigureIndexer,
GetIndexers,
TestIndexer,
DeleteIndexer,
GetSonarrConfig,
ApplySonarrConfig,
TestSonarr,
GetJackettConfig,
ApplyJackettConfig,
JackettRestart,
}
static Dictionary<string, WebApiMethod> WebApiMethods = new Dictionary<string, WebApiMethod> {
{ "get_config_form", WebApiMethod.GetConfigForm },
{ "configure_indexer", WebApiMethod.ConfigureIndexer },
{ "get_indexers", WebApiMethod.GetIndexers },
{ "test_indexer", WebApiMethod.TestIndexer },
{ "delete_indexer", WebApiMethod.DeleteIndexer },
{ "get_sonarr_config", WebApiMethod.GetSonarrConfig },
{ "apply_sonarr_config", WebApiMethod.ApplySonarrConfig },
{ "test_sonarr", WebApiMethod.TestSonarr },
{ "get_jackett_config",WebApiMethod.GetJackettConfig},
{ "apply_jackett_config",WebApiMethod.ApplyJackettConfig},
{ "jackett_restart", WebApiMethod.JackettRestart },
};
IndexerManager indexerManager;
SonarrApi sonarrApi;
public WebApi(IndexerManager indexerManager, SonarrApi sonarrApi)
{
this.indexerManager = indexerManager;
this.sonarrApi = sonarrApi;
}
public async Task<bool> HandleRequest(HttpListenerContext context)
{
string path = context.Request.Url.AbsolutePath.TrimStart('/');
if (path == "")
path = "index.html";
var sysPath = Path.Combine(WebContentFolder, path.Replace("/", Path.DirectorySeparatorChar.ToString()));
if (Array.IndexOf(StaticFiles, sysPath) > -1)
{
await ServeStaticFile(context, path);
return true;
}
WebApi.WebApiMethod apiMethod;
if (WebApi.WebApiMethods.TryGetValue(path, out apiMethod))
{
await ProcessWebApiRequest(context, apiMethod);
return true;
}
return false;
}
async Task ServeStaticFile(HttpListenerContext context, string file)
{
var contentFile = File.ReadAllBytes(Path.Combine(WebContentFolder, file));
context.Response.ContentType = MimeMapping.GetMimeMapping(file);
context.Response.StatusCode = (int)HttpStatusCode.OK;
try
{
await context.Response.OutputStream.WriteAsync(contentFile, 0, contentFile.Length);
}
catch (HttpListenerException)
{
}
}
async Task<JToken> ReadPostDataJson(Stream stream)
{
string postData = await new StreamReader(stream).ReadToEndAsync();
return JObject.Parse(postData);
}
delegate Task<JToken> HandlerTask(HttpListenerContext context);
async Task ProcessWebApiRequest(HttpListenerContext context, WebApiMethod method)
{
context.Response.ContentType = "text/json";
context.Response.StatusCode = (int)HttpStatusCode.OK;
HandlerTask handlerTask;
switch (method)
{
case WebApiMethod.GetConfigForm:
handlerTask = HandleConfigForm;
break;
case WebApiMethod.ConfigureIndexer:
handlerTask = HandleConfigureIndexer;
break;
case WebApiMethod.GetIndexers:
handlerTask = HandleGetIndexers;
break;
case WebApiMethod.TestIndexer:
handlerTask = HandleTestIndexer;
break;
case WebApiMethod.DeleteIndexer:
handlerTask = HandleDeleteIndexer;
break;
case WebApiMethod.GetSonarrConfig:
handlerTask = HandleGetSonarrConfig;
break;
case WebApiMethod.ApplySonarrConfig:
handlerTask = HandleApplySonarrConfig;
break;
case WebApiMethod.TestSonarr:
handlerTask = HandleTestSonarr;
break;
case WebApiMethod.ApplyJackettConfig:
handlerTask = HandleApplyJackettConfig;
break;
case WebApiMethod.GetJackettConfig:
handlerTask = HandleJackettConfig;
break;
// case WebApiMethod.JackettRestart:
// handlerTask = HandleJackettRestart;
break;
default:
handlerTask = HandleInvalidApiMethod;
break;
}
JToken jsonReply = await handlerTask(context);
await ReplyWithJson(context, jsonReply, method.ToString());
}
async Task ReplyWithJson(HttpListenerContext context, JToken json, string apiCall)
{
try
{
byte[] jsonBytes = Encoding.UTF8.GetBytes(json.ToString());
await context.Response.OutputStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
}
catch (Exception ex)
{
Console.WriteLine("Error writing json to stream for API call " + apiCall + Environment.NewLine + ex.ToString());
}
}
async Task<JToken> HandleTestSonarr(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try
{
await sonarrApi.TestConnection();
jsonReply["result"] = "success";
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
return jsonReply;
}
async Task<JToken> HandleApplySonarrConfig(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try
{
var postData = await ReadPostDataJson(context.Request.InputStream);
await sonarrApi.ApplyConfiguration(postData);
jsonReply["result"] = "success";
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
return jsonReply;
}
Task<JToken> HandleGetSonarrConfig(HttpListenerContext context)
{
JObject jsonReply = new JObject();
try
{
jsonReply["config"] = sonarrApi.GetConfiguration().ToJson();
jsonReply["result"] = "success";
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
return Task.FromResult<JToken>(jsonReply);
}
Task<JToken> HandleInvalidApiMethod(HttpListenerContext context)
{
JToken jsonReply = new JObject();
jsonReply["result"] = "error";
jsonReply["error"] = "Invalid API method";
return Task.FromResult<JToken>(jsonReply);
}
async Task<JToken> HandleConfigForm(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try
{
var postData = await ReadPostDataJson(context.Request.InputStream);
string indexerString = (string)postData["indexer"];
var indexer = indexerManager.GetIndexer(indexerString);
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 jsonReply;
}
async Task<JToken> HandleConfigureIndexer(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try
{
var postData = await ReadPostDataJson(context.Request.InputStream);
string indexerString = (string)postData["indexer"];
var indexer = indexerManager.GetIndexer(indexerString);
jsonReply["name"] = indexer.DisplayName;
await indexer.ApplyConfiguration(postData["config"]);
await indexerManager.TestIndexer(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 jsonReply;
}
Task<JToken> HandleGetIndexers(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try
{
jsonReply["result"] = "success";
jsonReply["api_key"] = ApiKey.CurrentKey;
jsonReply["app_version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString();
JArray items = new JArray();
foreach (var i in indexerManager.Indexers.OrderBy(_=>_.Key))
{
var indexer = i.Value;
var item = new JObject();
item["id"] = i.Key;
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 Task.FromResult<JToken>(jsonReply);
}
async Task<JToken> HandleTestIndexer(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try
{
var postData = await ReadPostDataJson(context.Request.InputStream);
string indexerString = (string)postData["indexer"];
var indexer = indexerManager.GetIndexer(indexerString);
jsonReply["name"] = indexer.DisplayName;
await indexerManager.TestIndexer(indexer);
jsonReply["result"] = "success";
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
return jsonReply;
}
async Task<JToken> HandleDeleteIndexer(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try
{
var postData = await ReadPostDataJson(context.Request.InputStream);
string indexerString = (string)postData["indexer"];
indexerManager.DeleteIndexer(indexerString);
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
return jsonReply;
}
//Jacket port functions
Task<JToken> HandleJackettConfig(HttpListenerContext context)
{
JObject jsonReply = new JObject();
try
{
jsonReply["config"] = WebServer.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 Task.FromResult<JToken>(jsonReply);
}
async Task<JToken> HandleApplyJackettConfig(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try
{
var postData = await ReadPostDataJson(context.Request.InputStream);
int port = await WebServer.ApplyPortConfiguration(postData);
jsonReply["result"] = "success";
jsonReply["port"] = port;
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
return jsonReply;
}
async Task<JToken> HandleJackettRestart(HttpListenerContext context)
{
// WebServer.RestartServer();
* null;
}
}*/
}

View File

@ -1,409 +0,0 @@
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<int> 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());
}
}*/
}