Add NZB search results to torbox and show basic status

This commit is contained in:
TheOtherP 2024-12-14 17:35:36 +01:00
parent 707d197449
commit 7b2e53f135
32 changed files with 382 additions and 135 deletions

View File

@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All system tests v1Migration profile" type="JUnit" factoryName="JUnit">
<module name="system"/>
<option name="MAIN_CLASS_NAME" value=""/>
<option name="METHOD_NAME" value=""/>
<option name="TEST_OBJECT" value="directory"/>
<option name="VM_PARAMETERS"
value="-Djunit.jupiter.extensions.autodetection.enabled=true -Dspring.profiles.active=systemtest,v1Migration -Dnzbhydra.port=5077 -Dnzbhydra.mockUrl=http://mockserver:5080 -Dnzbhydra.host.external=http://core:5076"/>
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$"/>
<envs>
<env name="nzbhydra.name" value="v1Migration"/>
</envs>
<dir value="$PROJECT_DIR$/tests/system/src/test/java/org/nzbhydra"/>
<method v="2">
<option name="Make" enabled="true"/>
</method>
</configuration>
</component>

View File

@ -18,6 +18,7 @@ package org.nzbhydra.downloading.downloaders;
import com.google.common.base.MoreObjects;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.nzbhydra.springnative.ReflectionMarker;
@ -28,6 +29,7 @@ import java.time.Instant;
@ReflectionMarker
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DownloaderEntry {
protected String nzbId;
protected String nzbName;

View File

@ -22,6 +22,7 @@ import org.nzbhydra.downloading.FileHandler;
import org.nzbhydra.downloading.IndexerSpecificDownloadExceptions;
import org.nzbhydra.downloading.downloaders.nzbget.NzbGet;
import org.nzbhydra.downloading.downloaders.sabnzbd.Sabnzbd;
import org.nzbhydra.downloading.downloaders.torbox.Torbox;
import org.nzbhydra.searching.db.SearchResultRepository;
import org.nzbhydra.webaccess.HydraOkHttp3ClientHttpRequestFactory;
import org.nzbhydra.webaccess.Ssl;
@ -58,6 +59,9 @@ public class DownloaderInstatiator {
case SABNZBD -> {
return new Sabnzbd(nzbHandler, searchResultRepository, applicationEventPublisher, indexerSpecificDownloadExceptions, configProvider, restTemplate, requestFactory);
}
case TORBOX -> {
return new Torbox(nzbHandler, searchResultRepository, applicationEventPublisher, indexerSpecificDownloadExceptions, configProvider, requestFactory);
}
}
throw new RuntimeException("Unable to instantiate " + downloaderType);
}

View File

@ -21,9 +21,6 @@ import org.nzbhydra.config.BaseConfig;
import org.nzbhydra.config.ConfigChangedEvent;
import org.nzbhydra.config.ConfigProvider;
import org.nzbhydra.config.downloading.DownloaderConfig;
import org.nzbhydra.downloading.DownloaderType;
import org.nzbhydra.downloading.downloaders.nzbget.NzbGet;
import org.nzbhydra.downloading.downloaders.sabnzbd.Sabnzbd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
@ -34,20 +31,11 @@ import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class DownloaderProvider implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(DownloaderProvider.class);
private static final Map<DownloaderType, Class<? extends Downloader>> downloaderClasses = new HashMap<>();
static {
downloaderClasses.put(DownloaderType.SABNZBD, Sabnzbd.class);
downloaderClasses.put(DownloaderType.NZBGET, NzbGet.class);
}
@Autowired
private ConfigProvider configProvider;
@Autowired

View File

