diff --git a/src/Jackett/Content/custom.css b/src/Jackett/Content/custom.css
index 34874d758..bdf75e583 100644
--- a/src/Jackett/Content/custom.css
+++ b/src/Jackett/Content/custom.css
@@ -231,4 +231,16 @@ hr {
.jackettlogError {
background-color: #FF6060 !important;
+}
+
+.jackettdownloaded {
+ color: blueviolet;
+}
+
+.jacketdownloadlocal {
+ padding-left: 10px;
+}
+
+.downloadcolumn {
+ text-align:center;
}
\ No newline at end of file
diff --git a/src/Jackett/Content/custom.js b/src/Jackett/Content/custom.js
index 1161ca1a6..7ed47288c 100644
--- a/src/Jackett/Content/custom.js
+++ b/src/Jackett/Content/custom.js
@@ -2,14 +2,36 @@
$.ajaxSetup({ cache: false });
reloadIndexers();
loadJackettSettings();
+
+ $('body').on('click', '.downloadlink', function (e, b) {
+ $(e.target).addClass('jackettdownloaded');
+ });
+
+
+ $('body').on('click', '.jacketdownloadserver', function (event) {
+ var href = $(event.target).parent().attr('href');
+ var jqxhr = $.get(href, function (data) {
+ if (data.result == "error") {
+ doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
+ return;
+ } else {
+ doNotify("Downloaded sent to the blackhole successfully.", "success", "glyphicon glyphicon-ok");
+ }
+ }).fail(function () {
+ doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
+ });
+ event.preventDefault();
+ return false;
+ });
+
});
function loadJackettSettings() {
getJackettConfig(function (data) {
-
$("#api-key-input").val(data.config.api_key);
$("#app-version").html(data.app_version);
$("#jackett-port").val(data.config.port);
+ $("#jackett-savedir").val(data.config.blackholedir);
$("#jackett-allowext").attr('checked', data.config.external);
var password = data.config.password;
$("#jackett-adminpwd").val(password);
@@ -33,12 +55,14 @@ $("#jackett-show-releases").click(function () {
{
"targets": 0,
"visible": false,
- "searchable": false
+ "searchable": false,
+ "type": 'date'
},
{
"targets": 1,
"visible": false,
- "searchable": false
+ "searchable": false,
+ "type": 'date'
},
{
"targets": 2,
@@ -52,7 +76,31 @@ $("#jackett-show-releases").click(function () {
"searchable": false,
"iDataSort": 1
}
- ]
+ ],
+ initComplete: function () {
+ var count = 0;
+ this.api().columns().every(function () {
+ count++;
+ if (count === 5 || count ===7) {
+ var column = this;
+ var select = $('')
+ .appendTo($(column.footer()).empty())
+ .on('change', function () {
+ var val = $.fn.dataTable.util.escapeRegex(
+ $(this).val()
+ );
+
+ column
+ .search(val ? '^' + val + '$' : '', true, false)
+ .draw();
+ });
+
+ column.data().unique().sort().each(function (d, j) {
+ select.append('')
+ });
+ }
+ });
+ }
});
$("#modals").append(releaseDialog);
releaseDialog.modal("show");
@@ -80,13 +128,17 @@ $("#view-jackett-logs").click(function () {
$("#change-jackett-port").click(function () {
var jackett_port = $("#jackett-port").val();
var jackett_external = $("#jackett-allowext").is(':checked');
- var jsonObject = { port: jackett_port, external: jackett_external };
- var jqxhr = $.post("/admin/set_port", JSON.stringify(jsonObject), function (data) {
+ var jsonObject = {
+ port: jackett_port,
+ external: jackett_external,
+ blackholedir: $("#jackett-savedir").val()
+ };
+ var jqxhr = $.post("/admin/set_config", JSON.stringify(jsonObject), function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
return;
} else {
- doNotify("The port has been changed. Redirecting you to the new port.", "success", "glyphicon glyphicon-ok");
+ doNotify("Redirecting you to complete configuration update..", "success", "glyphicon glyphicon-ok");
window.setTimeout(function () {
url = window.location.href;
if (data.external) {
diff --git a/src/Jackett/Content/index.html b/src/Jackett/Content/index.html
index 8035cff32..6f03dc8c3 100644
--- a/src/Jackett/Content/index.html
+++ b/src/Jackett/Content/index.html
@@ -50,17 +50,36 @@
{{PublishDate}} |
{{FirstSeen}} |
- {{dateFormatRel PublishDate}} |
- {{dateFormatRel FirstSeen}} |
+ {{jacketTimespan PublishDate}} |
+ {{jacketTimespan FirstSeen}} |
{{Tracker}} |
{{Title}} |
{{CategoryDesc}} |
{{Seeders}} |
{{Peers}} |
- |
+
+
+ {{#if BlackholeLink}}
+
+ {{/if}}
+ |
{{/each}}
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
diff --git a/src/Jackett/Content/libs/handlebarsmoment.js b/src/Jackett/Content/libs/handlebarsmoment.js
index 851e9d7fd..55062fd16 100644
--- a/src/Jackett/Content/libs/handlebarsmoment.js
+++ b/src/Jackett/Content/libs/handlebarsmoment.js
@@ -8,10 +8,27 @@ Handlebars.registerHelper('dateFormat', function (context, block) {
};
});
-Handlebars.registerHelper('dateFormatRel', function (context, block) {
- if (window.moment) {
- return moment(context).fromNow(true);
- } else {
- return context;
- };
-});
\ No newline at end of file
+Handlebars.registerHelper('jacketTimespan', function (context, block) {
+ var now = moment();
+ var from = moment(context);
+ var timeSpan = moment.duration(now.diff(from));
+
+ var minutes = timeSpan.asMinutes();
+ if (minutes < 120) {
+ return Math.round(minutes) + 'm ago';
+ }
+
+ var hours = timeSpan.asHours();
+ if (hours < 48) {
+ return Math.round(hours) + 'h ago';
+ }
+
+ var days = timeSpan.asDays();
+ if (days < 365) {
+ return Math.round(days) + 'd ago';
+ }
+
+ var years = timeSpan.asYears();
+ return Math.round(years) + 'y ago';
+});
+
diff --git a/src/Jackett/Controllers/AdminController.cs b/src/Jackett/Controllers/AdminController.cs
index 04f215dbd..5762c7a64 100644
--- a/src/Jackett/Controllers/AdminController.cs
+++ b/src/Jackett/Controllers/AdminController.cs
@@ -286,6 +286,7 @@ namespace Jackett.Controllers
cfg["port"] = serverService.Config.Port;
cfg["external"] = serverService.Config.AllowExternal;
cfg["api_key"] = serverService.Config.APIKey;
+ cfg["blackholedir"] = serverService.Config.BlackholeDir;
cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword )? string.Empty:serverService.Config.AdminPassword.Substring(0,10);
jsonReply["config"] = cfg;
@@ -300,7 +301,7 @@ namespace Jackett.Controllers
return Json(jsonReply);
}
- [Route("set_port")]
+ [Route("set_config")]
[HttpPost]
public async Task SetConfig()
{
@@ -312,6 +313,7 @@ namespace Jackett.Controllers
var postData = await ReadPostDataJson();
int port = (int)postData["port"];
bool external = (bool)postData["external"];
+ string saveDir = (string)postData["blackholedir"];
if (port != Engine.Server.Config.Port || external != Engine.Server.Config.AllowExternal)
{
@@ -361,6 +363,21 @@ namespace Jackett.Controllers
})).Start();
}
+
+ if(saveDir != Engine.Server.Config.BlackholeDir)
+ {
+ if (!string.IsNullOrEmpty(saveDir))
+ {
+ if (!Directory.Exists(saveDir))
+ {
+ throw new Exception("Blackhole directory does not exist");
+ }
+ }
+
+ Engine.Server.Config.BlackholeDir = saveDir;
+ Engine.Server.SaveConfig();
+ }
+
jsonReply["result"] = "success";
jsonReply["port"] = port;
jsonReply["external"] = external;
diff --git a/src/Jackett/Controllers/BlackholeController.cs b/src/Jackett/Controllers/BlackholeController.cs
new file mode 100644
index 000000000..88a4ecae1
--- /dev/null
+++ b/src/Jackett/Controllers/BlackholeController.cs
@@ -0,0 +1,71 @@
+using Jackett.Services;
+using Newtonsoft.Json.Linq;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.IO;
+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
+{
+ [AllowAnonymous]
+ public class BlackholeController : ApiController
+ {
+ private Logger logger;
+ private IIndexerManagerService indexerService;
+
+ public BlackholeController(IIndexerManagerService i, Logger l)
+ {
+ logger = l;
+ indexerService = i;
+ }
+
+ [HttpGet]
+ public async Task Blackhole(string indexerID, string path)
+ {
+
+ var jsonReply = new JObject();
+ try
+ {
+ var indexer = indexerService.GetIndexer(indexerID);
+ if (!indexer.IsConfigured)
+ {
+ logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
+ throw new Exception("This indexer is not configured.");
+ }
+
+ var remoteFile = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
+ var downloadBytes = await indexer.Download(new Uri(remoteFile, UriKind.RelativeOrAbsolute));
+
+ if (string.IsNullOrWhiteSpace(Engine.Server.Config.BlackholeDir))
+ {
+ throw new Exception("Blackhole directory not set!");
+ }
+
+ if (!Directory.Exists(Engine.Server.Config.BlackholeDir))
+ {
+ throw new Exception("Blackhole directory does not exist: " + Engine.Server.Config.BlackholeDir);
+ }
+
+ var fileName = DateTime.Now.Ticks + ".torrent";
+ File.WriteAllBytes(Path.Combine(Engine.Server.Config.BlackholeDir, fileName), downloadBytes);
+ jsonReply["result"] = "success";
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, "Error downloading to blackhole " + indexerID + " " + path);
+ jsonReply["result"] = "error";
+ jsonReply["error"] = ex.Message;
+ }
+
+ return Json(jsonReply);
+ }
+ }
+}
diff --git a/src/Jackett/Controllers/PotatoController.cs b/src/Jackett/Controllers/PotatoController.cs
index 66b15a973..5815baa79 100644
--- a/src/Jackett/Controllers/PotatoController.cs
+++ b/src/Jackett/Controllers/PotatoController.cs
@@ -100,15 +100,14 @@ namespace Jackett.Controllers
}
releases = indexer.FilterResults(torznabQuery, releases);
-
- var severUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
-
- var proxiedReleases = releases.Select(s => Mapper.Map(s).ConvertToProxyLink(severUrl, indexerID));
-
+ var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
var potatoResponse = new TorrentPotatoResponse();
- foreach(var release in proxiedReleases)
+ foreach(var r in releases)
{
+ var release = Mapper.Map(r);
+ release.Link = release.ConvertToProxyLink(serverUrl, indexerID);
+
potatoResponse.results.Add(new TorrentPotatoResponseItem()
{
release_name = release.Title + "[" + indexer.DisplayName + "]", // Suffix the indexer so we can see which tracker we are using in CPS as it just says torrentpotato >.>
diff --git a/src/Jackett/Controllers/TorznabController.cs b/src/Jackett/Controllers/TorznabController.cs
index 515e62d44..fed5a110a 100644
--- a/src/Jackett/Controllers/TorznabController.cs
+++ b/src/Jackett/Controllers/TorznabController.cs
@@ -64,21 +64,25 @@ namespace Jackett.Controllers
var releases = await indexer.PerformQuery(torznabQuery);
+ // Some trackers do not keep their clocks up to date and can be ~20 minutes out!
+ foreach(var release in releases)
+ {
+ if (release.PublishDate > DateTime.Now)
+ release.PublishDate = DateTime.Now;
+ }
+
+ // Some trackers do not support multiple category filtering so filter the releases that match manually.
+ var filteredReleases = releases = indexer.FilterResults(torznabQuery, releases);
int? newItemCount = null;
+
// Cache non query results
if (string.IsNullOrEmpty(torznabQuery.SanitizedSearchTerm))
{
- newItemCount = cacheService.CacheRssResults(indexer, releases);
+ newItemCount = cacheService.GetNewItemCount(indexer, filteredReleases);
+ cacheService.CacheRssResults(indexer, releases);
}
- var releaseCount = releases.Count();
- releases = indexer.FilterResults(torznabQuery, releases);
-
- var removedInFilterCount = releaseCount - releases.Count();
- if (newItemCount.HasValue)
- newItemCount -= removedInFilterCount;
-
// Log info
var logBuilder = new StringBuilder();
if (newItemCount != null) {
@@ -94,20 +98,27 @@ namespace Jackett.Controllers
logger.Info(logBuilder.ToString());
- var severUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
+ var serverUrl = 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 = new Uri(indexer.SiteLink),
- ImageUrl = new Uri(severUrl + "logos/" + indexer.ID + ".png"),
+ ImageUrl = new Uri(serverUrl + "logos/" + indexer.ID + ".png"),
ImageTitle = indexer.DisplayName,
ImageLink = new Uri(indexer.SiteLink),
ImageDescription = indexer.DisplayName
});
- resultPage.Releases.AddRange(releases.Select(s=>Mapper.Map(s).ConvertToProxyLink(severUrl, indexerID)));
- var xml = resultPage.ToXml(new Uri(severUrl));
+
+ foreach(var result in releases)
+ {
+ var clone = Mapper.Map(result);
+ clone.Link = clone.ConvertToProxyLink(serverUrl, indexerID);
+ resultPage.Releases.Add(result);
+ }
+
+ var xml = resultPage.ToXml(new Uri(serverUrl));
// Force the return as XML
return new HttpResponseMessage()
{
diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj
index 89b91340a..85f6c83d4 100644
--- a/src/Jackett/Jackett.csproj
+++ b/src/Jackett/Jackett.csproj
@@ -172,6 +172,7 @@
+
diff --git a/src/Jackett/Models/Config/ServerConfig.cs b/src/Jackett/Models/Config/ServerConfig.cs
index d2c1b3fc0..02649cead 100644
--- a/src/Jackett/Models/Config/ServerConfig.cs
+++ b/src/Jackett/Models/Config/ServerConfig.cs
@@ -20,6 +20,7 @@ namespace Jackett.Models.Config
public string APIKey { get; set; }
public string AdminPassword { get; set; }
public string InstanceId { get; set; }
+ public string BlackholeDir { get; set; }
public string[] GetListenAddresses(bool? external = null)
{
diff --git a/src/Jackett/Models/ReleaseInfo.cs b/src/Jackett/Models/ReleaseInfo.cs
index baa3b7990..b4dd95d19 100644
--- a/src/Jackett/Models/ReleaseInfo.cs
+++ b/src/Jackett/Models/ReleaseInfo.cs
@@ -92,15 +92,14 @@ namespace Jackett.Models
return (long)(kb * 1024f);
}
- public ReleaseInfo ConvertToProxyLink(string serverUrl, string indexerId)
+ public Uri ConvertToProxyLink(string serverUrl, string indexerId, string action = "download")
{
if (Link == null || (Link.IsAbsoluteUri && Link.Scheme == "magnet"))
- return this;
+ return Link;
var originalLink = Link;
var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/t.torrent";
- var proxyLink = string.Format("{0}api/{1}/download/{2}", serverUrl, indexerId, encodedLink);
- Link = new Uri(proxyLink);
- return this;
+ var proxyLink = string.Format("{0}api/{1}/{2}/{3}", serverUrl, indexerId, action, encodedLink);
+ return new Uri(proxyLink);
}
}
}
diff --git a/src/Jackett/Models/TrackerCacheResult.cs b/src/Jackett/Models/TrackerCacheResult.cs
index 13b1569e8..1475c4ac8 100644
--- a/src/Jackett/Models/TrackerCacheResult.cs
+++ b/src/Jackett/Models/TrackerCacheResult.cs
@@ -11,5 +11,6 @@ namespace Jackett.Models
public DateTime FirstSeen { get; set; }
public string Tracker { get; set; }
public string CategoryDesc { get; set; }
+ public Uri BlackholeLink { get; set; }
}
}
diff --git a/src/Jackett/Services/CacheService.cs b/src/Jackett/Services/CacheService.cs
index 2ff1d73be..536c68620 100644
--- a/src/Jackett/Services/CacheService.cs
+++ b/src/Jackett/Services/CacheService.cs
@@ -11,21 +11,21 @@ namespace Jackett.Services
{
public interface ICacheService
{
- int CacheRssResults(IIndexer indexer, IEnumerable releases);
+ void CacheRssResults(IIndexer indexer, IEnumerable releases);
List GetCachedResults(string serverUrl);
+ int GetNewItemCount(IIndexer indexer, IEnumerable releases);
}
public class CacheService : ICacheService
{
private readonly List cache = new List();
- private readonly int MAX_RESULTS_PER_TRACKER = 250;
+ private readonly int MAX_RESULTS_PER_TRACKER = 1000;
private readonly TimeSpan AGE_LIMIT = new TimeSpan(7, 0, 0, 0);
- public int CacheRssResults(IIndexer indexer, IEnumerable releases)
+ public void CacheRssResults(IIndexer indexer, IEnumerable releases)
{
lock (cache)
{
- int newItemCount = 0;
var trackerCache = cache.Where(c => c.TrackerId == indexer.ID).FirstOrDefault();
if (trackerCache == null)
{
@@ -49,7 +49,6 @@ namespace Jackett.Services
existingItem = new CachedResult();
existingItem.Created = DateTime.Now;
trackerCache.Results.Add(existingItem);
- newItemCount++;
}
existingItem.Result = release;
@@ -60,6 +59,28 @@ namespace Jackett.Services
{
tracker.Results = tracker.Results.OrderByDescending(i => i.Created).Take(MAX_RESULTS_PER_TRACKER).ToList();
}
+ }
+ }
+
+ public int GetNewItemCount(IIndexer indexer, IEnumerable releases)
+ {
+ lock (cache)
+ {
+ int newItemCount = 0;
+ var trackerCache = cache.Where(c => c.TrackerId == indexer.ID).FirstOrDefault();
+ if (trackerCache != null)
+ {
+ foreach (var release in releases)
+ {
+ if (trackerCache.Results.Where(i => i.Result.Guid == release.Guid).Count() == 0)
+ {
+ newItemCount++;
+ }
+ }
+ }
+ else {
+ newItemCount++;
+ }
return newItemCount;
}
@@ -79,12 +100,15 @@ namespace Jackett.Services
item.FirstSeen = release.Created;
item.Tracker = tracker.TrackerName;
item.Peers = item.Peers - item.Seeders; // Use peers as leechers
- item.ConvertToProxyLink(serverUrl, tracker.TrackerId);
+ item.Link = item.ConvertToProxyLink(serverUrl, tracker.TrackerId);
+ if(item.Link!=null && item.Link.Scheme != "magnet" && !string.IsNullOrWhiteSpace(Engine.Server.Config.BlackholeDir))
+ item.BlackholeLink = item.ConvertToProxyLink(serverUrl, tracker.TrackerId, "blackhole");
+
results.Add(item);
}
}
- return results.OrderByDescending(i=>i.PublishDate).ToList();
+ return results.Take(3000).OrderByDescending(i=>i.PublishDate).ToList();
}
}
}
diff --git a/src/Jackett/Services/LogCacheService.cs b/src/Jackett/Services/LogCacheService.cs
index 66d89b610..4d8751a3b 100644
--- a/src/Jackett/Services/LogCacheService.cs
+++ b/src/Jackett/Services/LogCacheService.cs
@@ -30,7 +30,7 @@ namespace Jackett.Services
Message = l.Message,
When = l.TimeStamp
});
- logs = logs.Take(100).ToList();
+ logs = logs.Take(50).ToList();
}
}
diff --git a/src/Jackett/Startup.cs b/src/Jackett/Startup.cs
index d427afd4a..5a6419223 100644
--- a/src/Jackett/Startup.cs
+++ b/src/Jackett/Startup.cs
@@ -115,6 +115,12 @@ namespace Jackett
defaults: new { controller = "Download", action = "Download" }
);
+ config.Routes.MapHttpRoute(
+ name: "blackhole",
+ routeTemplate: "api/{indexerID}/blackhole/{path}/t.torrent",
+ defaults: new { controller = "Blackhole", action = "Blackhole" }
+ );
+
appBuilder.UseFileServer(new FileServerOptions
{
RequestPath = new PathString(string.Empty),