Some work about adaptive color scheme for Web UI (PR #19901)
http://[316:c51a:62a3:8b9::4]/d4708/qBittorrent/src/branch/adaptive-webui
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
989 lines
31 KiB
989 lines
31 KiB
/* |
|
* Bittorrent Client using Qt and libtorrent. |
|
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru> |
|
* |
|
* This program is free software; you can redistribute it and/or |
|
* modify it under the terms of the GNU General Public License |
|
* as published by the Free Software Foundation; either version 2 |
|
* of the License, or (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program; if not, write to the Free Software |
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
* |
|
* In addition, as a special exception, the copyright holders give permission to |
|
* link this program with the OpenSSL project's "OpenSSL" library (or with |
|
* modified versions of it that use the same license as the "OpenSSL" library), |
|
* and distribute the linked executables. You must obey the GNU General Public |
|
* License in all respects for all of the code used other than "OpenSSL". If you |
|
* modify file(s), you may extend this exception to your version of the file(s), |
|
* but you are not obligated to do so. If you do not wish to do so, delete this |
|
* exception statement from your version. |
|
*/ |
|
|
|
#include "webapplication.h" |
|
|
|
#include <queue> |
|
#include <vector> |
|
|
|
#include <QCoreApplication> |
|
#include <QCryptographicHash> |
|
#include <QDebug> |
|
#include <QRegularExpression> |
|
#include <QTimer> |
|
|
|
#include "base/bittorrent/session.h" |
|
#include "base/bittorrent/torrenthandle.h" |
|
#include "base/bittorrent/torrentinfo.h" |
|
#include "base/bittorrent/trackerentry.h" |
|
#include "base/iconprovider.h" |
|
#include "base/logger.h" |
|
#include "base/net/downloadmanager.h" |
|
#include "base/preferences.h" |
|
#include "base/tristatebool.h" |
|
#include "base/utils/fs.h" |
|
#include "base/utils/misc.h" |
|
#include "base/utils/string.h" |
|
#include "btjson.h" |
|
#include "jsonutils.h" |
|
#include "prefjson.h" |
|
#include "websessiondata.h" |
|
|
|
static const int API_VERSION = 15; |
|
static const int API_VERSION_MIN = 15; |
|
|
|
const QString WWW_FOLDER = ":/www/public/"; |
|
const QString PRIVATE_FOLDER = ":/www/private/"; |
|
const QString DEFAULT_SCOPE = "public"; |
|
const QString SCOPE_IMAGES = "images"; |
|
const QString SCOPE_THEME = "theme"; |
|
const QString DEFAULT_ACTION = "index"; |
|
const QString WEBUI_ACTION = "webui"; |
|
const QString VERSION_INFO = "version"; |
|
const QString MAX_AGE_MONTH = "public, max-age=2592000"; |
|
|
|
#define ADD_ACTION(scope, action) actions[#scope][#action] = &WebApplication::action_##scope##_##action |
|
|
|
QMap<QString, QMap<QString, WebApplication::Action>> WebApplication::initializeActions() |
|
{ |
|
QMap<QString,QMap<QString, WebApplication::Action>> actions; |
|
|
|
ADD_ACTION(public, webui); |
|
ADD_ACTION(public, index); |
|
ADD_ACTION(public, login); |
|
ADD_ACTION(public, logout); |
|
ADD_ACTION(public, theme); |
|
ADD_ACTION(public, images); |
|
ADD_ACTION(query, torrents); |
|
ADD_ACTION(query, preferences); |
|
ADD_ACTION(query, transferInfo); |
|
ADD_ACTION(query, propertiesGeneral); |
|
ADD_ACTION(query, propertiesTrackers); |
|
ADD_ACTION(query, propertiesWebSeeds); |
|
ADD_ACTION(query, propertiesFiles); |
|
ADD_ACTION(query, getLog); |
|
ADD_ACTION(query, getPeerLog); |
|
ADD_ACTION(query, getPieceHashes); |
|
ADD_ACTION(query, getPieceStates); |
|
ADD_ACTION(sync, maindata); |
|
ADD_ACTION(sync, torrent_peers); |
|
ADD_ACTION(command, shutdown); |
|
ADD_ACTION(command, download); |
|
ADD_ACTION(command, upload); |
|
ADD_ACTION(command, addTrackers); |
|
ADD_ACTION(command, resumeAll); |
|
ADD_ACTION(command, pauseAll); |
|
ADD_ACTION(command, resume); |
|
ADD_ACTION(command, pause); |
|
ADD_ACTION(command, setPreferences); |
|
ADD_ACTION(command, setFilePrio); |
|
ADD_ACTION(command, getGlobalUpLimit); |
|
ADD_ACTION(command, getGlobalDlLimit); |
|
ADD_ACTION(command, setGlobalUpLimit); |
|
ADD_ACTION(command, setGlobalDlLimit); |
|
ADD_ACTION(command, getTorrentsUpLimit); |
|
ADD_ACTION(command, getTorrentsDlLimit); |
|
ADD_ACTION(command, setTorrentsUpLimit); |
|
ADD_ACTION(command, setTorrentsDlLimit); |
|
ADD_ACTION(command, alternativeSpeedLimitsEnabled); |
|
ADD_ACTION(command, toggleAlternativeSpeedLimits); |
|
ADD_ACTION(command, toggleSequentialDownload); |
|
ADD_ACTION(command, toggleFirstLastPiecePrio); |
|
ADD_ACTION(command, setSuperSeeding); |
|
ADD_ACTION(command, setForceStart); |
|
ADD_ACTION(command, delete); |
|
ADD_ACTION(command, deletePerm); |
|
ADD_ACTION(command, increasePrio); |
|
ADD_ACTION(command, decreasePrio); |
|
ADD_ACTION(command, topPrio); |
|
ADD_ACTION(command, bottomPrio); |
|
ADD_ACTION(command, setLocation); |
|
ADD_ACTION(command, rename); |
|
ADD_ACTION(command, setAutoTMM); |
|
ADD_ACTION(command, recheck); |
|
ADD_ACTION(command, setCategory); |
|
ADD_ACTION(command, addCategory); |
|
ADD_ACTION(command, removeCategories); |
|
ADD_ACTION(command, getSavePath); |
|
ADD_ACTION(version, api); |
|
ADD_ACTION(version, api_min); |
|
ADD_ACTION(version, qbittorrent); |
|
|
|
return actions; |
|
} |
|
|
|
namespace |
|
{ |
|
#define CHECK_URI(ARGS_NUM) \ |
|
if (args_.size() != ARGS_NUM) { \ |
|
status(404, "Not Found"); \ |
|
return; \ |
|
} |
|
#define CHECK_PARAMETERS(PARAMETERS) \ |
|
QStringList parameters; \ |
|
parameters << PARAMETERS; \ |
|
if (parameters.size() != request().posts.size()) { \ |
|
status(400, "Bad Request"); \ |
|
return; \ |
|
} \ |
|
foreach (QString key, request().posts.keys()) { \ |
|
if (!parameters.contains(key, Qt::CaseInsensitive)) { \ |
|
status(400, "Bad Request"); \ |
|
return; \ |
|
} \ |
|
} |
|
|
|
bool parseBool(const QString &string, const bool defaultValue) |
|
{ |
|
if (defaultValue) |
|
return (string.compare("false", Qt::CaseInsensitive) == 0) ? false : true; |
|
return (string.compare("true", Qt::CaseInsensitive) == 0) ? true : false; |
|
} |
|
|
|
TriStateBool parseTristatebool(const QString &string) |
|
{ |
|
if (string.compare("true", Qt::CaseInsensitive) == 0) |
|
return TriStateBool::True; |
|
if (string.compare("false", Qt::CaseInsensitive) == 0) |
|
return TriStateBool::False; |
|
return TriStateBool::Undefined; |
|
} |
|
} |
|
|
|
void WebApplication::action_public_index() |
|
{ |
|
QString path; |
|
|
|
if (!args_.isEmpty()) { |
|
if (args_.back() == "favicon.ico") |
|
path = ":/icons/skin/qbittorrent16.png"; |
|
else |
|
path = WWW_FOLDER + args_.join("/"); |
|
} |
|
|
|
printFile(path); |
|
} |
|
|
|
void WebApplication::action_public_webui() |
|
{ |
|
if (!sessionActive()) |
|
printFile(PRIVATE_FOLDER + "login.html"); |
|
else |
|
printFile(PRIVATE_FOLDER + "index.html"); |
|
} |
|
|
|
void WebApplication::action_public_login() |
|
{ |
|
if (sessionActive()) { |
|
print(QByteArray("Ok."), Http::CONTENT_TYPE_TXT); |
|
return; |
|
} |
|
|
|
const Preferences *const pref = Preferences::instance(); |
|
QCryptographicHash md5(QCryptographicHash::Md5); |
|
|
|
md5.addData(request().posts["password"].toLocal8Bit()); |
|
QString pass = md5.result().toHex(); |
|
|
|
bool equalUser = Utils::String::slowEquals(request().posts["username"].toUtf8(), pref->getWebUiUsername().toUtf8()); |
|
bool equalPass = Utils::String::slowEquals(pass.toUtf8(), pref->getWebUiPassword().toUtf8()); |
|
|
|
if (equalUser && equalPass) { |
|
sessionStart(); |
|
print(QByteArray("Ok."), Http::CONTENT_TYPE_TXT); |
|
} |
|
else { |
|
QString addr = env().clientAddress.toString(); |
|
increaseFailedAttempts(); |
|
qDebug("client IP: %s (%d failed attempts)", qUtf8Printable(addr), failedAttempts()); |
|
print(QByteArray("Fails."), Http::CONTENT_TYPE_TXT); |
|
} |
|
} |
|
|
|
void WebApplication::action_public_logout() |
|
{ |
|
CHECK_URI(0); |
|
sessionEnd(); |
|
} |
|
|
|
void WebApplication::action_public_theme() |
|
{ |
|
if (args_.size() != 1) { |
|
status(404, "Not Found"); |
|
return; |
|
} |
|
|
|
QString url = IconProvider::instance()->getIconPath(args_.front()); |
|
qDebug() << Q_FUNC_INFO << "There icon:" << url; |
|
|
|
printFile(url); |
|
header(Http::HEADER_CACHE_CONTROL, MAX_AGE_MONTH); |
|
} |
|
|
|
void WebApplication::action_public_images() |
|
{ |
|
const QString path = ":/icons/" + args_.join("/"); |
|
printFile(path); |
|
header(Http::HEADER_CACHE_CONTROL, MAX_AGE_MONTH); |
|
} |
|
|
|
// GET params: |
|
// - filter (string): all, downloading, seeding, completed, paused, resumed, active, inactive |
|
// - category (string): torrent category for filtering by it (empty string means "uncategorized"; no "category" param presented means "any category") |
|
// - sort (string): name of column for sorting by its value |
|
// - reverse (bool): enable reverse sorting |
|
// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited) |
|
// - offset (int): set offset (if less than 0 - offset from end) |
|
void WebApplication::action_query_torrents() |
|
{ |
|
CHECK_URI(0); |
|
const QStringMap& gets = request().gets; |
|
|
|
print(btjson::getTorrents( |
|
gets["filter"], gets["category"], gets["sort"], gets["reverse"] == "true", |
|
gets["limit"].toInt(), gets["offset"].toInt() |
|
), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_query_preferences() |
|
{ |
|
CHECK_URI(0); |
|
print(prefjson::getPreferences(), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_query_transferInfo() |
|
{ |
|
CHECK_URI(0); |
|
print(btjson::getTransferInfo(), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_query_propertiesGeneral() |
|
{ |
|
CHECK_URI(1); |
|
print(btjson::getPropertiesForTorrent(args_.front()), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_query_propertiesTrackers() |
|
{ |
|
CHECK_URI(1); |
|
print(btjson::getTrackersForTorrent(args_.front()), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_query_propertiesWebSeeds() |
|
{ |
|
CHECK_URI(1); |
|
print(btjson::getWebSeedsForTorrent(args_.front()), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_query_propertiesFiles() |
|
{ |
|
CHECK_URI(1); |
|
print(btjson::getFilesForTorrent(args_.front()), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
// GET params: |
|
// - normal (bool): include normal messages (default true) |
|
// - info (bool): include info messages (default true) |
|
// - warning (bool): include warning messages (default true) |
|
// - critical (bool): include critical messages (default true) |
|
// - last_known_id (int): exclude messages with id <= 'last_known_id' (default -1) |
|
void WebApplication::action_query_getLog() |
|
{ |
|
CHECK_URI(0); |
|
bool normal = request().gets["normal"] != "false"; |
|
bool info = request().gets["info"] != "false"; |
|
bool warning = request().gets["warning"] != "false"; |
|
bool critical = request().gets["critical"] != "false"; |
|
int lastKnownId; |
|
bool ok; |
|
|
|
lastKnownId = request().gets["last_known_id"].toInt(&ok); |
|
if (!ok) |
|
lastKnownId = -1; |
|
|
|
print(btjson::getLog(normal, info, warning, critical, lastKnownId), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
// GET params: |
|
// - last_known_id (int): exclude messages with id <= 'last_known_id' (default -1) |
|
void WebApplication::action_query_getPeerLog() |
|
{ |
|
CHECK_URI(0); |
|
int lastKnownId; |
|
bool ok; |
|
|
|
lastKnownId = request().gets["last_known_id"].toInt(&ok); |
|
if (!ok) |
|
lastKnownId = -1; |
|
|
|
print(btjson::getPeerLog(lastKnownId), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_query_getPieceHashes() |
|
{ |
|
CHECK_URI(1); |
|
print(btjson::getPieceHashesForTorrent(args_.front()), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_query_getPieceStates() |
|
{ |
|
CHECK_URI(1); |
|
print(btjson::getPieceStatesForTorrent(args_.front()), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
// GET param: |
|
// - rid (int): last response id |
|
void WebApplication::action_sync_maindata() |
|
{ |
|
CHECK_URI(0); |
|
print(btjson::getSyncMainData(request().gets["rid"].toInt(), |
|
session()->syncMainDataLastResponse, |
|
session()->syncMainDataLastAcceptedResponse), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
// GET param: |
|
// - hash (string): torrent hash |
|
// - rid (int): last response id |
|
void WebApplication::action_sync_torrent_peers() |
|
{ |
|
CHECK_URI(0); |
|
print(btjson::getSyncTorrentPeersData(request().gets["rid"].toInt(), |
|
request().gets["hash"], |
|
session()->syncTorrentPeersLastResponse, |
|
session()->syncTorrentPeersLastAcceptedResponse), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
|
|
void WebApplication::action_version_api() |
|
{ |
|
CHECK_URI(0); |
|
print(QString::number(API_VERSION), Http::CONTENT_TYPE_TXT); |
|
} |
|
|
|
void WebApplication::action_version_api_min() |
|
{ |
|
CHECK_URI(0); |
|
print(QString::number(API_VERSION_MIN), Http::CONTENT_TYPE_TXT); |
|
} |
|
|
|
void WebApplication::action_version_qbittorrent() |
|
{ |
|
CHECK_URI(0); |
|
print(QString(QBT_VERSION), Http::CONTENT_TYPE_TXT); |
|
} |
|
|
|
void WebApplication::action_command_shutdown() |
|
{ |
|
qDebug() << "Shutdown request from Web UI"; |
|
CHECK_URI(0); |
|
|
|
// Special case handling for shutdown, we |
|
// need to reply to the Web UI before |
|
// actually shutting down. |
|
QTimer::singleShot(100, qApp, SLOT(quit())); |
|
} |
|
|
|
void WebApplication::action_command_download() |
|
{ |
|
CHECK_URI(0); |
|
|
|
const QString urls = request().posts.value("urls"); |
|
const bool skipChecking = parseBool(request().posts.value("skip_checking"), false); |
|
const bool seqDownload = parseBool(request().posts.value("sequentialDownload"), false); |
|
const bool firstLastPiece = parseBool(request().posts.value("firstLastPiecePrio"), false); |
|
const TriStateBool addPaused = parseTristatebool(request().posts.value("paused")); |
|
const TriStateBool rootFolder = parseTristatebool(request().posts.value("root_folder")); |
|
const QString savepath = request().posts.value("savepath").trimmed(); |
|
const QString category = request().posts.value("category").trimmed(); |
|
const QString cookie = request().posts.value("cookie"); |
|
const QString torrentName = request().posts.value("rename").trimmed(); |
|
const int upLimit = request().posts.value("upLimit").toInt(); |
|
const int dlLimit = request().posts.value("dlLimit").toInt(); |
|
|
|
QList<QNetworkCookie> cookies; |
|
if (!cookie.isEmpty()) { |
|
const QStringList cookiesStr = cookie.split("; "); |
|
for (QString cookieStr : cookiesStr) { |
|
cookieStr = cookieStr.trimmed(); |
|
int index = cookieStr.indexOf('='); |
|
if (index > 1) { |
|
QByteArray name = cookieStr.left(index).toLatin1(); |
|
QByteArray value = cookieStr.right(cookieStr.length() - index - 1).toLatin1(); |
|
cookies += QNetworkCookie(name, value); |
|
} |
|
} |
|
} |
|
|
|
BitTorrent::AddTorrentParams params; |
|
// TODO: Check if destination actually exists |
|
params.skipChecking = skipChecking; |
|
params.sequential = seqDownload; |
|
params.firstLastPiecePriority = firstLastPiece; |
|
params.addPaused = addPaused; |
|
params.createSubfolder = rootFolder; |
|
params.savePath = savepath; |
|
params.category = category; |
|
params.name = torrentName; |
|
params.uploadLimit = (upLimit > 0) ? upLimit : -1; |
|
params.downloadLimit = (dlLimit > 0) ? dlLimit : -1; |
|
|
|
bool partialSuccess = false; |
|
for (QString url : urls.split('\n')) { |
|
url = url.trimmed(); |
|
if (!url.isEmpty()) { |
|
Net::DownloadManager::instance()->setCookiesFromUrl(cookies, QUrl::fromEncoded(url.toUtf8())); |
|
partialSuccess |= BitTorrent::Session::instance()->addTorrent(url, params); |
|
} |
|
} |
|
|
|
if (partialSuccess) |
|
print(QByteArray("Ok."), Http::CONTENT_TYPE_TXT); |
|
else |
|
print(QByteArray("Fails."), Http::CONTENT_TYPE_TXT); |
|
} |
|
|
|
void WebApplication::action_command_upload() |
|
{ |
|
CHECK_URI(0); |
|
|
|
const bool skipChecking = parseBool(request().posts.value("skip_checking"), false); |
|
const bool seqDownload = parseBool(request().posts.value("sequentialDownload"), false); |
|
const bool firstLastPiece = parseBool(request().posts.value("firstLastPiecePrio"), false); |
|
const TriStateBool addPaused = parseTristatebool(request().posts.value("paused")); |
|
const TriStateBool rootFolder = parseTristatebool(request().posts.value("root_folder")); |
|
const QString savepath = request().posts.value("savepath").trimmed(); |
|
const QString category = request().posts.value("category").trimmed(); |
|
const QString torrentName = request().posts.value("rename").trimmed(); |
|
const int upLimit = request().posts.value("upLimit").toInt(); |
|
const int dlLimit = request().posts.value("dlLimit").toInt(); |
|
|
|
for (const Http::UploadedFile &torrent : request().files) { |
|
const QString filePath = saveTmpFile(torrent.data); |
|
if (filePath.isEmpty()) { |
|
qWarning() << "I/O Error: Could not create temporary file"; |
|
status(500, "Internal Server Error"); |
|
print(QObject::tr("I/O Error: Could not create temporary file."), Http::CONTENT_TYPE_TXT); |
|
continue; |
|
} |
|
|
|
const BitTorrent::TorrentInfo torrentInfo = BitTorrent::TorrentInfo::loadFromFile(filePath); |
|
if (!torrentInfo.isValid()) { |
|
status(415, "Unsupported Media Type"); |
|
print(QObject::tr("Error: '%1' is not a valid torrent file.\n").arg(torrent.filename), Http::CONTENT_TYPE_TXT); |
|
} |
|
else { |
|
BitTorrent::AddTorrentParams params; |
|
// TODO: Check if destination actually exists |
|
params.skipChecking = skipChecking; |
|
params.sequential = seqDownload; |
|
params.firstLastPiecePriority = firstLastPiece; |
|
params.addPaused = addPaused; |
|
params.createSubfolder = rootFolder; |
|
params.savePath = savepath; |
|
params.category = category; |
|
params.name = torrentName; |
|
params.uploadLimit = (upLimit > 0) ? upLimit : -1; |
|
params.downloadLimit = (dlLimit > 0) ? dlLimit : -1; |
|
|
|
if (!BitTorrent::Session::instance()->addTorrent(torrentInfo, params)) { |
|
status(500, "Internal Server Error"); |
|
print(QObject::tr("Error: Could not add torrent to session."), Http::CONTENT_TYPE_TXT); |
|
} |
|
} |
|
// Clean up |
|
Utils::Fs::forceRemove(filePath); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_addTrackers() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hash" << "urls"); |
|
QString hash = request().posts["hash"]; |
|
|
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) { |
|
QList<BitTorrent::TrackerEntry> trackers; |
|
foreach (QString url, request().posts["urls"].split('\n')) { |
|
url = url.trimmed(); |
|
if (!url.isEmpty()) |
|
trackers << url; |
|
} |
|
torrent->addTrackers(trackers); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_resumeAll() |
|
{ |
|
CHECK_URI(0); |
|
|
|
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents()) |
|
torrent->resume(); |
|
} |
|
|
|
void WebApplication::action_command_pauseAll() |
|
{ |
|
CHECK_URI(0); |
|
|
|
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents()) |
|
torrent->pause(); |
|
} |
|
|
|
void WebApplication::action_command_resume() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hash"); |
|
|
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(request().posts["hash"]); |
|
if (torrent) |
|
torrent->resume(); |
|
} |
|
|
|
void WebApplication::action_command_pause() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hash"); |
|
|
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(request().posts["hash"]); |
|
if (torrent) |
|
torrent->pause(); |
|
} |
|
|
|
void WebApplication::action_command_setPreferences() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("json"); |
|
prefjson::setPreferences(request().posts["json"]); |
|
} |
|
|
|
void WebApplication::action_command_setFilePrio() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hash" << "id" << "priority"); |
|
QString hash = request().posts["hash"]; |
|
int fileID = request().posts["id"].toInt(); |
|
int priority = request().posts["priority"].toInt(); |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
|
|
if (torrent && torrent->hasMetadata()) |
|
torrent->setFilePriority(fileID, priority); |
|
} |
|
|
|
void WebApplication::action_command_getGlobalUpLimit() |
|
{ |
|
CHECK_URI(0); |
|
print(QByteArray::number(BitTorrent::Session::instance()->uploadSpeedLimit()), Http::CONTENT_TYPE_TXT); |
|
} |
|
|
|
void WebApplication::action_command_getGlobalDlLimit() |
|
{ |
|
CHECK_URI(0); |
|
print(QByteArray::number(BitTorrent::Session::instance()->downloadSpeedLimit()), Http::CONTENT_TYPE_TXT); |
|
} |
|
|
|
void WebApplication::action_command_setGlobalUpLimit() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("limit"); |
|
qlonglong limit = request().posts["limit"].toLongLong(); |
|
if (limit == 0) limit = -1; |
|
|
|
BitTorrent::Session::instance()->setUploadSpeedLimit(limit); |
|
} |
|
|
|
void WebApplication::action_command_setGlobalDlLimit() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("limit"); |
|
qlonglong limit = request().posts["limit"].toLongLong(); |
|
if (limit == 0) limit = -1; |
|
|
|
BitTorrent::Session::instance()->setDownloadSpeedLimit(limit); |
|
} |
|
|
|
void WebApplication::action_command_getTorrentsUpLimit() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
print(btjson::getTorrentsRatesLimits(hashes, false), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_command_getTorrentsDlLimit() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
print(btjson::getTorrentsRatesLimits(hashes, true), Http::CONTENT_TYPE_JSON); |
|
} |
|
|
|
void WebApplication::action_command_setTorrentsUpLimit() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes" << "limit"); |
|
|
|
qlonglong limit = request().posts["limit"].toLongLong(); |
|
if (limit == 0) |
|
limit = -1; |
|
|
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) |
|
torrent->setUploadLimit(limit); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_setTorrentsDlLimit() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes" << "limit"); |
|
|
|
qlonglong limit = request().posts["limit"].toLongLong(); |
|
if (limit == 0) |
|
limit = -1; |
|
|
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) |
|
torrent->setDownloadLimit(limit); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_toggleAlternativeSpeedLimits() |
|
{ |
|
CHECK_URI(0); |
|
BitTorrent::Session *const session = BitTorrent::Session::instance(); |
|
session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled()); |
|
} |
|
|
|
void WebApplication::action_command_alternativeSpeedLimitsEnabled() |
|
{ |
|
CHECK_URI(0); |
|
print(QByteArray::number(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled()) |
|
, Http::CONTENT_TYPE_TXT); |
|
} |
|
|
|
void WebApplication::action_command_toggleSequentialDownload() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) |
|
torrent->toggleSequentialDownload(); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_toggleFirstLastPiecePrio() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) |
|
torrent->toggleFirstLastPiecePriority(); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_setSuperSeeding() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes" << "value"); |
|
bool value = request().posts["value"] == "true"; |
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) |
|
torrent->setSuperSeeding(value); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_setForceStart() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes" << "value"); |
|
bool value = request().posts["value"] == "true"; |
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) |
|
torrent->resume(value); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_delete() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
foreach (const QString &hash, hashes) |
|
BitTorrent::Session::instance()->deleteTorrent(hash, false); |
|
} |
|
|
|
void WebApplication::action_command_deletePerm() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
foreach (const QString &hash, hashes) |
|
BitTorrent::Session::instance()->deleteTorrent(hash, true); |
|
} |
|
|
|
void WebApplication::action_command_increasePrio() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
|
|
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled()) { |
|
status(403, "Torrent queueing must be enabled"); |
|
return; |
|
} |
|
|
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
BitTorrent::Session::instance()->increaseTorrentsPriority(hashes); |
|
} |
|
|
|
void WebApplication::action_command_decreasePrio() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
|
|
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled()) { |
|
status(403, "Torrent queueing must be enabled"); |
|
return; |
|
} |
|
|
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
BitTorrent::Session::instance()->decreaseTorrentsPriority(hashes); |
|
} |
|
|
|
void WebApplication::action_command_topPrio() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
|
|
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled()) { |
|
status(403, "Torrent queueing must be enabled"); |
|
return; |
|
} |
|
|
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
BitTorrent::Session::instance()->topTorrentsPriority(hashes); |
|
} |
|
|
|
void WebApplication::action_command_bottomPrio() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes"); |
|
|
|
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled()) { |
|
status(403, "Torrent queueing must be enabled"); |
|
return; |
|
} |
|
|
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
BitTorrent::Session::instance()->bottomTorrentsPriority(hashes); |
|
} |
|
|
|
void WebApplication::action_command_setLocation() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes" << "location"); |
|
|
|
QStringList hashes = request().posts["hashes"].split("|"); |
|
QString newLocation = request().posts["location"].trimmed(); |
|
|
|
// check if the location exists |
|
if (newLocation.isEmpty() || !QDir(newLocation).exists()) |
|
return; |
|
|
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) { |
|
Logger::instance()->addMessage(tr("WebUI Set location: moving \"%1\", from \"%2\" to \"%3\"").arg(torrent->name()).arg(torrent->savePath()).arg(newLocation)); |
|
|
|
torrent->move(Utils::Fs::expandPathAbs(newLocation)); |
|
} |
|
} |
|
} |
|
|
|
void WebApplication::action_command_rename() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hash" << "name"); |
|
|
|
QString hash = request().posts["hash"]; |
|
QString name = request().posts["name"].trimmed(); |
|
|
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent && !name.isEmpty()) { |
|
name.replace(QRegularExpression("\r?\n|\r"), " "); |
|
qDebug() << "Renaming" << torrent->name() << "to" << name; |
|
torrent->setName(name); |
|
} |
|
else { |
|
status(400, "Incorrect torrent hash or name"); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_setAutoTMM() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes" << "enable"); |
|
|
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
QString enableStr = request().posts["enable"]; |
|
|
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) |
|
torrent->setAutoTMMEnabled(QString::compare(enableStr, "true", Qt::CaseInsensitive) == 0); |
|
} |
|
} |
|
|
|
void WebApplication::action_command_recheck() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hash"); |
|
|
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(request().posts["hash"]); |
|
if (torrent) |
|
torrent->forceRecheck(); |
|
} |
|
|
|
void WebApplication::action_command_setCategory() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("hashes" << "category"); |
|
|
|
QStringList hashes = request().posts["hashes"].split('|'); |
|
QString category = request().posts["category"].trimmed(); |
|
|
|
foreach (const QString &hash, hashes) { |
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); |
|
if (torrent) { |
|
if (!torrent->setCategory(category)) { |
|
status(400, "Incorrect category name"); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void WebApplication::action_command_addCategory() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("category"); |
|
|
|
QString category = request().posts["category"].trimmed(); |
|
|
|
if (!BitTorrent::Session::isValidCategoryName(category) && !category.isEmpty()) { |
|
status(400, tr("Incorrect category name")); |
|
return; |
|
} |
|
|
|
BitTorrent::Session::instance()->addCategory(category); |
|
} |
|
|
|
void WebApplication::action_command_removeCategories() |
|
{ |
|
CHECK_URI(0); |
|
CHECK_PARAMETERS("categories"); |
|
|
|
QStringList categories = request().posts["categories"].split('\n'); |
|
foreach (const QString &category, categories) |
|
BitTorrent::Session::instance()->removeCategory(category); |
|
} |
|
|
|
void WebApplication::action_command_getSavePath() |
|
{ |
|
CHECK_URI(0); |
|
print(BitTorrent::Session::instance()->defaultSavePath()); |
|
} |
|
|
|
bool WebApplication::isPublicScope() |
|
{ |
|
return (scope_ == DEFAULT_SCOPE || scope_ == VERSION_INFO); |
|
} |
|
|
|
void WebApplication::doProcessRequest() |
|
{ |
|
scope_ = DEFAULT_SCOPE; |
|
action_ = DEFAULT_ACTION; |
|
|
|
parsePath(); |
|
|
|
if (args_.contains(".") || args_.contains("..")) { |
|
qDebug() << Q_FUNC_INFO << "Invalid path:" << request().path; |
|
status(404, "Not Found"); |
|
return; |
|
} |
|
|
|
if (!isPublicScope() && !sessionActive()) { |
|
status(403, "Forbidden"); |
|
return; |
|
} |
|
|
|
if (actions_.value(scope_).value(action_) != 0) { |
|
(this->*(actions_[scope_][action_]))(); |
|
} |
|
else { |
|
status(404, "Not Found"); |
|
qDebug() << Q_FUNC_INFO << "Resource not found:" << request().path; |
|
} |
|
} |
|
|
|
void WebApplication::parsePath() |
|
{ |
|
if (request().path == "/") action_ = WEBUI_ACTION; |
|
|
|
// check action for requested path |
|
QStringList pathItems = request().path.split('/', QString::SkipEmptyParts); |
|
if (!pathItems.empty() && actions_.contains(pathItems.front())) { |
|
scope_ = pathItems.front(); |
|
pathItems.pop_front(); |
|
} |
|
|
|
if (!pathItems.empty() && actions_[scope_].contains(pathItems.front())) { |
|
action_ = pathItems.front(); |
|
pathItems.pop_front(); |
|
} |
|
|
|
args_ = pathItems; |
|
} |
|
|
|
WebApplication::WebApplication(QObject *parent) |
|
: AbstractWebApplication(parent) |
|
{ |
|
} |
|
|
|
QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::actions_ = WebApplication::initializeActions();
|
|
|