Web UI development started

This commit is contained in:
zone117x 2015-04-14 08:51:56 -06:00
parent 7b4e6f97d5
commit a4f18471a8
18 changed files with 4035 additions and 175 deletions

View File

@ -1,6 +1,16 @@
<?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.1"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -4,21 +4,16 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace Jackett
{
public class DataUrl
{
static Dictionary<string, string> ImageMimeTypes = new Dictionary<string, string>{
{ ".jpg", "data:image/jpeg" },
{ ".jpeg", "data:image/jpeg" },
{ ".png", "data:image/png" },
{ ".gif", "data:image/gif" }
};
public static string ReadFileToDataUrl(string file)
{
string mime = ImageMimeTypes[Path.GetExtension(file)];
string mime = MimeMapping.GetMimeMapping(file);
return "data:" + mime + ";base64," + Convert.ToBase64String(File.ReadAllBytes(file));
}

View File

@ -12,8 +12,7 @@ namespace Jackett
{
string DisplayName { get; }
string DisplayDescription { get; }
Uri SitLink { get; }
Uri SiteLink { get; }
// Retrieved for starting setup for the indexer via web API
Task<ConfigurationData> GetConfigurationForSetup();

View File

@ -13,30 +13,26 @@ namespace Jackett
static string AppConfigDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
static string IndexerConfigDirectory = Path.Combine(AppConfigDirectory, "Indexers");
Dictionary<string, IndexerInterface> loadedIndexers;
Dictionary<string, Type> implementedIndexerTypes;
public Dictionary<string, IndexerInterface> Indexers { get; private set; }
public IndexerManager()
{
loadedIndexers = new Dictionary<string, IndexerInterface>();
Indexers = new Dictionary<string, IndexerInterface>();
implementedIndexerTypes = (AppDomain.CurrentDomain.GetAssemblies()
var implementedIndexerTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(IndexerInterface).IsAssignableFrom(p)))
.ToDictionary(t => t.Name.ToLower());
.Where(p => typeof(IndexerInterface).IsAssignableFrom(p) && !p.IsInterface)
.ToArray();
// TODO: initialize all indexers at start, read all saved config json files then fill their indexers
foreach (var t in implementedIndexerTypes)
{
LoadIndexer(t);
}
}
IndexerInterface LoadIndexer(string name)
IndexerInterface LoadIndexer(Type indexerType)
{
name = name.Trim().ToLower();
Type indexerType;
if (!implementedIndexerTypes.TryGetValue(name, out indexerType))
throw new Exception(string.Format("No indexer of type '{0}'", name));
var name = indexerType.Name.Trim().ToLower();
IndexerInterface newIndexer = (IndexerInterface)Activator.CreateInstance(indexerType);
@ -47,15 +43,15 @@ namespace Jackett
newIndexer.LoadFromSavedConfiguration(jsonString);
}
loadedIndexers.Add(name, newIndexer);
Indexers.Add(name, newIndexer);
return newIndexer;
}
public IndexerInterface GetIndexer(string name)
{
IndexerInterface indexer;
if (!loadedIndexers.TryGetValue(name, out indexer))
indexer = LoadIndexer(name);
if (!Indexers.TryGetValue(name, out indexer))
throw new Exception(string.Format("No indexer with ID '{0}'", name));
return indexer;
}

View File

@ -60,7 +60,7 @@ namespace Jackett
public string DisplayName { get { return "BitMeTV.org"; } }
public string DisplayDescription { get { return "TV Episode specialty tracker"; } }
public Uri SitLink { get { return new Uri("https://bitmetv.org"); } }
public Uri SiteLink { get { return new Uri("https://bitmetv.org"); } }
public bool IsConfigured { get; private set; }
@ -81,7 +81,7 @@ namespace Jackett
return Task.Run(async () =>
{
var config = new BmtvConfig();
config.LoadValuesFromJson(configJson["config"]);
config.LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string>
{

View File

@ -11,8 +11,20 @@ namespace Jackett
public class Freshon : IndexerInterface
{
public string DisplayName { get; private set; }
public string DisplayName
{
get { return "FreshOnTV"; }
}
public string DisplayDescription
{
get { return "Our goal is to provide the latest stuff in the TV show domain"; }
}
public Uri SiteLink
{
get { return new Uri("https://freshon.tv/"); }
}
public Task<ConfigurationData> GetConfigurationForSetup()
{
@ -33,23 +45,12 @@ namespace Jackett
public bool IsConfigured
{
get { throw new NotImplementedException(); }
get { return false; }
}
public void LoadFromSavedConfiguration(JToken jsonConfig)
{
throw new NotImplementedException();
}
public string DisplayDescription
{
get { throw new NotImplementedException(); }
}
public Uri SitLink
{
get { throw new NotImplementedException(); }
}
}
}

View File

@ -51,7 +51,8 @@
<Reference Include="CsQuery">
<HintPath>..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
@ -85,10 +86,14 @@
<Compile Include="ResultPage.cs" />
<Compile Include="Server.cs" />
<Compile Include="TorznabQuery.cs" />
<Compile Include="WebApi.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
<None Include="WebContent\bootstrap\glyphicons-halflings-regular.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="Resources\test.xml" />
@ -100,20 +105,32 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Include="WebContent\handlebars-v3.0.1.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\bitmetv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\logos\freshon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\bootstrap\bootstrap.min.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\bootstrap\bootstrap.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\common.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\jquery-2.1.3.min.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\setup_indexer.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\IndexerImages\bitmetv.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\validator_reply.xml" />
</ItemGroup>

View File

@ -14,23 +14,12 @@ namespace Jackett
{
HttpListener listener;
IndexerManager indexerManager;
static string[] StaticFiles = Directory.EnumerateFiles("WebContent", "*", SearchOption.AllDirectories).Select(Path.GetFileName).ToArray();
enum WebApiMethod
{
GetConfigForm,
ConfigureIndexer
}
static Dictionary<string, WebApiMethod> WebApiMethods = new Dictionary<string, WebApiMethod>
{
{ "get_config_form", WebApiMethod.GetConfigForm },
{ "configure_indexer", WebApiMethod.ConfigureIndexer }
};
WebApi webApi;
public Server()
{
indexerManager = new IndexerManager();
webApi = new WebApi(indexerManager);
listener = new HttpListener();
listener.Prefixes.Add("http://*:9117/");
@ -42,7 +31,7 @@ namespace Jackett
while (true)
{
var context = await listener.GetContextAsync();
ProcessContext(context);
ProcessHttpRequest(context);
}
}
@ -52,114 +41,10 @@ namespace Jackett
listener.Abort();
}
static Dictionary<string, string> MimeMapping = new Dictionary<string, string> {
{ ".html", "text/html" },
{ ".js", "application/javascript" }
};
async void ServeStaticFile(HttpListenerContext context, string file)
async void ProcessHttpRequest(HttpListenerContext context)
{
var contentFile = File.ReadAllBytes(Path.Combine("WebContent", file));
string contentType;
MimeMapping.TryGetValue(Path.GetExtension(file), out contentType);
context.Response.ContentType = contentType;
context.Response.StatusCode = (int)HttpStatusCode.OK;
await context.Response.OutputStream.WriteAsync(contentFile, 0, contentFile.Length);
context.Response.OutputStream.Close();
}
async void ProcessWebApiRequest(HttpListenerContext context, WebApiMethod method)
{
var query = HttpUtility.ParseQueryString(context.Request.Url.Query);
string postData = await new StreamReader(context.Request.InputStream).ReadToEndAsync();
JToken dataJson = JObject.Parse(postData);
JToken jsonReply = new JObject();
var indexerString = (string)dataJson["indexer"];
IndexerInterface indexer;
try
if (webApi.HandleRequest(context))
{
indexer = indexerManager.GetIndexer(indexerString);
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
ReplyWithJson(context, jsonReply);
return;
}
context.Response.ContentType = "text/json";
context.Response.StatusCode = (int)HttpStatusCode.OK;
switch (method)
{
case WebApiMethod.GetConfigForm:
try
{
var config = await indexer.GetConfigurationForSetup();
jsonReply = config.ToJson();
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
break;
case WebApiMethod.ConfigureIndexer:
try
{
await indexer.ApplyConfiguration(dataJson);
await indexer.VerifyConnection();
jsonReply["result"] = "success";
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
if (ex is ExceptionWithConfigData)
{
jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson();
}
}
break;
default:
jsonReply["result"] = "error";
jsonReply["error"] = "Invalid API method";
break;
}
ReplyWithJson(context, jsonReply);
}
async void ReplyWithJson(HttpListenerContext context, JToken json)
{
byte[] jsonBytes = Encoding.UTF8.GetBytes(json.ToString());
await context.Response.OutputStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
context.Response.OutputStream.Close();
}
async void ProcessContext(HttpListenerContext context)
{
Console.WriteLine(context.Request.Url.Query);
string path = context.Request.Url.AbsolutePath.TrimStart('/');
if (path == "")
path = "index.html";
if (Array.IndexOf(StaticFiles, path) > -1)
{
ServeStaticFile(context, path);
return;
}
WebApiMethod apiMethod;
if (WebApiMethods.TryGetValue(path, out apiMethod))
{
ProcessWebApiRequest(context, apiMethod);
return;
}

164
src/Jackett/WebApi.cs Normal file
View File

@ -0,0 +1,164 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace Jackett
{
public class WebApi
{
static string WebContentFolder = "WebContent";
static string[] StaticFiles = Directory.EnumerateFiles(WebContentFolder, "*", SearchOption.AllDirectories).ToArray();
public enum WebApiMethod
{
GetConfigForm,
ConfigureIndexer,
GetIndexers
}
static Dictionary<string, WebApiMethod> WebApiMethods = new Dictionary<string, WebApiMethod>
{
{ "get_config_form", WebApiMethod.GetConfigForm },
{ "configure_indexer", WebApiMethod.ConfigureIndexer },
{ "get_indexers", WebApiMethod.GetIndexers }
};
IndexerManager indexerManager;
public WebApi(IndexerManager indexerManager)
{
this.indexerManager = indexerManager;
}
public 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)
{
ServeStaticFile(context, path);
return true;
}
WebApi.WebApiMethod apiMethod;
if (WebApi.WebApiMethods.TryGetValue(path, out apiMethod))
{
ProcessWebApiRequest(context, apiMethod);
return true;
}
return false;
}
async void 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;
await context.Response.OutputStream.WriteAsync(contentFile, 0, contentFile.Length);
context.Response.OutputStream.Close();
}
async Task<JToken> ReadPostDataJson(Stream stream)
{
string postData = await new StreamReader(stream).ReadToEndAsync();
return JObject.Parse(postData);
}
async void ProcessWebApiRequest(HttpListenerContext context, WebApiMethod method)
{
var query = HttpUtility.ParseQueryString(context.Request.Url.Query);
JToken jsonReply = new JObject();
context.Response.ContentType = "text/json";
context.Response.StatusCode = (int)HttpStatusCode.OK;
switch (method)
{
case WebApiMethod.GetConfigForm:
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["result"] = "success";
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
break;
case WebApiMethod.ConfigureIndexer:
try
{
var postData = await ReadPostDataJson(context.Request.InputStream);
string indexerString = (string)postData["indexer"];
var indexer = indexerManager.GetIndexer(indexerString);
await indexer.ApplyConfiguration(postData["config"]);
await indexer.VerifyConnection();
jsonReply["result"] = "success";
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
if (ex is ExceptionWithConfigData)
{
jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson();
}
}
break;
case WebApiMethod.GetIndexers:
try
{
jsonReply["result"] = "success";
JArray items = new JArray();
foreach (var i in indexerManager.Indexers)
{
var indexer = i.Value;
var item = new JObject();
item["id"] = i.Key;
item["display_name"] = indexer.DisplayName;
item["display_description"] = indexer.DisplayDescription;
item["is_configured"] = indexer.IsConfigured;
item["site_link"] = indexer.SiteLink;
items.Add(item);
}
jsonReply["items"] = items;
}
catch (Exception ex)
{
jsonReply["result"] = "error";
jsonReply["error"] = ex.Message;
}
break;
default:
jsonReply["result"] = "error";
jsonReply["error"] = "Invalid API method";
break;
}
ReplyWithJson(context, jsonReply);
}
async void ReplyWithJson(HttpListenerContext context, JToken json)
{
byte[] jsonBytes = Encoding.UTF8.GetBytes(json.ToString());
await context.Response.OutputStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
context.Response.OutputStream.Close();
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,9 +3,42 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script src="jquery-2.1.3.min.js"></script>
<script src="handlebars-v3.0.1.js"></script>
<script src="bootstrap/bootstrap.min.js"></script>
<link href="bootstrap/bootstrap.min.css" rel="stylesheet">
<title>Jackett</title>
<style>
#templates {
display: none;
}
</style>
</head>
<body>
home
<div class="container-fluid">
<div id="indexers"></div>
</div>
<div id="templates">
<div class="indexer">
<div class="indexer-name">{{name}}</div>
<div class="indexer-description">{{description}}</div>
<div class="indexer-link"><a href="{{link}}">{{link}}</a></div>
<div class="indexer-configured">
{{#if configured}}
<span>Configred</span>
{{else}}
<span>Not configured</span>
{{/if}}
</div>
</div>
</div>
<script>
$(function () {
var indexerTemplate = Handlebars.compile($("#templates > .indexer").html());
$('#indexers').append(indexerTemplate({ name: "n", description: "desc", link: "http://google.com", configured: true }));
});
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -20,7 +20,7 @@
var urlParams = getUrlParams();
var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: urlParams.indexer }), function (data) {
populateForm(data);
populateForm(data.config);
})
.fail(function () {
alert("error");