Files
lunasea/lib/modules/nzbget/core/api/api.dart
Jagandeep Brar f130c26f55 [Release] 3.2.0+320002 (#229)
* [Sonarr/Episode] Store media information
* [APIs] Updated home, search, wake_on_lan to capture stack from try-catch
* [Logger] Prepare for Sentry implementation
* [NZBGet] Improved StackTrace fetching/prep for Sentry logs
* [Radarr] Improved StackTrace fetching/prep for Sentry logs
* [SABnzbd] Improved StackTrace fetching/prep for Sentry logs
* [Sonarr] Improved StackTrace fetching/prep for Sentry logs
* [Logging] Capture and send exceptions to Sentry
* [Sentry] Implemented Sentry
* [Settings/System] Option to disable Sentry
2020-07-28 00:36:58 -05:00

659 lines
26 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/nzbget.dart';
class NZBGetAPI extends API {
final Map<String, dynamic> _values;
final Dio _dio;
NZBGetAPI._internal(this._values, this._dio);
factory NZBGetAPI.from(ProfileHiveObject profile) {
String _baseURL = Uri.encodeFull(profile.getNZBGet()['host']);
Map<String, dynamic> _headers = Map<String, dynamic>.from(profile.getNZBGet()['headers']);
if(profile.getNZBGet()['basic_auth']) {
String _auth = base64.encode(utf8.encode('${profile.getNZBGet()['user']}:${profile.getNZBGet()['pass']}'));
_headers['Authorization'] = 'Basic $_auth';
_baseURL += '/jsonrpc';
} else {
_baseURL += profile.getNZBGet()['user'] != '' && profile.getNZBGet()['pass'] != ''
? '/${profile.getNZBGet()['user']}:${profile.getNZBGet()['pass']}/jsonrpc'
: '/jsonrpc';
}
Dio _client = Dio(
BaseOptions(
baseUrl: _baseURL,
headers: _headers,
followRedirects: true,
maxRedirects: 5,
),
);
if(!profile.getNZBGet()['strict_tls']) {
(_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
};
}
return NZBGetAPI._internal(
profile.getNZBGet(),
_client,
);
}
void logWarning(String methodName, String text) => Logger.warning('NZBGetAPI', methodName, 'NZBGet: $text');
void logError(String methodName, String text, Object error, StackTrace trace, {
bool uploadToSentry = true,
}) => Logger.error(
'NZBGetAPI',
methodName,
'NZBGet: $text',
error,
trace,
uploadToSentry: uploadToSentry,
);
bool get enabled => _values['enabled'];
String get host => _values['host'];
String get user => _values['user'];
String get pass => _values['pass'];
String getBody(String method, {List<dynamic> params = Constants.EMPTY_LIST}) {
return json.encode({
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": 1,
});
}
Future<bool> testConnection() async {
try {
Response response = await _dio.post(
'',
data: getBody('version'),
);
if(response.statusCode == 200) return true;
} catch (error, stack) {
logError('testConnection', 'Connection test failed', error, stack, uploadToSentry: false);
}
return false;
}
Future<NZBGetStatusData> getStatus() async {
try {
Response response = await _dio.post(
'',
data: getBody('status'),
);
return NZBGetStatusData(
paused: response.data['result']['DownloadPaused'] ?? true,
speed: response.data['result']['DownloadRate'] ?? 0,
remainingHigh: response.data['result']['RemainingSizeHi'] ?? 0,
remainingLow: response.data['result']['RemainingSizeLo'] ?? 0,
speedlimit: response.data['result']['DownloadLimit'] ?? 0,
);
} on DioError catch (error, stack) {
logError('getStatus', 'Failed to fetch status', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('getStatus', 'Failed to fetch status', error, stack);
return Future.error(error, stack);
}
}
Future<NZBGetStatisticsData> getStatistics() async {
try {
Response response = await _dio.post(
'',
data: getBody('status'),
);
return NZBGetStatisticsData(
freeSpaceHigh: response.data['result']['FreeDiskSpaceHi'] ?? 0,
freeSpaceLow: response.data['result']['FreeDiskSpaceLo'] ?? 0,
downloadedHigh: response.data['result']['DownloadedSizeHi'] ?? 0,
downloadedLow: response.data['result']['DownloadedSizeLo'] ?? 0,
uptimeSeconds: response.data['result']['UpTimeSec'] ?? 0,
speedLimit: response.data['result']['DownloadRate'] ?? 0,
serverPaused: response.data['result']['DownloadPaused'] ?? true,
postPaused: response.data['result']['PostPaused'] ?? true,
scanPaused: response.data['result']['ScanPaused'] ?? true,
);
} on DioError catch (error, stack) {
logError('getStatistics', 'Failed to fetch statistics', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('getStatistics', 'Failed to fetch statistics', error, stack);
return Future.error(error, stack);
}
}
Future<List<NZBGetLogData>> getLogs({int amount = 25}) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'log',
params: [
0,
amount,
],
),
);
List<NZBGetLogData> _entries = [];
for(var entry in response.data['result']) {
_entries.add(NZBGetLogData(
id: entry['ID'],
kind: entry['Kind'],
time: entry['Time'],
text: entry['Text'],
));
}
return _entries;
} on DioError catch (error, stack) {
logError('getLogs', 'Failed to fetch logs ($amount)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('getLogs', 'Failed to fetch logs ($amount)', error, stack);
return Future.error(error, stack);
}
}
Future<List<NZBGetQueueData>> getQueue(int speed, int limit) async {
try {
Response response = await _dio.post(
'',
data: getBody('listgroups'),
);
List<NZBGetQueueData> _entries = [];
int queueSeconds = 0;
for(int i=0; i < min(limit, response.data['result'].length); i++) {
NZBGetQueueData _entry = NZBGetQueueData(
id: response.data['result'][i]['NZBID'] ?? -1,
name: response.data['result'][i]['NZBName'] ?? 'Unknown',
status: response.data['result'][i]['Status'] ?? 'UNKNOWN',
remaining: response.data['result'][i]['RemainingSizeMB'] ?? 0,
downloaded: response.data['result'][i]['DownloadedSizeMB'] ?? 0,
sizeTotal: response.data['result'][i]['FileSizeMB'] ?? 0,
category: response.data['result'][i]['Category'] ?? '',
speed: speed ?? -1,
queueSeconds: queueSeconds ?? 0,
);
if(_entry.status == 'QUEUED' || _entry.status == 'DOWNLOADING') queueSeconds += _entry.remainingTime;
_entries.add(_entry);
}
return _entries;
} on DioError catch (error, stack) {
logError('getQueue', 'Failed to fetch queue', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('getQueue', 'Failed to fetch queue', error, stack);
return Future.error(error, stack);
}
}
Future<List<NZBGetHistoryData>> getHistory({bool hidden = false}) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'history',
params: [hidden],
),
);
List<NZBGetHistoryData> _entries = [];
for(var entry in response.data['result']) {
_entries.add(NZBGetHistoryData(
id: entry['NZBID'] ?? -1,
name: entry['Name'] ?? 'Unknown',
status: entry['Status'] ?? 'Unkown',
timestamp: entry['HistoryTime'] ?? -1,
downloadedLow: entry['FileSizeLo'] ?? 0,
downloadedHigh: entry['FileSizeHi'] ?? 0,
category: entry['Category'] ?? 'Unknown',
storageLocation: entry['DestDir'] ?? 'Unknown',
downloadTime: entry['DownloadTimeSec'] ?? 0,
health: entry['Health'] ?? 0,
));
}
return _entries;
} on DioError catch (error, stack) {
logError('getHistory', 'Failed to fetch history', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('getHistory', 'Failed to fetch history', error, stack);
return Future.error(error, stack);
}
}
Future<bool> pauseQueue() async {
try {
Response response = await _dio.post(
'',
data: getBody('pausedownload'),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('pauseQueue', 'Failed to pause queue', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('pauseQueue', 'Failed to pause queue', error, stack);
return Future.error(error, stack);
}
}
Future<bool> pauseQueueFor(int minutes) async {
try {
await pauseQueue();
Response response = await _dio.post(
'',
data: getBody(
'scheduleresume',
params: [minutes*60],
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('pauseQueueFor', 'Failed to pause queue for $minutes minutes', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('pauseQueueFor', 'Failed to pause queue for $minutes minutes', error, stack);
return Future.error(error, stack);
}
}
Future<bool> resumeQueue() async {
try {
Response response = await _dio.post(
'',
data: getBody('resumedownload'),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('resumeQueue', 'Failed to resume queue', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('resumeQueue', 'Failed to resume queue', error, stack);
return Future.error(error, stack);
}
}
Future<bool> moveQueue(int id, int offset) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupMoveOffset',
'$offset',
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('moveQueue', 'Failed to move queue entry ($id, $offset)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('moveQueue', 'Failed to move queue entry ($id, $offset)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> pauseSingleJob(int id) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupPause',
'',
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('pauseSingleJob', 'Failed to pause job ($id)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('pauseSingleJob', 'Failed to pause job ($id)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> resumeSingleJob(int id) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupResume',
'',
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('resumeSingleJob', 'Failed to resume job ($id)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('resumeSingleJob', 'Failed to resume job ($id)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> deleteJob(int id) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupFinalDelete',
'',
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('deleteJob', 'Failed to delete job ($id)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('deleteJob', 'Failed to delete job ($id)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> renameJob(int id, String name) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupSetName',
name,
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('renameJob', 'Failed to rename job ($id, $name)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('renameJob', 'Failed to rename job ($id, $name)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> setJobPriority(int id, NZBGetPriority priority) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupSetPriority',
'${priority.value}',
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('setJobPriority', 'Failed to set job priority ($id, ${priority.name})', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('setJobPriority', 'Failed to set job priority ($id, ${priority.name})', error, stack);
return Future.error(error, stack);
}
}
Future<bool> setJobCategory(int id, NZBGetCategoryData category) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupApplyCategory',
category.name,
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('setJobCategory', 'Failed to set job category ($id, ${category.name})', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('setJobCategory', 'Failed to set job category ($id, ${category.name})', error, stack);
return Future.error(error, stack);
}
}
Future<bool> setJobPassword(int id, String password) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupSetParameter',
'*Unpack:Password=$password',
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('setJobPassword', 'Failed to set job password ($id, $password)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('setJobPassword', 'Failed to set job password ($id, $password)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> deleteHistoryEntry(int id, { bool hide = false}) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
hide ? 'HistoryDelete' : 'HistoryFinalDelete',
'',
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('deleteHistoryEntry', 'Failed to delete history entry ($id, $hide)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('deleteHistoryEntry', 'Failed to delete history entry ($id, $hide)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> retryHistoryEntry(int id) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'HistoryRedownload',
'',
[id],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('retryHistoryEntry', 'Failed to retry history entry ($id)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('retryHistoryEntry', 'Failed to retry history entry ($id)', error, stack);
return Future.error(error, stack);
}
}
Future<List<NZBGetCategoryData>> getCategories() async {
try {
Response response = await _dio.post(
'',
data: getBody('config'),
);
List<NZBGetCategoryData> _entries = [NZBGetCategoryData(name: '')];
for(var entry in response.data['result']) {
if(
entry['Name'] != null &&
entry['Name'].length >= 8 &&
entry['Name'].substring(0, 8) == 'Category' &&
entry['Name'].indexOf('.Name') != -1
) _entries.add(NZBGetCategoryData(
name: entry['Value'] ?? 'Unknown',
));
}
return _entries;
} on DioError catch (error, stack) {
logError('getCategories', 'Failed to fetch categories', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('getCategories', 'Failed to fetch categories', error, stack);
return Future.error(error, stack);
}
}
Future<bool> sortQueue(NZBGetSort sort) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'editqueue',
params: [
'GroupSort',
'${sort.value}',
[],
]
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('sortQueue', 'Failed to sort queue (${sort.name})', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('sortQueue', 'Failed to sort queue (${sort.name})', error, stack);
return Future.error(error, stack);
}
}
Future<bool> uploadURL(String url) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'append',
params: [
'', //NZBFileName
url, //Content
'', //Category
0, //Priority
false, //AddToTop
false, //AddPaused
url, //DupeKey
0, //DupeScore
'All', //DupeMode
[], //PPParameters
],
),
);
if(response.data['result'] != null && response.data['result'] > 0) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('uploadURL', 'Failed to add NZB by URL ($url)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('uploadURL', 'Failed to add NZB by URL ($url)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> uploadFile(String data, String name) async {
try {
String dataBase64 = utf8.fuse(base64).encode(data);
Response response = await _dio.post(
'',
data: getBody(
'append',
params: [
name, //NZBFileName
dataBase64, //Content
'', //Category
0, //Priority
false, //AddToTop
false, //AddPaused
name, //DupeKey
0, //DupeScore
'All', //DupeMode
[], //PPParameters
],
),
);
if(response.data['result'] != null && response.data['result'] > 0) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('uploadFile', 'Failed to add NZB by file ($name)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('uploadFile', 'Failed to add NZB by file ($name)', error, stack);
return Future.error(error, stack);
}
}
Future<bool> setSpeedLimit(int limit) async {
try {
Response response = await _dio.post(
'',
data: getBody(
'rate',
params: [limit],
),
);
if(response.data['result'] != null && response.data['result'] == true) return true;
throw(Error());
} on DioError catch (error, stack) {
logError('setSpeedLimit', 'Failed to set speed limit ($limit)', error, stack, uploadToSentry: false);
return Future.error(error, stack);
} catch (error, stack) {
logError('setSpeedLimit', 'Failed to set speed limit ($limit)', error, stack);
return Future.error(error, stack);
}
}
}