@ -18,8 +18,10 @@ package org.nzbhydra.downloading.downloaders.torbox;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import org.jetbrains.annotations.NotNull;
import org.nzbhydra.GenericResponse;
import org.nzbhydra.config.ConfigProvider;
import org.nzbhydra.downloading.DownloaderType;
import org.nzbhydra.downloading.FileDownloadStatus;
import org.nzbhydra.downloading.FileHandler;
import org.nzbhydra.downloading.IndexerSpecificDownloadExceptions;
@ -27,6 +29,7 @@ import org.nzbhydra.downloading.downloaders.Downloader;
import org.nzbhydra.downloading.downloaders.DownloaderEntry;
import org.nzbhydra.downloading.downloaders.DownloaderStatus;
import org.nzbhydra.downloading.downloaders.torbox.mapping.AddUDlResponse;
import org.nzbhydra.downloading.downloaders.torbox.mapping.UsenetListResponse;
import org.nzbhydra.downloading.exceptions.DownloaderException;
import org.nzbhydra.searching.db.SearchResultRepository;
import org.nzbhydra.webaccess.HydraOkHttp3ClientHttpRequestFactory;
@ -44,7 +47,9 @@ import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@ -54,11 +59,18 @@ public class Torbox extends Downloader {
// TODO sist 14.12.2024: Try and parse error responses
// TODO sist 14.12.2024: Exclude nzbs.in
// TODO sist 14.12.2024: Check if bypass_cache for status check is OK and which limit should be used
// TODO sist 14.12.2024: Cache calls to usenet/torrents list so that we only make one call for history, status and queue
// TODO sist 14.12.2024: What are possible values for download_state?
private static final String HOST = "torbox.app";
private static final String BASE_URL = "https://api.torbox.app/v1/api";
private static final String BASE_URL = "https://torbox.app";
private static final String BASE_API_URL = "https://api.torbox.app/v1/api";
public static final Duration CACHE_TIME = Duration.ofSeconds(5);
private final RestTemplate restTemplate;
private Instant lastUpdate = Instant.MIN;
private final List<DownloaderEntry> lastDownloaderEntries = new ArrayList<>();
public Torbox(FileHandler nzbHandler, SearchResultRepository searchResultRepository, ApplicationEventPublisher applicationEventPublisher, IndexerSpecificDownloadExceptions indexerSpecificDownloadExceptions, ConfigProvider configProvider, HydraOkHttp3ClientHttpRequestFactory requestFactory) {
super(nzbHandler, searchResultRepository, applicationEventPublisher, indexerSpecificDownloadExceptions, configProvider);
@ -143,26 +155,87 @@ public class Torbox extends Downloader {
@Override
public DownloaderStatus getStatus() throws DownloaderException {
return null;
return DownloaderStatus.builder()
.downloaderName("Torbox")
.downloaderType(DownloaderType.TORBOX)
.state(DownloaderStatus.State.IDLE)
.url(BASE_URL)
.elementsInQueue(0)
.downloadingRatesInKilobytes(new ArrayList<>())
.build();
}
@Override
public List<DownloaderEntry> getHistory(Instant earliestDownload) throws DownloaderException {
return List.of();
return getDownloaderEntries()
.stream()
.filter(x -> x.getStatus().equals("failed") || x.getStatus().equals("completed"))
.toList();
}
@Override
public List<DownloaderEntry> getQueue(Instant earliestDownload) throws DownloaderException {
return List.of();
return getDownloaderEntries()
.stream()
.filter(x -> !x.getStatus().equals("failed") && !x.getStatus().equals("completed"))
.toList();
}
@NotNull
private List<DownloaderEntry> getDownloaderEntries() throws DownloaderException {
if (lastUpdate.isAfter(Instant.now().minus(CACHE_TIME))) {
return lastDownloaderEntries;
}
log.debug("Loading usenet list from torbox");
UriComponentsBuilder url = getBaseUrl()
.path("/usenet/mylist")
.queryParam("bypass_cache", true);
try {
ResponseEntity<UsenetListResponse> response = restTemplate.getForEntity(url.toUriString(), UsenetListResponse.class);
UsenetListResponse body = response.getBody();
if (response.getStatusCode().is2xxSuccessful()) {
List<DownloaderEntry> downloaderEntries = body.getData().stream().map(entry -> DownloaderEntry.builder()
.nzbId(String.valueOf(entry.getId()))
.nzbName(entry.getName())
.time(entry.getCreated_at())
.status(entry.getDownload_state())
.build())
.toList();
lastUpdate = Instant.now();
lastDownloaderEntries.clear();
lastDownloaderEntries.addAll(downloaderEntries);
return downloaderEntries;
}
log.error("Loading usenet list from torbox failed. Error: {}. Details:\n{}", body.getError(), body.getDetail());
throw new DownloaderException("Error loading usenet list from torbox. Error: " + body.getError());
} catch (RestClientException e) {
throw new DownloaderException("Error loading usenet list from torbox", e);
}
}
@Override
protected FileDownloadStatus getDownloadStatusFromDownloaderEntry(DownloaderEntry entry, StatusCheckType statusCheckType) {
return null;
switch (entry.getStatus()) {
case "completed" -> {
return FileDownloadStatus.CONTENT_DOWNLOAD_SUCCESSFUL;
}
case "failed" -> {
return FileDownloadStatus.CONTENT_DOWNLOAD_ERROR;
}
case "downloading" -> {
return FileDownloadStatus.NZB_ADDED;
}
default -> {
return FileDownloadStatus.NONE;
}
}
}
private UriComponentsBuilder getBaseUrl() {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(BASE_URL);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(BASE_API_URL);
return builder;
}
}

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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2583,7 +2583,14 @@ function downloaderStatusFooter() {
}
$scope.foo = downloaderStatus;
$scope.foo.downloaderImage = downloaderStatus.downloaderType === 'NZBGET' ? 'nzbgetlogo' : 'sabnzbdlogo';
if (downloaderStatus.downloaderType === 'NZBGET') {
$scope.foo.downloaderImage = 'nzbgetlogo';
}
if (downloaderStatus.downloaderType === 'TORBOX') {
$scope.foo.downloaderImage = 'torboxlogo';
} else {
$scope.foo.downloaderImage = 'sabnzbdlogo';
}
$scope.foo.url = downloaderStatus.url;
//We need to splice the variable with the rates because it's watched by angular and when overwriting it we would lose the watch and it wouldn't be updated
var maxEntriesHistory = 200;
@ -3220,13 +3227,13 @@ function connectionTest() {
if ($scope.type === "downloader") {
url = "internalapi/test_downloader";
params = {name: $scope.downloader, username: $scope.data.username, password: $scope.data.password};
if ($scope.downloader === "SABNZBD") {
params.apiKey = $scope.data.apiKey;
params.url = $scope.data.url;
} else {
if ($scope.downloader === "NZBGET") {
params.host = $scope.data.host;
params.port = $scope.data.port;
params.ssl = $scope.data.ssl;
} else {
params.apiKey = $scope.data.apiKey;
params.url = $scope.data.url;
}
} else if ($scope.data.type === "newznab") {
url = "internalapi/test_newznab";
@ -3517,6 +3524,16 @@ addableNzb.$inject = ["DebugService"];angular
.module('nzbhydraApp')
.directive('addableNzb', addableNzb);
function getCssClass(downloaderType) {
if (downloaderType === "SABNZBD") {
return "sabnzbd";
} else if (downloaderType === "TORBOX") {
return "torbox";
} else {
return "nzbget";
}
}
function addableNzb(DebugService) {
controller.$inject = ["$scope", "NzbDownloadService", "growl"];
return {
@ -3533,7 +3550,7 @@ function addableNzb(DebugService) {
if (!_.isNullOrEmpty($scope.downloader.iconCssClass)) {
$scope.cssClass = "fa fa-" + $scope.downloader.iconCssClass.replace("fa-", "").replace("fa ", "");
} else {
$scope.cssClass = $scope.downloader.downloaderType === "SABNZBD" ? "sabnzbd" : "nzbget";
$scope.cssClass = getCssClass($scope.downloader.downloaderType);
}
$scope.add = function () {
@ -3546,16 +3563,16 @@ function addableNzb(DebugService) {
}], $scope.alwaysAsk).then(function (response) {
if (response !== "dismissed") {
if (response.data.successful && (response.data.addedIds != null && response.data.addedIds.indexOf(Number($scope.searchresult.searchResultId)) > -1)) {
$scope.cssClass = $scope.downloader.downloaderType === "SABNZBD" ? "sabnzbd-success" : "nzbget-success";
$scope.cssClass = getCssClass($scope.downloader.downloaderType) + "-success";
} else {
$scope.cssClass = $scope.downloader.downloaderType === "SABNZBD" ? "sabnzbd-error" : "nzbget-error";
$scope.cssClass = getCssClass($scope.downloader.downloaderType) + "-error";
growl.error(response.data.message);
}
} else {
$scope.cssClass = originalClass;
}
}, function () {
$scope.cssClass = $scope.downloader.downloaderType === "SABNZBD" ? "sabnzbd-error" : "nzbget-error";
$scope.cssClass = getCssClass($scope.downloader.downloaderType) + "-error";
growl.error("An unexpected error occurred while trying to contact NZBHydra or add the NZB.");
})
};
@ -4919,6 +4936,14 @@ angular
nzbAccessType: "REDIRECT",
iconCssClass: "",
downloadType: "NZB"
},
{
downloaderType: "TORBOX",
name: "Torbox",
nzbAddingType: "UPLOAD",
nzbAccessType: "PROXY",
iconCssClass: "",
downloadType: "NZB"
}
];
@ -5016,10 +5041,11 @@ angular
}
}
},
{
}]);
fieldset.push({
key: 'url',
type: 'horizontalInput',
hideFor: ["TORBOX"],
templateOptions: {
type: 'text',
label: 'URL',
@ -5034,13 +5060,14 @@ angular
}
}
}
]);
);
if (model.downloaderType === "SABNZBD") {
if (model.downloaderType === "SABNZBD" || model.downloaderType === "TORBOX") {
fieldset.push({
key: 'apiKey',
type: 'horizontalInput',
showFor: ["SABNZBD", "TORBOX"],
templateOptions: {
type: 'text',
label: 'API Key'
@ -5086,43 +5113,47 @@ angular
})
}
fieldset = _.union(fieldset, [
fieldset.push(
{
key: 'defaultCategory',
type: 'horizontalInput',
hideFor: ["TORBOX"],
templateOptions: {
type: 'text',
label: 'Default category',
help: 'When adding NZBs this category will be used instead of asking for the category. Write "Use original category", "Use no category" or "Use mapped category" to not be asked.',
placeholder: 'Ask when downloading'
}
},
{
key: 'nzbAddingType',
type: 'horizontalSelect',
templateOptions: {
type: 'select',
label: 'NZB adding type',
options: [
{name: 'Send link', value: 'SEND_LINK'},
{name: 'Upload NZB', value: 'UPLOAD'}
],
help: "How NZBs are added to the downloader, either by sending a link to the NZB or by uploading the NZB data.",
tooltip: 'You can select if you want to upload the NZB to the downloader or send a Hydra link. The downloader will do the download itself. This is a matter of taste, but adding a link and redirecting the downloader is the fastest way.' +
'<br>Usually the links are determined using the URL via which you call it in your browser. If your downloader cannot access NZBHydra using that URL you can set a specific URL to be used in the main downloading config.',
advanced: true
}
},
{
key: 'addPaused',
type: 'horizontalSwitch',
templateOptions: {
type: 'switch',
label: 'Add paused',
help: 'Add NZBs paused',
advanced: true
}
},
});
fieldset.push({
key: 'nzbAddingType',
type: 'horizontalSelect',
hideFor: ["TORBOX"],
templateOptions: {
type: 'select',
label: 'NZB adding type',
options: [
{name: 'Send link', value: 'SEND_LINK'},
{name: 'Upload NZB', value: 'UPLOAD'}
],
help: "How NZBs are added to the downloader, either by sending a link to the NZB or by uploading the NZB data.",
tooltip: 'You can select if you want to upload the NZB to the downloader or send a Hydra link. The downloader will do the download itself. This is a matter of taste, but adding a link and redirecting the downloader is the fastest way.' +
'<br>Usually the links are determined using the URL via which you call it in your browser. If your downloader cannot access NZBHydra using that URL you can set a specific URL to be used in the main downloading config.',
advanced: true
}
});
fieldset.push({
key: 'addPaused',
type: 'horizontalSwitch',
hideFor: ["TORBOX"],
templateOptions: {
type: 'switch',
label: 'Add paused',
help: 'Add NZBs paused',
advanced: true
}
});
fieldset.push(
{
key: 'iconCssClass',
type: 'horizontalInput',
@ -5134,9 +5165,16 @@ angular
tooltip: 'If you have multiple downloaders of the same type you can select an icon from the Font Awesome library. This icon will be shown in the search results and the NZB download history instead of the default downloader icon.',
advanced: true
}
});
fieldset = fieldset.filter(function (field) {
if (field.showFor) {
return field.showFor.includes(model.downloaderType);
}
]);
if (field.hideFor) {
return !field.hideFor.includes(model.downloaderType);
}
return true;
});
return fieldset;
}
}
@ -7939,7 +7977,7 @@ function ConfigFields($injector) {
wrapper: 'fieldset',
templateOptions: {
label: 'General',
tooltip: 'Hydra allows sending NZB search results directly to downloaders (NZBGet, sabnzbd). Torrent downloaders are not supported.'
tooltip: 'Hydra allows sending NZB search results directly to downloaders (NZBGet, sabnzbd, torbox). Torrent downloaders are not supported.'
},
fieldGroup: [
{

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,56 @@
{
"success": true,
"error": null,
"detail": "Torrent list retrieved successfully.",
"data": [
{
"id": 364792,
"auth_id": "1668bedb-8f02-491d-ac8d-a606158c505b",
"server": 16,
"hash": "3f9aac158c7de8dfcab171ea58a17aabdf7fbc93",
"name": "ubuntu-24.10-desktop-amd64",
"magnet": null,
"size": 5665497088,
"active": false,
"created_at": "2024-12-14T16:32:01Z",
"updated_at": "2024-12-14T16:32:01Z",
"download_state": "cached",
"seeds": 0,
"peers": 0,
"ratio": 0,
"progress": 1,
"download_speed": 0,
"upload_speed": 0,
"eta": 0,
"torrent_file": false,
"expires_at": null,
"download_present": true,
"files": [
{
"id": 0,
"md5": "ea0baab241893d4f2b679e7179009e1c",
"hash": "3f9aac158c7de8dfcab171ea58a17aabdf7fbc93",
"name": "ubuntu-24.10-desktop-amd64/ubuntu-24.10-desktop-amd64.iso",
"size": 5665497088,
"s3_path": "3f9aac158c7de8dfcab171ea58a17aabdf7fbc93/ubuntu-24.10-desktop-amd64/ubuntu-24.10-desktop-amd64.iso",
"mimetype": "application/x-iso9660-image",
"short_name": "ubuntu-24.10-desktop-amd64.iso",
"absolute_path": "/completed/3f9aac158c7de8dfcab171ea58a17aabdf7fbc93/ubuntu-24.10-desktop-amd64/ubuntu-24.10-desktop-amd64.iso"
}
],
"download_path": "3f9aac158c7de8dfcab171ea58a17aabdf7fbc93",
"inactive_check": 0,
"availability": 0,
"download_finished": true,
"tracker": null,
"total_uploaded": 0,
"total_downloaded": 0,
"cached": true,
"owner": "1668bedb-8f02-491d-ac8d-a606158c505b",
"seed_torrent": false,
"allow_zipped": true,
"long_term_seeding": false,
"tracker_message": null
}
]
}

View File

@ -0,0 +1,29 @@
{
"success": true,
"error": null,
"detail": "Usenet downloads list retrieved successfully.",
"data": [
{
"id": 27949,
"created_at": "2024-12-14T10:27:00Z",
"updated_at": "2024-12-14T10:27:00Z",
"auth_id": "408f0af4-f476-4fb9-9f13-c62dbb1f4d45",
"name": "Die.Hamburger.Purzelschachtel",
"hash": "6b531e077cd6b8b86e190dc9614e6807",
"download_state": "failed",
"download_speed": 0,
"original_url": null,
"eta": 0,
"progress": 0,
"size": 444711567,
"download_id": "SABnzbd_nzo_1q30bevu",
"files": [],
"active": false,
"cached": false,
"download_present": false,
"download_finished": false,
"expires_at": null,
"server": 18
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
core/ui-src/img/torbox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 1500 1500" style="enable-background:new 0 0 1500 1500;" xml:space="preserve" width="100px" height="100px">
<style type="text/css">
.st0{fill:#00444D;}
.st1{fill:#34BA90;}
.st2{fill:#52A153;}
.st3{fill:#FFFFFF;}
</style>
<g>
<polygon class="st0" points="749.99,749.99 749.99,1191.96 367.25,970.97 367.25,529.01 "/>
<polygon class="st1" points="1132.75,529.01 1132.75,970.97 749.99,1191.96 749.99,749.99 872.87,679.05 956.71,630.66 "/>
<polygon class="st2" points="1132.75,529.01 749.99,749.99 367.25,529.01 749.99,308.04 "/>
<polygon class="st3" points="1043.04,739.36 958.66,1057.08 952.4,851.84 839.71,915.39 872.87,679.05 956.71,630.66
931.81,799.21 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

View File

@ -1783,7 +1783,7 @@ function ConfigFields($injector) {
wrapper: 'fieldset',
templateOptions: {
label: 'General',
tooltip: 'Hydra allows sending NZB search results directly to downloaders (NZBGet, sabnzbd). Torrent downloaders are not supported.'
tooltip: 'Hydra allows sending NZB search results directly to downloaders (NZBGet, sabnzbd, torbox). Torrent downloaders are not supported.'
},
fieldGroup: [
{

View File

@ -44,6 +44,14 @@ angular
nzbAccessType: "REDIRECT",
iconCssClass: "",
downloadType: "NZB"
},
{
downloaderType: "TORBOX",
name: "Torbox",
nzbAddingType: "UPLOAD",
nzbAccessType: "PROXY",
iconCssClass: "",
downloadType: "NZB"
}
];
@ -141,10 +149,11 @@ angular
}
}
},
{
}]);
fieldset.push({
key: 'url',
type: 'horizontalInput',
hideFor: ["TORBOX"],
templateOptions: {
type: 'text',
label: 'URL',
@ -159,13 +168,14 @@ angular
}
}
}
]);
);
if (model.downloaderType === "SABNZBD") {
if (model.downloaderType === "SABNZBD" || model.downloaderType === "TORBOX") {
fieldset.push({
key: 'apiKey',
type: 'horizontalInput',
showFor: ["SABNZBD", "TORBOX"],
templateOptions: {
type: 'text',
label: 'API Key'
@ -211,43 +221,47 @@ angular
})
}
fieldset = _.union(fieldset, [
fieldset.push(
{
key: 'defaultCategory',
type: 'horizontalInput',
hideFor: ["TORBOX"],
templateOptions: {
type: 'text',
label: 'Default category',
help: 'When adding NZBs this category will be used instead of asking for the category. Write "Use original category", "Use no category" or "Use mapped category" to not be asked.',
placeholder: 'Ask when downloading'
}
},
{
key: 'nzbAddingType',
type: 'horizontalSelect',
templateOptions: {
type: 'select',
label: 'NZB adding type',
options: [
{name: 'Send link', value: 'SEND_LINK'},
{name: 'Upload NZB', value: 'UPLOAD'}
],
help: "How NZBs are added to the downloader, either by sending a link to the NZB or by uploading the NZB data.",
tooltip: 'You can select if you want to upload the NZB to the downloader or send a Hydra link. The downloader will do the download itself. This is a matter of taste, but adding a link and redirecting the downloader is the fastest way.' +
'<br>Usually the links are determined using the URL via which you call it in your browser. If your downloader cannot access NZBHydra using that URL you can set a specific URL to be used in the main downloading config.',
advanced: true
}
},
{
key: 'addPaused',
type: 'horizontalSwitch',
templateOptions: {
type: 'switch',
label: 'Add paused',
help: 'Add NZBs paused',
advanced: true
}
},
});
fieldset.push({
key: 'nzbAddingType',
type: 'horizontalSelect',
hideFor: ["TORBOX"],
templateOptions: {
type: 'select',
label: 'NZB adding type',
options: [
{name: 'Send link', value: 'SEND_LINK'},
{name: 'Upload NZB', value: 'UPLOAD'}
],
help: "How NZBs are added to the downloader, either by sending a link to the NZB or by uploading the NZB data.",
tooltip: 'You can select if you want to upload the NZB to the downloader or send a Hydra link. The downloader will do the download itself. This is a matter of taste, but adding a link and redirecting the downloader is the fastest way.' +
'<br>Usually the links are determined using the URL via which you call it in your browser. If your downloader cannot access NZBHydra using that URL you can set a specific URL to be used in the main downloading config.',
advanced: true
}
});
fieldset.push({
key: 'addPaused',
type: 'horizontalSwitch',
hideFor: ["TORBOX"],
templateOptions: {
type: 'switch',
label: 'Add paused',
help: 'Add NZBs paused',
advanced: true
}
});
fieldset.push(
{
key: 'iconCssClass',
type: 'horizontalInput',
@ -259,9 +273,16 @@ angular
tooltip: 'If you have multiple downloaders of the same type you can select an icon from the Font Awesome library. This icon will be shown in the search results and the NZB download history instead of the default downloader icon.',
advanced: true
}
});
fieldset = fieldset.filter(function (field) {
if (field.showFor) {
return field.showFor.includes(model.downloaderType);
}
]);
if (field.hideFor) {
return !field.hideFor.includes(model.downloaderType);
}
return true;
});
return fieldset;
}
}

View File

@ -2,6 +2,16 @@ angular
.module('nzbhydraApp')
.directive('addableNzb', addableNzb);
function getCssClass(downloaderType) {
if (downloaderType === "SABNZBD") {
return "sabnzbd";
} else if (downloaderType === "TORBOX") {
return "torbox";
} else {
return "nzbget";
}
}
function addableNzb(DebugService) {
return {
templateUrl: 'static/html/directives/addable-nzb.html',
@ -17,7 +27,7 @@ function addableNzb(DebugService) {
if (!_.isNullOrEmpty($scope.downloader.iconCssClass)) {
$scope.cssClass = "fa fa-" + $scope.downloader.iconCssClass.replace("fa-", "").replace("fa ", "");
} else {
$scope.cssClass = $scope.downloader.downloaderType === "SABNZBD" ? "sabnzbd" : "nzbget";
$scope.cssClass = getCssClass($scope.downloader.downloaderType);
}
$scope.add = function () {
@ -30,16 +40,16 @@ function addableNzb(DebugService) {
}], $scope.alwaysAsk).then(function (response) {
if (response !== "dismissed") {
if (response.data.successful && (response.data.addedIds != null && response.data.addedIds.indexOf(Number($scope.searchresult.searchResultId)) > -1)) {
$scope.cssClass = $scope.downloader.downloaderType === "SABNZBD" ? "sabnzbd-success" : "nzbget-success";
$scope.cssClass = getCssClass($scope.downloader.downloaderType) + "-success";
} else {
$scope.cssClass = $scope.downloader.downloaderType === "SABNZBD" ? "sabnzbd-error" : "nzbget-error";
$scope.cssClass = getCssClass($scope.downloader.downloaderType) + "-error";
growl.error(response.data.message);
}
} else {
$scope.cssClass = originalClass;
}
}, function () {
$scope.cssClass = $scope.downloader.downloaderType === "SABNZBD" ? "sabnzbd-error" : "nzbget-error";
$scope.cssClass = getCssClass($scope.downloader.downloaderType) + "-error";
growl.error("An unexpected error occurred while trying to contact NZBHydra or add the NZB.");
})
};

View File

@ -43,13 +43,13 @@ function connectionTest() {
if ($scope.type === "downloader") {
url = "internalapi/test_downloader";
params = {name: $scope.downloader, username: $scope.data.username, password: $scope.data.password};
if ($scope.downloader === "SABNZBD") {
params.apiKey = $scope.data.apiKey;
params.url = $scope.data.url;
} else {
if ($scope.downloader === "NZBGET") {
params.host = $scope.data.host;
params.port = $scope.data.port;
params.ssl = $scope.data.ssl;
} else {
params.apiKey = $scope.data.apiKey;
params.url = $scope.data.url;
}
} else if ($scope.data.type === "newznab") {
url = "internalapi/test_newznab";

View File

@ -123,7 +123,14 @@ function downloaderStatusFooter() {
}
$scope.foo = downloaderStatus;
$scope.foo.downloaderImage = downloaderStatus.downloaderType === 'NZBGET' ? 'nzbgetlogo' : 'sabnzbdlogo';
if (downloaderStatus.downloaderType === 'NZBGET') {
$scope.foo.downloaderImage = 'nzbgetlogo';
}
if (downloaderStatus.downloaderType === 'TORBOX') {
$scope.foo.downloaderImage = 'torboxlogo';
} else {
$scope.foo.downloaderImage = 'sabnzbdlogo';
}
$scope.foo.url = downloaderStatus.url;
//We need to splice the variable with the rates because it's watched by angular and when overwriting it we would lose the watch and it wouldn't be updated
var maxEntriesHistory = 200;

View File

@ -37,6 +37,18 @@
background-image: url('../../img/nzbgeterror.png');
}
.torbox {
background-image: url('../../img/torbox.png');
}
.torbox-success {
background-image: url('../../img/torboxsuccess.png');
}
.torbox-error {
background-image: url('../../img/torboxerror.png');
}
.growl-container > .icon {
width: inherit;
height: inherit;

View File

@ -19,6 +19,7 @@ package org.nzbhydra.downloading;
public enum DownloaderType {
SABNZBD,
NZBGET
NZBGET,
TORBOX
}

View File

@ -19,7 +19,9 @@ package org.nzbhydra.downloading.downloaders;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.nzbhydra.downloading.DownloaderType;
import org.nzbhydra.springnative.ReflectionMarker;
@ -28,6 +30,9 @@ import java.util.List;
@Data
@ReflectionMarker
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DownloaderStatus {
public enum State {
@ -48,6 +53,7 @@ public class DownloaderStatus {
private String downloadRateFormatted;
private long downloadRateInKilobytes;
@Builder.Default
private List<Long> downloadingRatesInKilobytes = new ArrayList<>();
private Long lastDownloadRate;