Redesign Web API

Normalize Web API method names.
Allow to use alternative Web UI.
Switch Web API version to standard form (i.e. "2.0").
Improve Web UI translation code.
Retranslate changed files.
Add Web API for RSS subsystem.
This commit is contained in:
Vladimir Golovnev (Glassez)
2017-10-14 16:27:21 +03:00
parent 0fc1ad664f
commit 27d8dbf13b
90 changed files with 4817 additions and 3038 deletions

View File

@@ -0,0 +1,91 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "apicontroller.h"
#include <QJsonDocument>
#include <QMetaObject>
#include "apierror.h"
APIController::APIController(ISessionManager *sessionManager, QObject *parent)
: QObject {parent}
, m_sessionManager {sessionManager}
{
}
QVariant APIController::run(const QString &action, const StringMap &params, const DataMap &data)
{
m_result.clear(); // clear result
m_params = params;
m_data = data;
const QString methodName {action + QLatin1String("Action")};
if (!QMetaObject::invokeMethod(this, methodName.toLatin1().constData()))
throw APIError(APIErrorType::NotFound);
return m_result;
}
ISessionManager *APIController::sessionManager() const
{
return m_sessionManager;
}
const StringMap &APIController::params() const
{
return m_params;
}
const DataMap &APIController::data() const
{
return m_data;
}
void APIController::checkParams(const QSet<QString> &requiredParams) const
{
const QSet<QString> params {this->params().keys().toSet()};
if (!params.contains(requiredParams))
throw APIError(APIErrorType::BadParams);
}
void APIController::setResult(const QString &result)
{
m_result = result;
}
void APIController::setResult(const QJsonArray &result)
{
m_result = QJsonDocument(result);
}
void APIController::setResult(const QJsonObject &result)
{
m_result = QJsonDocument(result);
}

View File

@@ -0,0 +1,72 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include <QMap>
#include <QObject>
#include <QSet>
#include <QString>
#include <QVariant>
struct ISessionManager;
using StringMap = QMap<QString, QString>;
using DataMap = QMap<QString, QByteArray>;
class APIController : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(APIController)
#ifndef Q_MOC_RUN
#define WEBAPI_PUBLIC
#define WEBAPI_PRIVATE
#endif
public:
explicit APIController(ISessionManager *sessionManager, QObject *parent = nullptr);
QVariant run(const QString &action, const StringMap &params, const DataMap &data = {});
ISessionManager *sessionManager() const;
protected:
const StringMap &params() const;
const DataMap &data() const;
void checkParams(const QSet<QString> &requiredParams) const;
void setResult(const QString &result);
void setResult(const QJsonArray &result);
void setResult(const QJsonObject &result);
private:
ISessionManager *m_sessionManager;
StringMap m_params;
DataMap m_data;
QVariant m_result;
};

View File

@@ -0,0 +1,40 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "apierror.h"
APIError::APIError(APIErrorType type, const QString &message)
: RuntimeError {message}
, m_type {type}
{
}
APIErrorType APIError::type() const
{
return m_type;
}

51
src/webui/api/apierror.h Normal file
View File

@@ -0,0 +1,51 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include "base/exceptions.h"
enum class APIErrorType
{
BadParams,
BadData,
NotFound,
AccessDenied,
Conflict
};
class APIError : public RuntimeError
{
public:
explicit APIError(APIErrorType type, const QString &message = "");
APIErrorType type() const;
private:
const APIErrorType m_type;
};

View File

@@ -0,0 +1,498 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2006-2012 Ishan Arora <ishan@qbittorrent.org>
*
* 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 "appcontroller.h"
#include <QCoreApplication>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QStringList>
#include <QTimer>
#include <QTranslator>
#ifndef QT_NO_OPENSSL
#include <QSslCertificate>
#include <QSslKey>
#endif
#include "base/bittorrent/session.h"
#include "base/net/portforwarder.h"
#include "base/net/proxyconfigurationmanager.h"
#include "base/preferences.h"
#include "base/rss/rss_autodownloader.h"
#include "base/rss/rss_session.h"
#include "base/scanfoldersmodel.h"
#include "base/utils/fs.h"
#include "base/utils/net.h"
#include "../webapplication.h"
void AppController::webapiVersionAction()
{
setResult(static_cast<QString>(API_VERSION));
}
void AppController::versionAction()
{
setResult(QBT_VERSION);
}
void AppController::shutdownAction()
{
qDebug() << "Shutdown request from Web UI";
// Special case handling for shutdown, we
// need to reply to the Web UI before
// actually shutting down.
QTimer::singleShot(100, qApp, &QCoreApplication::quit);
}
void AppController::preferencesAction()
{
const Preferences *const pref = Preferences::instance();
auto session = BitTorrent::Session::instance();
QVariantMap data;
// Downloads
// Hard Disk
data["save_path"] = Utils::Fs::toNativePath(session->defaultSavePath());
data["temp_path_enabled"] = session->isTempPathEnabled();
data["temp_path"] = Utils::Fs::toNativePath(session->tempPath());
data["preallocate_all"] = session->isPreallocationEnabled();
data["incomplete_files_ext"] = session->isAppendExtensionEnabled();
QVariantHash dirs = pref->getScanDirs();
QVariantMap nativeDirs;
for (QVariantHash::const_iterator i = dirs.begin(), e = dirs.end(); i != e; ++i) {
if (i.value().type() == QVariant::Int)
nativeDirs.insert(Utils::Fs::toNativePath(i.key()), i.value().toInt());
else
nativeDirs.insert(Utils::Fs::toNativePath(i.key()), Utils::Fs::toNativePath(i.value().toString()));
}
data["scan_dirs"] = nativeDirs;
data["export_dir"] = Utils::Fs::toNativePath(session->torrentExportDirectory());
data["export_dir_fin"] = Utils::Fs::toNativePath(session->finishedTorrentExportDirectory());
// Email notification upon download completion
data["mail_notification_enabled"] = pref->isMailNotificationEnabled();
data["mail_notification_email"] = pref->getMailNotificationEmail();
data["mail_notification_smtp"] = pref->getMailNotificationSMTP();
data["mail_notification_ssl_enabled"] = pref->getMailNotificationSMTPSSL();
data["mail_notification_auth_enabled"] = pref->getMailNotificationSMTPAuth();
data["mail_notification_username"] = pref->getMailNotificationSMTPUsername();
data["mail_notification_password"] = pref->getMailNotificationSMTPPassword();
// Run an external program on torrent completion
data["autorun_enabled"] = pref->isAutoRunEnabled();
data["autorun_program"] = Utils::Fs::toNativePath(pref->getAutoRunProgram());
// Connection
// Listening Port
data["listen_port"] = session->port();
data["upnp"] = Net::PortForwarder::instance()->isEnabled();
data["random_port"] = session->useRandomPort();
// Connections Limits
data["max_connec"] = session->maxConnections();
data["max_connec_per_torrent"] = session->maxConnectionsPerTorrent();
data["max_uploads"] = session->maxUploads();
data["max_uploads_per_torrent"] = session->maxUploadsPerTorrent();
// Proxy Server
auto proxyManager = Net::ProxyConfigurationManager::instance();
Net::ProxyConfiguration proxyConf = proxyManager->proxyConfiguration();
data["proxy_type"] = static_cast<int>(proxyConf.type);
data["proxy_ip"] = proxyConf.ip;
data["proxy_port"] = proxyConf.port;
data["proxy_auth_enabled"] = proxyManager->isAuthenticationRequired(); // deprecated
data["proxy_username"] = proxyConf.username;
data["proxy_password"] = proxyConf.password;
data["proxy_peer_connections"] = session->isProxyPeerConnectionsEnabled();
data["force_proxy"] = session->isForceProxyEnabled();
// IP Filtering
data["ip_filter_enabled"] = session->isIPFilteringEnabled();
data["ip_filter_path"] = Utils::Fs::toNativePath(session->IPFilterFile());
data["ip_filter_trackers"] = session->isTrackerFilteringEnabled();
data["banned_IPs"] = session->bannedIPs().join("\n");
// Speed
// Global Rate Limits
data["dl_limit"] = session->globalDownloadSpeedLimit();
data["up_limit"] = session->globalUploadSpeedLimit();
data["bittorrent_protocol"] = static_cast<int>(session->btProtocol());
data["limit_utp_rate"] = session->isUTPRateLimited();
data["limit_tcp_overhead"] = session->includeOverheadInLimits();
data["alt_dl_limit"] = session->altGlobalDownloadSpeedLimit();
data["alt_up_limit"] = session->altGlobalUploadSpeedLimit();
// Scheduling
data["scheduler_enabled"] = session->isBandwidthSchedulerEnabled();
const QTime start_time = pref->getSchedulerStartTime();
data["schedule_from_hour"] = start_time.hour();
data["schedule_from_min"] = start_time.minute();
const QTime end_time = pref->getSchedulerEndTime();
data["schedule_to_hour"] = end_time.hour();
data["schedule_to_min"] = end_time.minute();
data["scheduler_days"] = pref->getSchedulerDays();
// Bittorrent
// Privacy
data["dht"] = session->isDHTEnabled();
data["pex"] = session->isPeXEnabled();
data["lsd"] = session->isLSDEnabled();
data["encryption"] = session->encryption();
data["anonymous_mode"] = session->isAnonymousModeEnabled();
// Torrent Queueing
data["queueing_enabled"] = session->isQueueingSystemEnabled();
data["max_active_downloads"] = session->maxActiveDownloads();
data["max_active_torrents"] = session->maxActiveTorrents();
data["max_active_uploads"] = session->maxActiveUploads();
data["dont_count_slow_torrents"] = session->ignoreSlowTorrentsForQueueing();
// Share Ratio Limiting
data["max_ratio_enabled"] = (session->globalMaxRatio() >= 0.);
data["max_ratio"] = session->globalMaxRatio();
data["max_seeding_time_enabled"] = (session->globalMaxSeedingMinutes() >= 0.);
data["max_seeding_time"] = session->globalMaxSeedingMinutes();
data["max_ratio_act"] = session->maxRatioAction();
// Add trackers
data["add_trackers_enabled"] = session->isAddTrackersEnabled();
data["add_trackers"] = session->additionalTrackers();
// Web UI
// Language
data["locale"] = pref->getLocale();
// HTTP Server
data["web_ui_domain_list"] = pref->getServerDomains();
data["web_ui_address"] = pref->getWebUiAddress();
data["web_ui_port"] = pref->getWebUiPort();
data["web_ui_upnp"] = pref->useUPnPForWebUIPort();
data["use_https"] = pref->isWebUiHttpsEnabled();
data["ssl_key"] = QString::fromLatin1(pref->getWebUiHttpsKey());
data["ssl_cert"] = QString::fromLatin1(pref->getWebUiHttpsCertificate());
// Authentication
data["web_ui_username"] = pref->getWebUiUsername();
data["web_ui_password"] = pref->getWebUiPassword();
data["bypass_local_auth"] = !pref->isWebUiLocalAuthEnabled();
data["bypass_auth_subnet_whitelist_enabled"] = pref->isWebUiAuthSubnetWhitelistEnabled();
QStringList authSubnetWhitelistStringList;
for (const Utils::Net::Subnet &subnet : pref->getWebUiAuthSubnetWhitelist())
authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet);
data["bypass_auth_subnet_whitelist"] = authSubnetWhitelistStringList.join("\n");
// Update my dynamic domain name
data["dyndns_enabled"] = pref->isDynDNSEnabled();
data["dyndns_service"] = pref->getDynDNSService();
data["dyndns_username"] = pref->getDynDNSUsername();
data["dyndns_password"] = pref->getDynDNSPassword();
data["dyndns_domain"] = pref->getDynDomainName();
// RSS settings
data["RSSRefreshInterval"] = RSS::Session::instance()->refreshInterval();
data["RSSMaxArticlesPerFeed"] = RSS::Session::instance()->maxArticlesPerFeed();
data["RSSProcessingEnabled"] = RSS::Session::instance()->isProcessingEnabled();
data["RSSAutoDownloadingEnabled"] = RSS::AutoDownloader::instance()->isProcessingEnabled();
setResult(QJsonObject::fromVariantMap(data));
}
void AppController::setPreferencesAction()
{
checkParams({"json"});
Preferences *const pref = Preferences::instance();
auto session = BitTorrent::Session::instance();
const QVariantMap m = QJsonDocument::fromJson(params()["json"].toUtf8()).toVariant().toMap();
// Downloads
// Hard Disk
if (m.contains("save_path"))
session->setDefaultSavePath(m["save_path"].toString());
if (m.contains("temp_path_enabled"))
session->setTempPathEnabled(m["temp_path_enabled"].toBool());
if (m.contains("temp_path"))
session->setTempPath(m["temp_path"].toString());
if (m.contains("preallocate_all"))
session->setPreallocationEnabled(m["preallocate_all"].toBool());
if (m.contains("incomplete_files_ext"))
session->setAppendExtensionEnabled(m["incomplete_files_ext"].toBool());
if (m.contains("scan_dirs")) {
QVariantMap nativeDirs = m["scan_dirs"].toMap();
QVariantHash oldScanDirs = pref->getScanDirs();
QVariantHash scanDirs;
ScanFoldersModel *model = ScanFoldersModel::instance();
for (QVariantMap::const_iterator i = nativeDirs.begin(), e = nativeDirs.end(); i != e; ++i) {
QString folder = Utils::Fs::fromNativePath(i.key());
int downloadType;
QString downloadPath;
ScanFoldersModel::PathStatus ec;
if (i.value().type() == QVariant::String) {
downloadType = ScanFoldersModel::CUSTOM_LOCATION;
downloadPath = Utils::Fs::fromNativePath(i.value().toString());
}
else {
downloadType = i.value().toInt();
downloadPath = (downloadType == ScanFoldersModel::DEFAULT_LOCATION) ? "Default folder" : "Watch folder";
}
if (!oldScanDirs.contains(folder))
ec = model->addPath(folder, static_cast<ScanFoldersModel::PathType>(downloadType), downloadPath);
else
ec = model->updatePath(folder, static_cast<ScanFoldersModel::PathType>(downloadType), downloadPath);
if (ec == ScanFoldersModel::Ok) {
scanDirs.insert(folder, (downloadType == ScanFoldersModel::CUSTOM_LOCATION) ? QVariant(downloadPath) : QVariant(downloadType));
qDebug("New watched folder: %s to %s", qUtf8Printable(folder), qUtf8Printable(downloadPath));
}
else {
qDebug("Watched folder %s failed with error %d", qUtf8Printable(folder), ec);
}
}
// Update deleted folders
foreach (QVariant folderVariant, oldScanDirs.keys()) {
QString folder = folderVariant.toString();
if (!scanDirs.contains(folder)) {
model->removePath(folder);
qDebug("Removed watched folder %s", qUtf8Printable(folder));
}
}
pref->setScanDirs(scanDirs);
}
if (m.contains("export_dir"))
session->setTorrentExportDirectory(m["export_dir"].toString());
if (m.contains("export_dir_fin"))
session->setFinishedTorrentExportDirectory(m["export_dir_fin"].toString());
// Email notification upon download completion
if (m.contains("mail_notification_enabled"))
pref->setMailNotificationEnabled(m["mail_notification_enabled"].toBool());
if (m.contains("mail_notification_email"))
pref->setMailNotificationEmail(m["mail_notification_email"].toString());
if (m.contains("mail_notification_smtp"))
pref->setMailNotificationSMTP(m["mail_notification_smtp"].toString());
if (m.contains("mail_notification_ssl_enabled"))
pref->setMailNotificationSMTPSSL(m["mail_notification_ssl_enabled"].toBool());
if (m.contains("mail_notification_auth_enabled"))
pref->setMailNotificationSMTPAuth(m["mail_notification_auth_enabled"].toBool());
if (m.contains("mail_notification_username"))
pref->setMailNotificationSMTPUsername(m["mail_notification_username"].toString());
if (m.contains("mail_notification_password"))
pref->setMailNotificationSMTPPassword(m["mail_notification_password"].toString());
// Run an external program on torrent completion
if (m.contains("autorun_enabled"))
pref->setAutoRunEnabled(m["autorun_enabled"].toBool());
if (m.contains("autorun_program"))
pref->setAutoRunProgram(m["autorun_program"].toString());
// Connection
// Listening Port
if (m.contains("listen_port"))
session->setPort(m["listen_port"].toInt());
if (m.contains("upnp"))
Net::PortForwarder::instance()->setEnabled(m["upnp"].toBool());
if (m.contains("random_port"))
session->setUseRandomPort(m["random_port"].toBool());
// Connections Limits
if (m.contains("max_connec"))
session->setMaxConnections(m["max_connec"].toInt());
if (m.contains("max_connec_per_torrent"))
session->setMaxConnectionsPerTorrent(m["max_connec_per_torrent"].toInt());
if (m.contains("max_uploads"))
session->setMaxUploads(m["max_uploads"].toInt());
if (m.contains("max_uploads_per_torrent"))
session->setMaxUploadsPerTorrent(m["max_uploads_per_torrent"].toInt());
// Proxy Server
auto proxyManager = Net::ProxyConfigurationManager::instance();
Net::ProxyConfiguration proxyConf = proxyManager->proxyConfiguration();
if (m.contains("proxy_type"))
proxyConf.type = static_cast<Net::ProxyType>(m["proxy_type"].toInt());
if (m.contains("proxy_ip"))
proxyConf.ip = m["proxy_ip"].toString();
if (m.contains("proxy_port"))
proxyConf.port = m["proxy_port"].toUInt();
if (m.contains("proxy_username"))
proxyConf.username = m["proxy_username"].toString();
if (m.contains("proxy_password"))
proxyConf.password = m["proxy_password"].toString();
proxyManager->setProxyConfiguration(proxyConf);
if (m.contains("proxy_peer_connections"))
session->setProxyPeerConnectionsEnabled(m["proxy_peer_connections"].toBool());
if (m.contains("force_proxy"))
session->setForceProxyEnabled(m["force_proxy"].toBool());
// IP Filtering
if (m.contains("ip_filter_enabled"))
session->setIPFilteringEnabled(m["ip_filter_enabled"].toBool());
if (m.contains("ip_filter_path"))
session->setIPFilterFile(m["ip_filter_path"].toString());
if (m.contains("ip_filter_trackers"))
session->setTrackerFilteringEnabled(m["ip_filter_trackers"].toBool());
if (m.contains("banned_IPs"))
session->setBannedIPs(m["banned_IPs"].toString().split('\n'));
// Speed
// Global Rate Limits
if (m.contains("dl_limit"))
session->setGlobalDownloadSpeedLimit(m["dl_limit"].toInt());
if (m.contains("up_limit"))
session->setGlobalUploadSpeedLimit(m["up_limit"].toInt());
if (m.contains("bittorrent_protocol"))
session->setBTProtocol(static_cast<BitTorrent::BTProtocol>(m["bittorrent_protocol"].toInt()));
if (m.contains("limit_utp_rate"))
session->setUTPRateLimited(m["limit_utp_rate"].toBool());
if (m.contains("limit_tcp_overhead"))
session->setIncludeOverheadInLimits(m["limit_tcp_overhead"].toBool());
if (m.contains("alt_dl_limit"))
session->setAltGlobalDownloadSpeedLimit(m["alt_dl_limit"].toInt());
if (m.contains("alt_up_limit"))
session->setAltGlobalUploadSpeedLimit(m["alt_up_limit"].toInt());
// Scheduling
if (m.contains("scheduler_enabled"))
session->setBandwidthSchedulerEnabled(m["scheduler_enabled"].toBool());
if (m.contains("schedule_from_hour") && m.contains("schedule_from_min"))
pref->setSchedulerStartTime(QTime(m["schedule_from_hour"].toInt(), m["schedule_from_min"].toInt()));
if (m.contains("schedule_to_hour") && m.contains("schedule_to_min"))
pref->setSchedulerEndTime(QTime(m["schedule_to_hour"].toInt(), m["schedule_to_min"].toInt()));
if (m.contains("scheduler_days"))
pref->setSchedulerDays(scheduler_days(m["scheduler_days"].toInt()));
// Bittorrent
// Privacy
if (m.contains("dht"))
session->setDHTEnabled(m["dht"].toBool());
if (m.contains("pex"))
session->setPeXEnabled(m["pex"].toBool());
if (m.contains("lsd"))
session->setLSDEnabled(m["lsd"].toBool());
if (m.contains("encryption"))
session->setEncryption(m["encryption"].toInt());
if (m.contains("anonymous_mode"))
session->setAnonymousModeEnabled(m["anonymous_mode"].toBool());
// Torrent Queueing
if (m.contains("queueing_enabled"))
session->setQueueingSystemEnabled(m["queueing_enabled"].toBool());
if (m.contains("max_active_downloads"))
session->setMaxActiveDownloads(m["max_active_downloads"].toInt());
if (m.contains("max_active_torrents"))
session->setMaxActiveTorrents(m["max_active_torrents"].toInt());
if (m.contains("max_active_uploads"))
session->setMaxActiveUploads(m["max_active_uploads"].toInt());
if (m.contains("dont_count_slow_torrents"))
session->setIgnoreSlowTorrentsForQueueing(m["dont_count_slow_torrents"].toBool());
// Share Ratio Limiting
if (m.contains("max_ratio_enabled"))
session->setGlobalMaxRatio(m["max_ratio"].toReal());
else
session->setGlobalMaxRatio(-1);
if (m.contains("max_seeding_time_enabled"))
session->setGlobalMaxSeedingMinutes(m["max_seeding_time"].toInt());
else
session->setGlobalMaxSeedingMinutes(-1);
if (m.contains("max_ratio_act"))
session->setMaxRatioAction(static_cast<MaxRatioAction>(m["max_ratio_act"].toInt()));
// Add trackers
session->setAddTrackersEnabled(m["add_trackers_enabled"].toBool());
session->setAdditionalTrackers(m["add_trackers"].toString());
// Web UI
// Language
if (m.contains("locale")) {
QString locale = m["locale"].toString();
if (pref->getLocale() != locale) {
QTranslator *translator = new QTranslator;
if (translator->load(QLatin1String(":/lang/qbittorrent_") + locale)) {
qDebug("%s locale recognized, using translation.", qUtf8Printable(locale));
}else{
qDebug("%s locale unrecognized, using default (en).", qUtf8Printable(locale));
}
qApp->installTranslator(translator);
pref->setLocale(locale);
}
}
// HTTP Server
if (m.contains("web_ui_domain_list"))
pref->setServerDomains(m["web_ui_domain_list"].toString());
if (m.contains("web_ui_address"))
pref->setWebUiAddress(m["web_ui_address"].toString());
if (m.contains("web_ui_port"))
pref->setWebUiPort(m["web_ui_port"].toUInt());
if (m.contains("web_ui_upnp"))
pref->setUPnPForWebUIPort(m["web_ui_upnp"].toBool());
if (m.contains("use_https"))
pref->setWebUiHttpsEnabled(m["use_https"].toBool());
#ifndef QT_NO_OPENSSL
if (m.contains("ssl_key")) {
QByteArray raw_key = m["ssl_key"].toString().toLatin1();
if (!QSslKey(raw_key, QSsl::Rsa).isNull())
pref->setWebUiHttpsKey(raw_key);
}
if (m.contains("ssl_cert")) {
QByteArray raw_cert = m["ssl_cert"].toString().toLatin1();
if (!QSslCertificate(raw_cert).isNull())
pref->setWebUiHttpsCertificate(raw_cert);
}
#endif
// Authentication
if (m.contains("web_ui_username"))
pref->setWebUiUsername(m["web_ui_username"].toString());
if (m.contains("web_ui_password"))
pref->setWebUiPassword(m["web_ui_password"].toString());
if (m.contains("bypass_local_auth"))
pref->setWebUiLocalAuthEnabled(!m["bypass_local_auth"].toBool());
if (m.contains("bypass_auth_subnet_whitelist_enabled"))
pref->setWebUiAuthSubnetWhitelistEnabled(m["bypass_auth_subnet_whitelist_enabled"].toBool());
if (m.contains("bypass_auth_subnet_whitelist")) {
// recognize new lines and commas as delimiters
pref->setWebUiAuthSubnetWhitelist(m["bypass_auth_subnet_whitelist"].toString().split(QRegularExpression("\n|,"), QString::SkipEmptyParts));
}
// Update my dynamic domain name
if (m.contains("dyndns_enabled"))
pref->setDynDNSEnabled(m["dyndns_enabled"].toBool());
if (m.contains("dyndns_service"))
pref->setDynDNSService(m["dyndns_service"].toInt());
if (m.contains("dyndns_username"))
pref->setDynDNSUsername(m["dyndns_username"].toString());
if (m.contains("dyndns_password"))
pref->setDynDNSPassword(m["dyndns_password"].toString());
if (m.contains("dyndns_domain"))
pref->setDynDomainName(m["dyndns_domain"].toString());
// Save preferences
pref->apply();
RSS::Session::instance()->setRefreshInterval(m["RSSRefreshInterval"].toUInt());
RSS::Session::instance()->setMaxArticlesPerFeed(m["RSSMaxArticlesPerFeed"].toInt());
RSS::Session::instance()->setProcessingEnabled(m["RSSProcessingEnabled"].toBool());
RSS::AutoDownloader::instance()->setProcessingEnabled(m["RSSAutoDownloadingEnabled"].toBool());
}
void AppController::defaultSavePathAction()
{
setResult(BitTorrent::Session::instance()->defaultSavePath());
}

View File

@@ -0,0 +1,50 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2006-2012 Ishan Arora <ishan@qbittorrent.org>
*
* 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.
*/
#pragma once
#include "apicontroller.h"
class AppController : public APIController
{
Q_OBJECT
Q_DISABLE_COPY(AppController)
public:
using APIController::APIController;
private slots:
void webapiVersionAction();
void versionAction();
void shutdownAction();
void preferencesAction();
void setPreferencesAction();
void defaultSavePathAction();
};

View File

@@ -0,0 +1,108 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "authcontroller.h"
#include <QCryptographicHash>
#include "base/preferences.h"
#include "base/utils/string.h"
#include "apierror.h"
#include "isessionmanager.h"
constexpr int BAN_TIME = 3600000; // 1 hour
constexpr int MAX_AUTH_FAILED_ATTEMPTS = 5;
void AuthController::loginAction()
{
if (sessionManager()->session()) {
setResult(QLatin1String("Ok."));
return;
}
if (isBanned())
throw APIError(APIErrorType::AccessDenied
, tr("Your IP address has been banned after too many failed authentication attempts."));
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(params()["password"].toLocal8Bit());
QString pass = md5.result().toHex();
const QString username {Preferences::instance()->getWebUiUsername()};
const QString password {Preferences::instance()->getWebUiPassword()};
const bool equalUser = Utils::String::slowEquals(params()["username"].toUtf8(), username.toUtf8());
const bool equalPass = Utils::String::slowEquals(pass.toUtf8(), password.toUtf8());
if (equalUser && equalPass) {
sessionManager()->sessionStart();
setResult(QLatin1String("Ok."));
}
else {
QString addr = sessionManager()->clientId();
increaseFailedAttempts();
qDebug("client IP: %s (%d failed attempts)", qUtf8Printable(addr), failedAttemptsCount());
setResult(QLatin1String("Fails."));
}
}
void AuthController::logoutAction()
{
sessionManager()->sessionEnd();
}
bool AuthController::isBanned() const
{
const uint now = QDateTime::currentDateTime().toTime_t();
const FailedLogin failedLogin = m_clientFailedLogins.value(sessionManager()->clientId());
bool isBanned = (failedLogin.bannedAt > 0);
if (isBanned && ((now - failedLogin.bannedAt) > BAN_TIME)) {
m_clientFailedLogins.remove(sessionManager()->clientId());
isBanned = false;
}
return isBanned;
}
int AuthController::failedAttemptsCount() const
{
return m_clientFailedLogins.value(sessionManager()->clientId()).failedAttemptsCount;
}
void AuthController::increaseFailedAttempts()
{
FailedLogin &failedLogin = m_clientFailedLogins[sessionManager()->clientId()];
++failedLogin.failedAttemptsCount;
if (failedLogin.failedAttemptsCount == MAX_AUTH_FAILED_ATTEMPTS) {
// Max number of failed attempts reached
// Start ban period
failedLogin.bannedAt = QDateTime::currentDateTime().toTime_t();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include <QHash>
#include <QString>
#include "apicontroller.h"
class AuthController : public APIController
{
Q_OBJECT
Q_DISABLE_COPY(AuthController)
public:
using APIController::APIController;
private slots:
void loginAction();
void logoutAction();
private:
bool isBanned() const;
int failedAttemptsCount() const;
void increaseFailedAttempts();
struct FailedLogin
{
int failedAttemptsCount = 0;
uint bannedAt = 0;
};
mutable QHash<QString, FailedLogin> m_clientFailedLogins;
};

View File

@@ -0,0 +1,49 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include <QString>
#include <QVariant>
struct ISession
{
virtual ~ISession() = default;
virtual QString id() const = 0;
virtual QVariant getData(const QString &id) const = 0;
virtual void setData(const QString &id, const QVariant &data) = 0;
};
struct ISessionManager
{
virtual ~ISessionManager() = default;
virtual QString clientId() const = 0;
virtual ISession *session() = 0;
virtual void sessionStart() = 0;
virtual void sessionEnd() = 0;
};

View File

@@ -0,0 +1,124 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "logcontroller.h"
#include <QJsonArray>
#include "base/logger.h"
#include "base/utils/string.h"
const char KEY_LOG_ID[] = "id";
const char KEY_LOG_TIMESTAMP[] = "timestamp";
const char KEY_LOG_MSG_TYPE[] = "type";
const char KEY_LOG_MSG_MESSAGE[] = "message";
const char KEY_LOG_PEER_IP[] = "ip";
const char KEY_LOG_PEER_BLOCKED[] = "blocked";
const char KEY_LOG_PEER_REASON[] = "reason";
// Returns the log in JSON format.
// The return value is an array of dictionaries.
// The dictionary keys are:
// - "id": id of the message
// - "timestamp": milliseconds since epoch
// - "type": type of the message (int, see MsgType)
// - "message": text of the message
// 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 LogController::mainAction()
{
using Utils::String::parseBool;
const bool isNormal = parseBool(params()["normal"], true);
const bool isInfo = parseBool(params()["info"], true);
const bool isWarning = parseBool(params()["warning"], true);
const bool isCritical = parseBool(params()["critical"], true);
bool ok = false;
int lastKnownId = params()["last_known_id"].toInt(&ok);
if (!ok)
lastKnownId = -1;
Logger *const logger = Logger::instance();
QVariantList msgList;
foreach (const Log::Msg &msg, logger->getMessages(lastKnownId)) {
if (!((msg.type == Log::NORMAL && isNormal)
|| (msg.type == Log::INFO && isInfo)
|| (msg.type == Log::WARNING && isWarning)
|| (msg.type == Log::CRITICAL && isCritical)))
continue;
QVariantMap map;
map[KEY_LOG_ID] = msg.id;
map[KEY_LOG_TIMESTAMP] = msg.timestamp;
map[KEY_LOG_MSG_TYPE] = msg.type;
map[KEY_LOG_MSG_MESSAGE] = msg.message;
msgList.append(map);
}
setResult(QJsonArray::fromVariantList(msgList));
}
// Returns the peer log in JSON format.
// The return value is an array of dictionaries.
// The dictionary keys are:
// - "id": id of the message
// - "timestamp": milliseconds since epoch
// - "ip": IP of the peer
// - "blocked": whether or not the peer was blocked
// - "reason": reason of the block
// GET params:
// - last_known_id (int): exclude messages with id <= 'last_known_id' (default -1)
void LogController::peersAction()
{
int lastKnownId;
bool ok;
lastKnownId = params()["last_known_id"].toInt(&ok);
if (!ok)
lastKnownId = -1;
Logger *const logger = Logger::instance();
QVariantList peerList;
foreach (const Log::Peer &peer, logger->getPeers(lastKnownId)) {
QVariantMap map;
map[KEY_LOG_ID] = peer.id;
map[KEY_LOG_TIMESTAMP] = peer.timestamp;
map[KEY_LOG_PEER_IP] = peer.ip;
map[KEY_LOG_PEER_BLOCKED] = peer.blocked;
map[KEY_LOG_PEER_REASON] = peer.reason;
peerList.append(map);
}
setResult(QJsonArray::fromVariantList(peerList));
}

View File

@@ -0,0 +1,44 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include "apicontroller.h"
class LogController : public APIController
{
Q_OBJECT
Q_DISABLE_COPY(LogController)
public:
using APIController::APIController;
private slots:
void mainAction();
void peersAction();
};

View File

@@ -0,0 +1,131 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "rsscontroller.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include "base/rss/rss_autodownloader.h"
#include "base/rss/rss_autodownloadrule.h"
#include "base/rss/rss_folder.h"
#include "base/rss/rss_session.h"
#include "base/utils/string.h"
#include "apierror.h"
using Utils::String::parseBool;
void RSSController::addFolderAction()
{
checkParams({"path"});
const QString path = params()["path"].trimmed();
QString error;
if (!RSS::Session::instance()->addFolder(path, &error))
throw APIError(APIErrorType::Conflict, error);
}
void RSSController::addFeedAction()
{
checkParams({"url", "path"});
const QString url = params()["url"].trimmed();
const QString path = params()["path"].trimmed();
QString error;
if (!RSS::Session::instance()->addFeed(url, (path.isEmpty() ? url : path), &error))
throw APIError(APIErrorType::Conflict, error);
}
void RSSController::removeItemAction()
{
checkParams({"path"});
const QString path = params()["path"].trimmed();
QString error;
if (!RSS::Session::instance()->removeItem(path, &error))
throw APIError(APIErrorType::Conflict, error);
}
void RSSController::moveItemAction()
{
checkParams({"itemPath", "destPath"});
const QString itemPath = params()["itemPath"].trimmed();
const QString destPath = params()["destPath"].trimmed();
QString error;
if (!RSS::Session::instance()->moveItem(itemPath, destPath, &error))
throw APIError(APIErrorType::Conflict, error);
}
void RSSController::itemsAction()
{
const bool withData {parseBool(params()["withData"], false)};
const auto jsonVal = RSS::Session::instance()->rootFolder()->toJsonValue(withData);
setResult(jsonVal.toObject());
}
void RSSController::setRuleAction()
{
checkParams({"ruleName", "ruleDef"});
const QString ruleName {params()["ruleName"].trimmed()};
const QByteArray ruleDef {params()["ruleDef"].trimmed().toUtf8()};
const auto jsonObj = QJsonDocument::fromJson(ruleDef).object();
RSS::AutoDownloader::instance()->insertRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName));
}
void RSSController::renameRuleAction()
{
checkParams({"ruleName", "newRuleName"});
const QString ruleName {params()["ruleName"].trimmed()};
const QString newRuleName {params()["newRuleName"].trimmed()};
RSS::AutoDownloader::instance()->renameRule(ruleName, newRuleName);
}
void RSSController::removeRuleAction()
{
checkParams({"ruleName"});
const QString ruleName {params()["ruleName"].trimmed()};
RSS::AutoDownloader::instance()->removeRule(ruleName);
}
void RSSController::rulesAction()
{
const QList<RSS::AutoDownloadRule> rules {RSS::AutoDownloader::instance()->rules()};
QJsonObject jsonObj;
for (const auto &rule : rules)
jsonObj.insert(rule.name(), rule.toJsonObject());
setResult(jsonObj);
}

View File

@@ -0,0 +1,51 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include "apicontroller.h"
class RSSController : public APIController
{
Q_OBJECT
Q_DISABLE_COPY(RSSController)
public:
using APIController::APIController;
private slots:
void addFolderAction();
void addFeedAction();
void removeItemAction();
void moveItemAction();
void itemsAction();
void setRuleAction();
void renameRuleAction();
void removeRuleAction();
void rulesAction();
};

View File

@@ -0,0 +1,140 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "serialize_torrent.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrenthandle.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
namespace
{
QString torrentStateToString(const BitTorrent::TorrentState state)
{
switch (state) {
case BitTorrent::TorrentState::Error:
return QLatin1String("error");
case BitTorrent::TorrentState::MissingFiles:
return QLatin1String("missingFiles");
case BitTorrent::TorrentState::Uploading:
return QLatin1String("uploading");
case BitTorrent::TorrentState::PausedUploading:
return QLatin1String("pausedUP");
case BitTorrent::TorrentState::QueuedUploading:
return QLatin1String("queuedUP");
case BitTorrent::TorrentState::StalledUploading:
return QLatin1String("stalledUP");
case BitTorrent::TorrentState::CheckingUploading:
return QLatin1String("checkingUP");
case BitTorrent::TorrentState::ForcedUploading:
return QLatin1String("forcedUP");
case BitTorrent::TorrentState::Allocating:
return QLatin1String("allocating");
case BitTorrent::TorrentState::Downloading:
return QLatin1String("downloading");
case BitTorrent::TorrentState::DownloadingMetadata:
return QLatin1String("metaDL");
case BitTorrent::TorrentState::PausedDownloading:
return QLatin1String("pausedDL");
case BitTorrent::TorrentState::QueuedDownloading:
return QLatin1String("queuedDL");
case BitTorrent::TorrentState::StalledDownloading:
return QLatin1String("stalledDL");
case BitTorrent::TorrentState::CheckingDownloading:
return QLatin1String("checkingDL");
case BitTorrent::TorrentState::ForcedDownloading:
return QLatin1String("forcedDL");
#if LIBTORRENT_VERSION_NUM < 10100
case BitTorrent::TorrentState::QueuedForChecking:
return QLatin1String("queuedForChecking");
#endif
case BitTorrent::TorrentState::CheckingResumeData:
return QLatin1String("checkingResumeData");
default:
return QLatin1String("unknown");
}
}
}
QVariantMap serialize(const BitTorrent::TorrentHandle &torrent)
{
QVariantMap ret;
ret[KEY_TORRENT_HASH] = QString(torrent.hash());
ret[KEY_TORRENT_NAME] = torrent.name();
ret[KEY_TORRENT_MAGNET_URI] = torrent.toMagnetUri();
ret[KEY_TORRENT_SIZE] = torrent.wantedSize();
ret[KEY_TORRENT_PROGRESS] = torrent.progress();
ret[KEY_TORRENT_DLSPEED] = torrent.downloadPayloadRate();
ret[KEY_TORRENT_UPSPEED] = torrent.uploadPayloadRate();
ret[KEY_TORRENT_PRIORITY] = torrent.queuePosition();
ret[KEY_TORRENT_SEEDS] = torrent.seedsCount();
ret[KEY_TORRENT_NUM_COMPLETE] = torrent.totalSeedsCount();
ret[KEY_TORRENT_LEECHS] = torrent.leechsCount();
ret[KEY_TORRENT_NUM_INCOMPLETE] = torrent.totalLeechersCount();
const qreal ratio = torrent.realRatio();
ret[KEY_TORRENT_RATIO] = (ratio > BitTorrent::TorrentHandle::MAX_RATIO) ? -1 : ratio;
ret[KEY_TORRENT_STATE] = torrentStateToString(torrent.state());
ret[KEY_TORRENT_ETA] = torrent.eta();
ret[KEY_TORRENT_SEQUENTIAL_DOWNLOAD] = torrent.isSequentialDownload();
if (torrent.hasMetadata())
ret[KEY_TORRENT_FIRST_LAST_PIECE_PRIO] = torrent.hasFirstLastPiecePriority();
ret[KEY_TORRENT_CATEGORY] = torrent.category();
ret[KEY_TORRENT_TAGS] = torrent.tags().toList().join(", ");
ret[KEY_TORRENT_SUPER_SEEDING] = torrent.superSeeding();
ret[KEY_TORRENT_FORCE_START] = torrent.isForced();
ret[KEY_TORRENT_SAVE_PATH] = Utils::Fs::toNativePath(torrent.savePath());
ret[KEY_TORRENT_ADDED_ON] = torrent.addedTime().toTime_t();
ret[KEY_TORRENT_COMPLETION_ON] = torrent.completedTime().toTime_t();
ret[KEY_TORRENT_TRACKER] = torrent.currentTracker();
ret[KEY_TORRENT_DL_LIMIT] = torrent.downloadLimit();
ret[KEY_TORRENT_UP_LIMIT] = torrent.uploadLimit();
ret[KEY_TORRENT_AMOUNT_DOWNLOADED] = torrent.totalDownload();
ret[KEY_TORRENT_AMOUNT_UPLOADED] = torrent.totalUpload();
ret[KEY_TORRENT_AMOUNT_DOWNLOADED_SESSION] = torrent.totalPayloadDownload();
ret[KEY_TORRENT_AMOUNT_UPLOADED_SESSION] = torrent.totalPayloadUpload();
ret[KEY_TORRENT_AMOUNT_LEFT] = torrent.incompletedSize();
ret[KEY_TORRENT_AMOUNT_COMPLETED] = torrent.completedSize();
ret[KEY_TORRENT_RATIO_LIMIT] = torrent.maxRatio();
ret[KEY_TORRENT_LAST_SEEN_COMPLETE_TIME] = torrent.lastSeenComplete().toTime_t();
ret[KEY_TORRENT_AUTO_TORRENT_MANAGEMENT] = torrent.isAutoTMMEnabled();
ret[KEY_TORRENT_TIME_ACTIVE] = torrent.activeTime();
if (torrent.isPaused() || torrent.isChecking()) {
ret[KEY_TORRENT_LAST_ACTIVITY_TIME] = 0;
}
else {
QDateTime dt = QDateTime::currentDateTime();
dt = dt.addSecs(-torrent.timeSinceActivity());
ret[KEY_TORRENT_LAST_ACTIVITY_TIME] = dt.toTime_t();
}
ret[KEY_TORRENT_TOTAL_SIZE] = torrent.totalSize();
return ret;
}

View File

@@ -0,0 +1,79 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include <QVariantMap>
namespace BitTorrent
{
class TorrentHandle;
}
// Torrent keys
const char KEY_TORRENT_HASH[] = "hash";
const char KEY_TORRENT_NAME[] = "name";
const char KEY_TORRENT_MAGNET_URI[] = "magnet_uri";
const char KEY_TORRENT_SIZE[] = "size";
const char KEY_TORRENT_PROGRESS[] = "progress";
const char KEY_TORRENT_DLSPEED[] = "dlspeed";
const char KEY_TORRENT_UPSPEED[] = "upspeed";
const char KEY_TORRENT_PRIORITY[] = "priority";
const char KEY_TORRENT_SEEDS[] = "num_seeds";
const char KEY_TORRENT_NUM_COMPLETE[] = "num_complete";
const char KEY_TORRENT_LEECHS[] = "num_leechs";
const char KEY_TORRENT_NUM_INCOMPLETE[] = "num_incomplete";
const char KEY_TORRENT_RATIO[] = "ratio";
const char KEY_TORRENT_ETA[] = "eta";
const char KEY_TORRENT_STATE[] = "state";
const char KEY_TORRENT_SEQUENTIAL_DOWNLOAD[] = "seq_dl";
const char KEY_TORRENT_FIRST_LAST_PIECE_PRIO[] = "f_l_piece_prio";
const char KEY_TORRENT_CATEGORY[] = "category";
const char KEY_TORRENT_TAGS[] = "tags";
const char KEY_TORRENT_SUPER_SEEDING[] = "super_seeding";
const char KEY_TORRENT_FORCE_START[] = "force_start";
const char KEY_TORRENT_SAVE_PATH[] = "save_path";
const char KEY_TORRENT_ADDED_ON[] = "added_on";
const char KEY_TORRENT_COMPLETION_ON[] = "completion_on";
const char KEY_TORRENT_TRACKER[] = "tracker";
const char KEY_TORRENT_DL_LIMIT[] = "dl_limit";
const char KEY_TORRENT_UP_LIMIT[] = "up_limit";
const char KEY_TORRENT_AMOUNT_DOWNLOADED[] = "downloaded";
const char KEY_TORRENT_AMOUNT_UPLOADED[] = "uploaded";
const char KEY_TORRENT_AMOUNT_DOWNLOADED_SESSION[] = "downloaded_session";
const char KEY_TORRENT_AMOUNT_UPLOADED_SESSION[] = "uploaded_session";
const char KEY_TORRENT_AMOUNT_LEFT[] = "amount_left";
const char KEY_TORRENT_AMOUNT_COMPLETED[] = "completed";
const char KEY_TORRENT_RATIO_LIMIT[] = "ratio_limit";
const char KEY_TORRENT_LAST_SEEN_COMPLETE_TIME[] = "seen_complete";
const char KEY_TORRENT_LAST_ACTIVITY_TIME[] = "last_activity";
const char KEY_TORRENT_TOTAL_SIZE[] = "total_size";
const char KEY_TORRENT_AUTO_TORRENT_MANAGEMENT[] = "auto_tmm";
const char KEY_TORRENT_TIME_ACTIVE[] = "time_active";
QVariantMap serialize(const BitTorrent::TorrentHandle &torrent);

View File

@@ -0,0 +1,473 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "synccontroller.h"
#include <QJsonObject>
#include "base/bittorrent/peerinfo.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrenthandle.h"
#include "base/net/geoipmanager.h"
#include "base/preferences.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
#include "apierror.h"
#include "isessionmanager.h"
#include "serialize/serialize_torrent.h"
// Sync main data keys
const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing";
const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits";
const char KEY_SYNC_MAINDATA_REFRESH_INTERVAL[] = "refresh_interval";
// Sync torrent peers keys
const char KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS[] = "show_flags";
// Peer keys
const char KEY_PEER_IP[] = "ip";
const char KEY_PEER_PORT[] = "port";
const char KEY_PEER_COUNTRY_CODE[] = "country_code";
const char KEY_PEER_COUNTRY[] = "country";
const char KEY_PEER_CLIENT[] = "client";
const char KEY_PEER_PROGRESS[] = "progress";
const char KEY_PEER_DOWN_SPEED[] = "dl_speed";
const char KEY_PEER_UP_SPEED[] = "up_speed";
const char KEY_PEER_TOT_DOWN[] = "downloaded";
const char KEY_PEER_TOT_UP[] = "uploaded";
const char KEY_PEER_CONNECTION_TYPE[] = "connection";
const char KEY_PEER_FLAGS[] = "flags";
const char KEY_PEER_FLAGS_DESCRIPTION[] = "flags_desc";
const char KEY_PEER_RELEVANCE[] = "relevance";
const char KEY_PEER_FILES[] = "files";
// TransferInfo keys
const char KEY_TRANSFER_DLSPEED[] = "dl_info_speed";
const char KEY_TRANSFER_DLDATA[] = "dl_info_data";
const char KEY_TRANSFER_DLRATELIMIT[] = "dl_rate_limit";
const char KEY_TRANSFER_UPSPEED[] = "up_info_speed";
const char KEY_TRANSFER_UPDATA[] = "up_info_data";
const char KEY_TRANSFER_UPRATELIMIT[] = "up_rate_limit";
const char KEY_TRANSFER_DHT_NODES[] = "dht_nodes";
const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status";
// Statistics keys
const char KEY_TRANSFER_ALLTIME_DL[] = "alltime_dl";
const char KEY_TRANSFER_ALLTIME_UL[] = "alltime_ul";
const char KEY_TRANSFER_TOTAL_WASTE_SESSION[] = "total_wasted_session";
const char KEY_TRANSFER_GLOBAL_RATIO[] = "global_ratio";
const char KEY_TRANSFER_TOTAL_PEER_CONNECTIONS[] = "total_peer_connections";
const char KEY_TRANSFER_READ_CACHE_HITS[] = "read_cache_hits";
const char KEY_TRANSFER_TOTAL_BUFFERS_SIZE[] = "total_buffers_size";
const char KEY_TRANSFER_WRITE_CACHE_OVERLOAD[] = "write_cache_overload";
const char KEY_TRANSFER_READ_CACHE_OVERLOAD[] = "read_cache_overload";
const char KEY_TRANSFER_QUEUED_IO_JOBS[] = "queued_io_jobs";
const char KEY_TRANSFER_AVERAGE_TIME_QUEUE[] = "average_time_queue";
const char KEY_TRANSFER_TOTAL_QUEUED_SIZE[] = "total_queued_size";
const char KEY_FULL_UPDATE[] = "full_update";
const char KEY_RESPONSE_ID[] = "rid";
const char KEY_SUFFIX_REMOVED[] = "_removed";
namespace
{
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData);
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems);
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems);
QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData);
QVariantMap getTranserInfo()
{
QVariantMap map;
const BitTorrent::SessionStatus &sessionStatus = BitTorrent::Session::instance()->status();
const BitTorrent::CacheStatus &cacheStatus = BitTorrent::Session::instance()->cacheStatus();
map[KEY_TRANSFER_DLSPEED] = sessionStatus.payloadDownloadRate;
map[KEY_TRANSFER_DLDATA] = sessionStatus.totalPayloadDownload;
map[KEY_TRANSFER_UPSPEED] = sessionStatus.payloadUploadRate;
map[KEY_TRANSFER_UPDATA] = sessionStatus.totalPayloadUpload;
map[KEY_TRANSFER_DLRATELIMIT] = BitTorrent::Session::instance()->downloadSpeedLimit();
map[KEY_TRANSFER_UPRATELIMIT] = BitTorrent::Session::instance()->uploadSpeedLimit();
quint64 atd = BitTorrent::Session::instance()->getAlltimeDL();
quint64 atu = BitTorrent::Session::instance()->getAlltimeUL();
map[KEY_TRANSFER_ALLTIME_DL] = atd;
map[KEY_TRANSFER_ALLTIME_UL] = atu;
map[KEY_TRANSFER_TOTAL_WASTE_SESSION] = sessionStatus.totalWasted;
map[KEY_TRANSFER_GLOBAL_RATIO] = ((atd > 0) && (atu > 0)) ? Utils::String::fromDouble(static_cast<qreal>(atu) / atd, 2) : "-";
map[KEY_TRANSFER_TOTAL_PEER_CONNECTIONS] = sessionStatus.peersCount;
qreal readRatio = cacheStatus.readRatio;
map[KEY_TRANSFER_READ_CACHE_HITS] = (readRatio >= 0) ? Utils::String::fromDouble(100 * readRatio, 2) : "-";
map[KEY_TRANSFER_TOTAL_BUFFERS_SIZE] = cacheStatus.totalUsedBuffers * 16 * 1024;
// num_peers is not reliable (adds up peers, which didn't even overcome tcp handshake)
quint32 peers = 0;
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents())
peers += torrent->peersCount();
map[KEY_TRANSFER_WRITE_CACHE_OVERLOAD] = ((sessionStatus.diskWriteQueue > 0) && (peers > 0)) ? Utils::String::fromDouble((100. * sessionStatus.diskWriteQueue) / peers, 2) : "0";
map[KEY_TRANSFER_READ_CACHE_OVERLOAD] = ((sessionStatus.diskReadQueue > 0) && (peers > 0)) ? Utils::String::fromDouble((100. * sessionStatus.diskReadQueue) / peers, 2) : "0";
map[KEY_TRANSFER_QUEUED_IO_JOBS] = cacheStatus.jobQueueLength;
map[KEY_TRANSFER_AVERAGE_TIME_QUEUE] = cacheStatus.averageJobTime;
map[KEY_TRANSFER_TOTAL_QUEUED_SIZE] = cacheStatus.queuedBytes;
map[KEY_TRANSFER_DHT_NODES] = sessionStatus.dhtNodes;
if (!BitTorrent::Session::instance()->isListening())
map[KEY_TRANSFER_CONNECTION_STATUS] = "disconnected";
else
map[KEY_TRANSFER_CONNECTION_STATUS] = sessionStatus.hasIncomingConnections ? "connected" : "firewalled";
return map;
}
// Compare two structures (prevData, data) and calculate difference (syncData).
// Structures encoded as map.
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData)
{
// initialize output variable
syncData.clear();
QVariantList removedItems;
foreach (QString key, data.keys()) {
removedItems.clear();
switch (static_cast<QMetaType::Type>(data[key].type())) {
case QMetaType::QVariantMap: {
QVariantMap map;
processMap(prevData[key].toMap(), data[key].toMap(), map);
if (!map.isEmpty())
syncData[key] = map;
}
break;
case QMetaType::QVariantHash: {
QVariantMap map;
processHash(prevData[key].toHash(), data[key].toHash(), map, removedItems);
if (!map.isEmpty())
syncData[key] = map;
if (!removedItems.isEmpty())
syncData[key + KEY_SUFFIX_REMOVED] = removedItems;
}
break;
case QMetaType::QVariantList: {
QVariantList list;
processList(prevData[key].toList(), data[key].toList(), list, removedItems);
if (!list.isEmpty())
syncData[key] = list;
if (!removedItems.isEmpty())
syncData[key + KEY_SUFFIX_REMOVED] = removedItems;
}
break;
case QMetaType::QString:
case QMetaType::LongLong:
case QMetaType::Float:
case QMetaType::Int:
case QMetaType::Bool:
case QMetaType::Double:
case QMetaType::ULongLong:
case QMetaType::UInt:
case QMetaType::QDateTime:
if (prevData[key] != data[key])
syncData[key] = data[key];
break;
default:
Q_ASSERT_X(false, "processMap"
, QString("Unexpected type: %1")
.arg(QMetaType::typeName(static_cast<QMetaType::Type>(data[key].type())))
.toUtf8().constData());
}
}
}
// Compare two lists of structures (prevData, data) and calculate difference (syncData, removedItems).
// Structures encoded as map.
// Lists are encoded as hash table (indexed by structure key value) to improve ease of searching for removed items.
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems)
{
// initialize output variables
syncData.clear();
removedItems.clear();
if (prevData.isEmpty()) {
// If list was empty before, then difference is a whole new list.
foreach (QString key, data.keys())
syncData[key] = data[key];
}
else {
foreach (QString key, data.keys()) {
switch (data[key].type()) {
case QVariant::Map:
if (!prevData.contains(key)) {
// new list item found - append it to syncData
syncData[key] = data[key];
}
else {
QVariantMap map;
processMap(prevData[key].toMap(), data[key].toMap(), map);
// existing list item found - remove it from prevData
prevData.remove(key);
if (!map.isEmpty())
// changed list item found - append its changes to syncData
syncData[key] = map;
}
break;
default:
Q_ASSERT(0);
}
}
if (!prevData.isEmpty()) {
// prevData contains only items that are missing now -
// put them in removedItems
foreach (QString s, prevData.keys())
removedItems << s;
}
}
}
// Compare two lists of simple value (prevData, data) and calculate difference (syncData, removedItems).
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems)
{
// initialize output variables
syncData.clear();
removedItems.clear();
if (prevData.isEmpty()) {
// If list was empty before, then difference is a whole new list.
syncData = data;
}
else {
foreach (QVariant item, data) {
if (!prevData.contains(item))
// new list item found - append it to syncData
syncData.append(item);
else
// unchanged list item found - remove it from prevData
prevData.removeOne(item);
}
if (!prevData.isEmpty())
// prevData contains only items that are missing now -
// put them in removedItems
removedItems = prevData;
}
}
QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData)
{
QVariantMap syncData;
bool fullUpdate = true;
int lastResponseId = 0;
if (acceptedResponseId > 0) {
lastResponseId = lastData[KEY_RESPONSE_ID].toInt();
if (lastResponseId == acceptedResponseId)
lastAcceptedData = lastData;
int lastAcceptedResponseId = lastAcceptedData[KEY_RESPONSE_ID].toInt();
if (lastAcceptedResponseId == acceptedResponseId) {
processMap(lastAcceptedData, data, syncData);
fullUpdate = false;
}
}
if (fullUpdate) {
lastAcceptedData.clear();
syncData = data;
syncData[KEY_FULL_UPDATE] = true;
}
lastResponseId = lastResponseId % 1000000 + 1; // cycle between 1 and 1000000
lastData = data;
lastData[KEY_RESPONSE_ID] = lastResponseId;
syncData[KEY_RESPONSE_ID] = lastResponseId;
return syncData;
}
}
// The function returns the changed data from the server to synchronize with the web client.
// Return value is map in JSON format.
// Map contain the key:
// - "Rid": ID response
// Map can contain the keys:
// - "full_update": full data update flag
// - "torrents": dictionary contains information about torrents.
// - "torrents_removed": a list of hashes of removed torrents
// - "categories": list of categories
// - "categories_removed": list of removed categories
// - "server_state": map contains information about the state of the server
// The keys of the 'torrents' dictionary are hashes of torrents.
// Each value of the 'torrents' dictionary contains map. The map can contain following keys:
// - "name": Torrent name
// - "size": Torrent size
// - "progress: Torrent progress
// - "dlspeed": Torrent download speed
// - "upspeed": Torrent upload speed
// - "priority": Torrent priority (-1 if queuing is disabled)
// - "num_seeds": Torrent seeds connected to
// - "num_complete": Torrent seeds in the swarm
// - "num_leechs": Torrent leechers connected to
// - "num_incomplete": Torrent leechers in the swarm
// - "ratio": Torrent share ratio
// - "eta": Torrent ETA
// - "state": Torrent state
// - "seq_dl": Torrent sequential download state
// - "f_l_piece_prio": Torrent first last piece priority state
// - "completion_on": Torrent copletion time
// - "tracker": Torrent tracker
// - "dl_limit": Torrent download limit
// - "up_limit": Torrent upload limit
// - "downloaded": Amount of data downloaded
// - "uploaded": Amount of data uploaded
// - "downloaded_session": Amount of data downloaded since program open
// - "uploaded_session": Amount of data uploaded since program open
// - "amount_left": Amount of data left to download
// - "save_path": Torrent save path
// - "completed": Amount of data completed
// - "ratio_limit": Upload share ratio limit
// - "seen_complete": Indicates the time when the torrent was last seen complete/whole
// - "last_activity": Last time when a chunk was downloaded/uploaded
// - "total_size": Size including unwanted data
// Server state map may contain the following keys:
// - "connection_status": connection status
// - "dht_nodes": DHT nodes count
// - "dl_info_data": bytes downloaded
// - "dl_info_speed": download speed
// - "dl_rate_limit: download rate limit
// - "up_info_data: bytes uploaded
// - "up_info_speed: upload speed
// - "up_rate_limit: upload speed limit
// - "queueing": priority system usage flag
// - "refresh_interval": torrents table refresh interval
// GET param:
// - rid (int): last response id
void SyncController::maindataAction()
{
auto lastResponse = sessionManager()->session()->getData(QLatin1String("syncMainDataLastResponse")).toMap();
auto lastAcceptedResponse = sessionManager()->session()->getData(QLatin1String("syncMainDataLastAcceptedResponse")).toMap();
QVariantMap data;
QVariantHash torrents;
BitTorrent::Session *const session = BitTorrent::Session::instance();
foreach (BitTorrent::TorrentHandle *const torrent, session->torrents()) {
QVariantMap map = serialize(*torrent);
map.remove(KEY_TORRENT_HASH);
// Calculated last activity time can differ from actual value by up to 10 seconds (this is a libtorrent issue).
// So we don't need unnecessary updates of last activity time in response.
if (lastResponse.contains("torrents") && lastResponse["torrents"].toHash().contains(torrent->hash()) &&
lastResponse["torrents"].toHash()[torrent->hash()].toMap().contains(KEY_TORRENT_LAST_ACTIVITY_TIME)) {
uint lastValue = lastResponse["torrents"].toHash()[torrent->hash()].toMap()[KEY_TORRENT_LAST_ACTIVITY_TIME].toUInt();
if (qAbs(static_cast<int>(lastValue - map[KEY_TORRENT_LAST_ACTIVITY_TIME].toUInt())) < 15)
map[KEY_TORRENT_LAST_ACTIVITY_TIME] = lastValue;
}
torrents[torrent->hash()] = map;
}
data["torrents"] = torrents;
QVariantList categories;
foreach (const QString &category, session->categories().keys())
categories << category;
data["categories"] = categories;
QVariantMap serverState = getTranserInfo();
serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
data["server_state"] = serverState;
const int acceptedResponseId {params()["rid"].toInt()};
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse)));
sessionManager()->session()->setData(QLatin1String("syncMainDataLastResponse"), lastResponse);
sessionManager()->session()->setData(QLatin1String("syncMainDataLastAcceptedResponse"), lastAcceptedResponse);
}
// GET param:
// - hash (string): torrent hash
// - rid (int): last response id
void SyncController::torrentPeersAction()
{
auto lastResponse = sessionManager()->session()->getData(QLatin1String("syncTorrentPeersLastResponse")).toMap();
auto lastAcceptedResponse = sessionManager()->session()->getData(QLatin1String("syncTorrentPeersLastAcceptedResponse")).toMap();
const QString hash {params()["hash"]};
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
QVariantMap data;
QVariantHash peers;
QList<BitTorrent::PeerInfo> peersList = torrent->peers();
#ifndef DISABLE_COUNTRIES_RESOLUTION
bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries();
#else
bool resolvePeerCountries = false;
#endif
data[KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS] = resolvePeerCountries;
foreach (const BitTorrent::PeerInfo &pi, peersList) {
if (pi.address().ip.isNull()) continue;
QVariantMap peer;
#ifndef DISABLE_COUNTRIES_RESOLUTION
if (resolvePeerCountries) {
peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower();
peer[KEY_PEER_COUNTRY] = Net::GeoIPManager::CountryName(pi.country());
}
#endif
peer[KEY_PEER_IP] = pi.address().ip.toString();
peer[KEY_PEER_PORT] = pi.address().port;
peer[KEY_PEER_CLIENT] = pi.client();
peer[KEY_PEER_PROGRESS] = pi.progress();
peer[KEY_PEER_DOWN_SPEED] = pi.payloadDownSpeed();
peer[KEY_PEER_UP_SPEED] = pi.payloadUpSpeed();
peer[KEY_PEER_TOT_DOWN] = pi.totalDownload();
peer[KEY_PEER_TOT_UP] = pi.totalUpload();
peer[KEY_PEER_CONNECTION_TYPE] = pi.connectionType();
peer[KEY_PEER_FLAGS] = pi.flags();
peer[KEY_PEER_FLAGS_DESCRIPTION] = pi.flagsDescription();
peer[KEY_PEER_RELEVANCE] = pi.relevance();
peer[KEY_PEER_FILES] = torrent->info().filesForPiece(pi.downloadingPieceIndex()).join(QLatin1String("\n"));
peers[pi.address().ip.toString() + ":" + QString::number(pi.address().port)] = peer;
}
data["peers"] = peers;
const int acceptedResponseId {params()["rid"].toInt()};
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse)));
sessionManager()->session()->setData(QLatin1String("syncTorrentPeersLastResponse"), lastResponse);
sessionManager()->session()->setData(QLatin1String("syncTorrentPeersLastAcceptedResponse"), lastAcceptedResponse);
}

View File

@@ -0,0 +1,44 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include "apicontroller.h"
class SyncController : public APIController
{
Q_OBJECT
Q_DISABLE_COPY(SyncController)
public:
using APIController::APIController;
private slots:
void maindataAction();
void torrentPeersAction();
};

View File

@@ -0,0 +1,805 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "torrentscontroller.h"
#include <functional>
#include <QBitArray>
#include <QDir>
#include <QJsonArray>
#include <QJsonObject>
#include <QList>
#include <QNetworkCookie>
#include <QRegularExpression>
#include <QUrl>
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrenthandle.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/bittorrent/trackerentry.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
#include "base/torrentfilter.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
#include "serialize/serialize_torrent.h"
#include "apierror.h"
// Tracker keys
const char KEY_TRACKER_URL[] = "url";
const char KEY_TRACKER_STATUS[] = "status";
const char KEY_TRACKER_MSG[] = "msg";
const char KEY_TRACKER_PEERS[] = "num_peers";
// Web seed keys
const char KEY_WEBSEED_URL[] = "url";
// Torrent keys (Properties)
const char KEY_PROP_TIME_ELAPSED[] = "time_elapsed";
const char KEY_PROP_SEEDING_TIME[] = "seeding_time";
const char KEY_PROP_ETA[] = "eta";
const char KEY_PROP_CONNECT_COUNT[] = "nb_connections";
const char KEY_PROP_CONNECT_COUNT_LIMIT[] = "nb_connections_limit";
const char KEY_PROP_DOWNLOADED[] = "total_downloaded";
const char KEY_PROP_DOWNLOADED_SESSION[] = "total_downloaded_session";
const char KEY_PROP_UPLOADED[] = "total_uploaded";
const char KEY_PROP_UPLOADED_SESSION[] = "total_uploaded_session";
const char KEY_PROP_DL_SPEED[] = "dl_speed";
const char KEY_PROP_DL_SPEED_AVG[] = "dl_speed_avg";
const char KEY_PROP_UP_SPEED[] = "up_speed";
const char KEY_PROP_UP_SPEED_AVG[] = "up_speed_avg";
const char KEY_PROP_DL_LIMIT[] = "dl_limit";
const char KEY_PROP_UP_LIMIT[] = "up_limit";
const char KEY_PROP_WASTED[] = "total_wasted";
const char KEY_PROP_SEEDS[] = "seeds";
const char KEY_PROP_SEEDS_TOTAL[] = "seeds_total";
const char KEY_PROP_PEERS[] = "peers";
const char KEY_PROP_PEERS_TOTAL[] = "peers_total";
const char KEY_PROP_RATIO[] = "share_ratio";
const char KEY_PROP_REANNOUNCE[] = "reannounce";
const char KEY_PROP_TOTAL_SIZE[] = "total_size";
const char KEY_PROP_PIECES_NUM[] = "pieces_num";
const char KEY_PROP_PIECE_SIZE[] = "piece_size";
const char KEY_PROP_PIECES_HAVE[] = "pieces_have";
const char KEY_PROP_CREATED_BY[] = "created_by";
const char KEY_PROP_LAST_SEEN[] = "last_seen";
const char KEY_PROP_ADDITION_DATE[] = "addition_date";
const char KEY_PROP_COMPLETION_DATE[] = "completion_date";
const char KEY_PROP_CREATION_DATE[] = "creation_date";
const char KEY_PROP_SAVE_PATH[] = "save_path";
const char KEY_PROP_COMMENT[] = "comment";
// File keys
const char KEY_FILE_NAME[] = "name";
const char KEY_FILE_SIZE[] = "size";
const char KEY_FILE_PROGRESS[] = "progress";
const char KEY_FILE_PRIORITY[] = "priority";
const char KEY_FILE_IS_SEED[] = "is_seed";
const char KEY_FILE_PIECE_RANGE[] = "piece_range";
const char KEY_FILE_AVAILABILITY[] = "availability";
namespace
{
using Utils::String::parseBool;
using Utils::String::parseTriStateBool;
void applyToTorrents(const QStringList &hashes, const std::function<void (BitTorrent::TorrentHandle *torrent)> &func)
{
if ((hashes.size() == 1) && (hashes[0] == QLatin1String("all"))) {
foreach (BitTorrent::TorrentHandle *torrent, BitTorrent::Session::instance()->torrents())
func(torrent);
}
else {
for (const QString &hash : hashes) {
BitTorrent::TorrentHandle *torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (torrent)
func(torrent);
}
}
}
}
// Returns all the torrents in JSON format.
// The return value is a JSON-formatted list of dictionaries.
// The dictionary keys are:
// - "hash": Torrent hash
// - "name": Torrent name
// - "size": Torrent size
// - "progress: Torrent progress
// - "dlspeed": Torrent download speed
// - "upspeed": Torrent upload speed
// - "priority": Torrent priority (-1 if queuing is disabled)
// - "num_seeds": Torrent seeds connected to
// - "num_complete": Torrent seeds in the swarm
// - "num_leechs": Torrent leechers connected to
// - "num_incomplete": Torrent leechers in the swarm
// - "ratio": Torrent share ratio
// - "eta": Torrent ETA
// - "state": Torrent state
// - "seq_dl": Torrent sequential download state
// - "f_l_piece_prio": Torrent first last piece priority state
// - "force_start": Torrent force start state
// - "category": Torrent category
// 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 TorrentsController::infoAction()
{
const QString filter {params()["filter"]};
const QString category {params()["category"]};
const QString sortedColumn {params()["sort"]};
const bool reverse {parseBool(params()["reverse"], false)};
int limit {params()["limit"].toInt()};
int offset {params()["offset"].toInt()};
QVariantList torrentList;
TorrentFilter torrentFilter(filter, TorrentFilter::AnyHash, category);
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents()) {
if (torrentFilter.match(torrent))
torrentList.append(serialize(*torrent));
}
std::sort(torrentList.begin(), torrentList.end()
, [sortedColumn, reverse](const QVariant &torrent1, const QVariant &torrent2)
{
return reverse
? (torrent1.toMap().value(sortedColumn) > torrent2.toMap().value(sortedColumn))
: (torrent1.toMap().value(sortedColumn) < torrent2.toMap().value(sortedColumn));
});
const int size = torrentList.size();
// normalize offset
if (offset < 0)
offset = size + offset;
if ((offset >= size) || (offset < 0))
offset = 0;
// normalize limit
if (limit <= 0)
limit = -1; // unlimited
if ((limit > 0) || (offset > 0))
torrentList = torrentList.mid(offset, limit);
setResult(QJsonArray::fromVariantList(torrentList));
}
// Returns the properties for a torrent in JSON format.
// The return value is a JSON-formatted dictionary.
// The dictionary keys are:
// - "time_elapsed": Torrent elapsed time
// - "seeding_time": Torrent elapsed time while complete
// - "eta": Torrent ETA
// - "nb_connections": Torrent connection count
// - "nb_connections_limit": Torrent connection count limit
// - "total_downloaded": Total data uploaded for torrent
// - "total_downloaded_session": Total data downloaded this session
// - "total_uploaded": Total data uploaded for torrent
// - "total_uploaded_session": Total data uploaded this session
// - "dl_speed": Torrent download speed
// - "dl_speed_avg": Torrent average download speed
// - "up_speed": Torrent upload speed
// - "up_speed_avg": Torrent average upload speed
// - "dl_limit": Torrent download limit
// - "up_limit": Torrent upload limit
// - "total_wasted": Total data wasted for torrent
// - "seeds": Torrent connected seeds
// - "seeds_total": Torrent total number of seeds
// - "peers": Torrent connected peers
// - "peers_total": Torrent total number of peers
// - "share_ratio": Torrent share ratio
// - "reannounce": Torrent next reannounce time
// - "total_size": Torrent total size
// - "pieces_num": Torrent pieces count
// - "piece_size": Torrent piece size
// - "pieces_have": Torrent pieces have
// - "created_by": Torrent creator
// - "last_seen": Torrent last seen complete
// - "addition_date": Torrent addition date
// - "completion_date": Torrent completion date
// - "creation_date": Torrent creation date
// - "save_path": Torrent save path
// - "comment": Torrent comment
void TorrentsController::propertiesAction()
{
checkParams({"hash"});
const QString hash {params()["hash"]};
QVariantMap dataDict;
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
dataDict[KEY_PROP_TIME_ELAPSED] = torrent->activeTime();
dataDict[KEY_PROP_SEEDING_TIME] = torrent->seedingTime();
dataDict[KEY_PROP_ETA] = torrent->eta();
dataDict[KEY_PROP_CONNECT_COUNT] = torrent->connectionsCount();
dataDict[KEY_PROP_CONNECT_COUNT_LIMIT] = torrent->connectionsLimit();
dataDict[KEY_PROP_DOWNLOADED] = torrent->totalDownload();
dataDict[KEY_PROP_DOWNLOADED_SESSION] = torrent->totalPayloadDownload();
dataDict[KEY_PROP_UPLOADED] = torrent->totalUpload();
dataDict[KEY_PROP_UPLOADED_SESSION] = torrent->totalPayloadUpload();
dataDict[KEY_PROP_DL_SPEED] = torrent->downloadPayloadRate();
dataDict[KEY_PROP_DL_SPEED_AVG] = torrent->totalDownload() / (1 + torrent->activeTime() - torrent->finishedTime());
dataDict[KEY_PROP_UP_SPEED] = torrent->uploadPayloadRate();
dataDict[KEY_PROP_UP_SPEED_AVG] = torrent->totalUpload() / (1 + torrent->activeTime());
dataDict[KEY_PROP_DL_LIMIT] = torrent->downloadLimit() <= 0 ? -1 : torrent->downloadLimit();
dataDict[KEY_PROP_UP_LIMIT] = torrent->uploadLimit() <= 0 ? -1 : torrent->uploadLimit();
dataDict[KEY_PROP_WASTED] = torrent->wastedSize();
dataDict[KEY_PROP_SEEDS] = torrent->seedsCount();
dataDict[KEY_PROP_SEEDS_TOTAL] = torrent->totalSeedsCount();
dataDict[KEY_PROP_PEERS] = torrent->leechsCount();
dataDict[KEY_PROP_PEERS_TOTAL] = torrent->totalLeechersCount();
const qreal ratio = torrent->realRatio();
dataDict[KEY_PROP_RATIO] = ratio > BitTorrent::TorrentHandle::MAX_RATIO ? -1 : ratio;
dataDict[KEY_PROP_REANNOUNCE] = torrent->nextAnnounce();
dataDict[KEY_PROP_TOTAL_SIZE] = torrent->totalSize();
dataDict[KEY_PROP_PIECES_NUM] = torrent->piecesCount();
dataDict[KEY_PROP_PIECE_SIZE] = torrent->pieceLength();
dataDict[KEY_PROP_PIECES_HAVE] = torrent->piecesHave();
dataDict[KEY_PROP_CREATED_BY] = torrent->creator();
dataDict[KEY_PROP_ADDITION_DATE] = torrent->addedTime().toTime_t();
if (torrent->hasMetadata()) {
dataDict[KEY_PROP_LAST_SEEN] = torrent->lastSeenComplete().isValid() ? static_cast<int>(torrent->lastSeenComplete().toTime_t()) : -1;
dataDict[KEY_PROP_COMPLETION_DATE] = torrent->completedTime().isValid() ? static_cast<int>(torrent->completedTime().toTime_t()) : -1;
dataDict[KEY_PROP_CREATION_DATE] = torrent->creationDate().toTime_t();
}
else {
dataDict[KEY_PROP_LAST_SEEN] = -1;
dataDict[KEY_PROP_COMPLETION_DATE] = -1;
dataDict[KEY_PROP_CREATION_DATE] = -1;
}
dataDict[KEY_PROP_SAVE_PATH] = Utils::Fs::toNativePath(torrent->savePath());
dataDict[KEY_PROP_COMMENT] = torrent->comment();
setResult(QJsonObject::fromVariantMap(dataDict));
}
// Returns the trackers for a torrent in JSON format.
// The return value is a JSON-formatted list of dictionaries.
// The dictionary keys are:
// - "url": Tracker URL
// - "status": Tracker status
// - "num_peers": Tracker peer count
// - "msg": Tracker message (last)
void TorrentsController::trackersAction()
{
checkParams({"hash"});
const QString hash {params()["hash"]};
QVariantList trackerList;
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
QHash<QString, BitTorrent::TrackerInfo> trackersData = torrent->trackerInfos();
foreach (const BitTorrent::TrackerEntry &tracker, torrent->trackers()) {
QVariantMap trackerDict;
trackerDict[KEY_TRACKER_URL] = tracker.url();
const BitTorrent::TrackerInfo data = trackersData.value(tracker.url());
QString status;
switch (tracker.status()) {
case BitTorrent::TrackerEntry::NotContacted:
status = tr("Not contacted yet"); break;
case BitTorrent::TrackerEntry::Updating:
status = tr("Updating..."); break;
case BitTorrent::TrackerEntry::Working:
status = tr("Working"); break;
case BitTorrent::TrackerEntry::NotWorking:
status = tr("Not working"); break;
}
trackerDict[KEY_TRACKER_STATUS] = status;
trackerDict[KEY_TRACKER_PEERS] = data.numPeers;
trackerDict[KEY_TRACKER_MSG] = data.lastMessage.trimmed();
trackerList.append(trackerDict);
}
setResult(QJsonArray::fromVariantList(trackerList));
}
// Returns the web seeds for a torrent in JSON format.
// The return value is a JSON-formatted list of dictionaries.
// The dictionary keys are:
// - "url": Web seed URL
void TorrentsController::webseedsAction()
{
checkParams({"hash"});
const QString hash {params()["hash"]};
QVariantList webSeedList;
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
foreach (const QUrl &webseed, torrent->urlSeeds()) {
QVariantMap webSeedDict;
webSeedDict[KEY_WEBSEED_URL] = webseed.toString();
webSeedList.append(webSeedDict);
}
setResult(QJsonArray::fromVariantList(webSeedList));
}
// Returns the files in a torrent in JSON format.
// The return value is a JSON-formatted list of dictionaries.
// The dictionary keys are:
// - "name": File name
// - "size": File size
// - "progress": File progress
// - "priority": File priority
// - "is_seed": Flag indicating if torrent is seeding/complete
// - "piece_range": Piece index range, the first number is the starting piece index
// and the second number is the ending piece index (inclusive)
void TorrentsController::filesAction()
{
checkParams({"hash"});
const QString hash {params()["hash"]};
QVariantList fileList;
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
if (torrent->hasMetadata()) {
const QVector<int> priorities = torrent->filePriorities();
const QVector<qreal> fp = torrent->filesProgress();
const QVector<qreal> fileAvailability = torrent->availableFileFractions();
const BitTorrent::TorrentInfo info = torrent->info();
for (int i = 0; i < torrent->filesCount(); ++i) {
QVariantMap fileDict;
fileDict[KEY_FILE_PROGRESS] = fp[i];
fileDict[KEY_FILE_PRIORITY] = priorities[i];
fileDict[KEY_FILE_SIZE] = torrent->fileSize(i);
fileDict[KEY_FILE_AVAILABILITY] = fileAvailability[i];
QString fileName = torrent->filePath(i);
if (fileName.endsWith(QB_EXT, Qt::CaseInsensitive))
fileName.chop(QB_EXT.size());
fileDict[KEY_FILE_NAME] = Utils::Fs::toNativePath(fileName);
const BitTorrent::TorrentInfo::PieceRange idx = info.filePieces(i);
fileDict[KEY_FILE_PIECE_RANGE] = QVariantList {idx.first(), idx.last()};
if (i == 0)
fileDict[KEY_FILE_IS_SEED] = torrent->isSeed();
fileList.append(fileDict);
}
}
setResult(QJsonArray::fromVariantList(fileList));
}
// Returns an array of hashes (of each pieces respectively) for a torrent in JSON format.
// The return value is a JSON-formatted array of strings (hex strings).
void TorrentsController::pieceHashesAction()
{
checkParams({"hash"});
const QString hash {params()["hash"]};
QVariantList pieceHashes;
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
const QVector<QByteArray> hashes = torrent->info().pieceHashes();
pieceHashes.reserve(hashes.size());
foreach (const QByteArray &hash, hashes)
pieceHashes.append(hash.toHex());
setResult(QJsonArray::fromVariantList(pieceHashes));
}
// Returns an array of states (of each pieces respectively) for a torrent in JSON format.
// The return value is a JSON-formatted array of ints.
// 0: piece not downloaded
// 1: piece requested or downloading
// 2: piece already downloaded
void TorrentsController::pieceStatesAction()
{
checkParams({"hash"});
const QString hash {params()["hash"]};
QVariantList pieceStates;
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
const QBitArray states = torrent->pieces();
pieceStates.reserve(states.size());
for (int i = 0; i < states.size(); ++i)
pieceStates.append(static_cast<int>(states[i]) * 2);
const QBitArray dlstates = torrent->downloadingPieces();
for (int i = 0; i < states.size(); ++i) {
if (dlstates[i])
pieceStates[i] = 1;
}
setResult(QJsonArray::fromVariantList(pieceStates));
}
void TorrentsController::addAction()
{
const QString urls = params()["urls"];
const bool skipChecking = parseBool(params()["skip_checking"], false);
const bool seqDownload = parseBool(params()["sequentialDownload"], false);
const bool firstLastPiece = parseBool(params()["firstLastPiecePrio"], false);
const TriStateBool addPaused = parseTriStateBool(params()["paused"]);
const TriStateBool rootFolder = parseTriStateBool(params()["root_folder"]);
const QString savepath = params()["savepath"].trimmed();
const QString category = params()["category"].trimmed();
const QString cookie = params()["cookie"];
const QString torrentName = params()["rename"].trimmed();
const int upLimit = params()["upLimit"].toInt();
const int dlLimit = params()["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);
}
}
for (auto it = data().constBegin(); it != data().constEnd(); ++it) {
const BitTorrent::TorrentInfo torrentInfo = BitTorrent::TorrentInfo::load(it.value());
if (!torrentInfo.isValid()) {
throw APIError(APIErrorType::BadData
, tr("Error: '%1' is not a valid torrent file.").arg(it.key()));
}
partialSuccess |= BitTorrent::Session::instance()->addTorrent(torrentInfo, params);
}
if (partialSuccess)
setResult("Ok.");
else
setResult("Fails.");
}
void TorrentsController::addTrackersAction()
{
checkParams({"hash", "urls"});
const QString hash = params()["hash"];
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (torrent) {
QList<BitTorrent::TrackerEntry> trackers;
foreach (QString url, params()["urls"].split('\n')) {
url = url.trimmed();
if (!url.isEmpty())
trackers << url;
}
torrent->addTrackers(trackers);
}
}
void TorrentsController::pauseAction()
{
checkParams({"hashes"});
const QStringList hashes = params()["hashes"].split('|');
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->pause(); });
}
void TorrentsController::resumeAction()
{
checkParams({"hashes"});
const QStringList hashes = params()["hashes"].split('|');
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->resume(); });
}
void TorrentsController::filePrioAction()
{
checkParams({"hash", "id", "priority"});
const QString hash = params()["hash"];
int fileID = params()["id"].toInt();
int priority = params()["priority"].toInt();
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (torrent && torrent->hasMetadata())
torrent->setFilePriority(fileID, priority);
}
void TorrentsController::uploadLimitAction()
{
checkParams({"hashes"});
const QStringList hashes {params()["hashes"].split('|')};
QVariantMap map;
foreach (const QString &hash, hashes) {
int limit = -1;
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (torrent)
limit = torrent->uploadLimit();
map[hash] = limit;
}
setResult(QJsonObject::fromVariantMap(map));
}
void TorrentsController::downloadLimitAction()
{
checkParams({"hashes"});
const QStringList hashes {params()["hashes"].split('|')};
QVariantMap map;
foreach (const QString &hash, hashes) {
int limit = -1;
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (torrent)
limit = torrent->downloadLimit();
map[hash] = limit;
}
setResult(QJsonObject::fromVariantMap(map));
}
void TorrentsController::setUploadLimitAction()
{
checkParams({"hashes", "limit"});
qlonglong limit = params()["limit"].toLongLong();
if (limit == 0)
limit = -1;
const QStringList hashes {params()["hashes"].split('|')};
applyToTorrents(hashes, [limit](BitTorrent::TorrentHandle *torrent) { torrent->setUploadLimit(limit); });
}
void TorrentsController::setDownloadLimitAction()
{
checkParams({"hashes", "limit"});
qlonglong limit = params()["limit"].toLongLong();
if (limit == 0)
limit = -1;
const QStringList hashes {params()["hashes"].split('|')};
applyToTorrents(hashes, [limit](BitTorrent::TorrentHandle *torrent) { torrent->setDownloadLimit(limit); });
}
void TorrentsController::toggleSequentialDownloadAction()
{
checkParams({"hashes"});
const QStringList hashes {params()["hashes"].split('|')};
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->toggleSequentialDownload(); });
}
void TorrentsController::toggleFirstLastPiecePrioAction()
{
checkParams({"hashes"});
const QStringList hashes {params()["hashes"].split('|')};
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->toggleFirstLastPiecePriority(); });
}
void TorrentsController::setSuperSeedingAction()
{
checkParams({"hashes", "value"});
const bool value {parseBool(params()["value"], false)};
const QStringList hashes {params()["hashes"].split('|')};
applyToTorrents(hashes, [value](BitTorrent::TorrentHandle *torrent) { torrent->setSuperSeeding(value); });
}
void TorrentsController::setForceStartAction()
{
checkParams({"hashes", "value"});
const bool value {parseBool(params()["value"], false)};
const QStringList hashes {params()["hashes"].split('|')};
applyToTorrents(hashes, [value](BitTorrent::TorrentHandle *torrent) { torrent->resume(value); });
}
void TorrentsController::deleteAction()
{
checkParams({"hashes", "delete_files"});
const QStringList hashes {params()["hashes"].split('|')};
const bool deleteFiles {parseBool(params()["delete_files"], false)};
applyToTorrents(hashes, [deleteFiles](BitTorrent::TorrentHandle *torrent)
{
BitTorrent::Session::instance()->deleteTorrent(torrent->hash(), deleteFiles);
});
}
void TorrentsController::increasePrioAction()
{
checkParams({"hashes"});
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
throw APIError(APIErrorType::Conflict, tr("Torrent queueing must be enabled"));
const QStringList hashes {params()["hashes"].split('|')};
BitTorrent::Session::instance()->increaseTorrentsPriority(hashes);
}
void TorrentsController::decreasePrioAction()
{
checkParams({"hashes"});
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
throw APIError(APIErrorType::Conflict, tr("Torrent queueing must be enabled"));
const QStringList hashes {params()["hashes"].split('|')};
BitTorrent::Session::instance()->decreaseTorrentsPriority(hashes);
}
void TorrentsController::topPrioAction()
{
checkParams({"hashes"});
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
throw APIError(APIErrorType::Conflict, tr("Torrent queueing must be enabled"));
const QStringList hashes {params()["hashes"].split('|')};
BitTorrent::Session::instance()->topTorrentsPriority(hashes);
}
void TorrentsController::bottomPrioAction()
{
checkParams({"hashes"});
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
throw APIError(APIErrorType::Conflict, tr("Torrent queueing must be enabled"));
const QStringList hashes {params()["hashes"].split('|')};
BitTorrent::Session::instance()->bottomTorrentsPriority(hashes);
}
void TorrentsController::setLocationAction()
{
checkParams({"hashes", "location"});
const QStringList hashes {params()["hashes"].split("|")};
const QString newLocation {params()["location"].trimmed()};
// check if the location exists
if (newLocation.isEmpty() || !QDir(newLocation).exists())
return;
applyToTorrents(hashes, [newLocation](BitTorrent::TorrentHandle *torrent)
{
LogMsg(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 TorrentsController::renameAction()
{
checkParams({"hash", "name"});
const QString hash = params()["hash"];
QString name = params()["name"].trimmed();
if (name.isEmpty())
throw APIError(APIErrorType::Conflict, tr("Incorrect torrent name"));
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
name.replace(QRegularExpression("\r?\n|\r"), " ");
qDebug() << "Renaming" << torrent->name() << "to" << name;
torrent->setName(name);
}
void TorrentsController::setAutoManagementAction()
{
checkParams({"hashes", "enable"});
const QStringList hashes {params()["hashes"].split('|')};
const bool isEnabled {parseBool(params()["enable"], false)};
applyToTorrents(hashes, [isEnabled](BitTorrent::TorrentHandle *torrent)
{
torrent->setAutoTMMEnabled(isEnabled);
});
}
void TorrentsController::recheckAction()
{
checkParams({"hashes"});
const QStringList hashes {params()["hashes"].split('|')};
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->forceRecheck(); });
}
void TorrentsController::setCategoryAction()
{
checkParams({"hashes", "category"});
const QStringList hashes {params()["hashes"].split('|')};
const QString category {params()["category"].trimmed()};
applyToTorrents(hashes, [category](BitTorrent::TorrentHandle *torrent)
{
if (!torrent->setCategory(category))
throw APIError(APIErrorType::Conflict, tr("Incorrect category name"));
});
}
void TorrentsController::createCategoryAction()
{
checkParams({"category"});
const QString category {params()["category"].trimmed()};
if (!BitTorrent::Session::isValidCategoryName(category) && !category.isEmpty())
throw APIError(APIErrorType::Conflict, tr("Incorrect category name"));
BitTorrent::Session::instance()->addCategory(category);
}
void TorrentsController::removeCategoriesAction()
{
checkParams({"categories"});
const QStringList categories {params()["categories"].split('\n')};
for (const QString &category : categories)
BitTorrent::Session::instance()->removeCategory(category);
}

View File

@@ -0,0 +1,74 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include "apicontroller.h"
class TorrentsController : public APIController
{
Q_OBJECT
Q_DISABLE_COPY(TorrentsController)
public:
using APIController::APIController;
private slots:
void infoAction();
void propertiesAction();
void trackersAction();
void webseedsAction();
void filesAction();
void pieceHashesAction();
void pieceStatesAction();
void resumeAction();
void pauseAction();
void recheckAction();
void renameAction();
void setCategoryAction();
void createCategoryAction();
void removeCategoriesAction();
void addAction();
void deleteAction();
void addTrackersAction();
void filePrioAction();
void uploadLimitAction();
void downloadLimitAction();
void setUploadLimitAction();
void setDownloadLimitAction();
void increasePrioAction();
void decreasePrioAction();
void topPrioAction();
void bottomPrioAction();
void setLocationAction();
void setAutoManagementAction();
void setSuperSeedingAction();
void setForceStartAction();
void toggleSequentialDownloadAction();
void toggleFirstLastPiecePrioAction();
};

View File

@@ -0,0 +1,113 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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 "transfercontroller.h"
#include <QJsonObject>
#include "base/bittorrent/session.h"
const char KEY_TRANSFER_DLSPEED[] = "dl_info_speed";
const char KEY_TRANSFER_DLDATA[] = "dl_info_data";
const char KEY_TRANSFER_DLRATELIMIT[] = "dl_rate_limit";
const char KEY_TRANSFER_UPSPEED[] = "up_info_speed";
const char KEY_TRANSFER_UPDATA[] = "up_info_data";
const char KEY_TRANSFER_UPRATELIMIT[] = "up_rate_limit";
const char KEY_TRANSFER_DHT_NODES[] = "dht_nodes";
const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status";
// Returns the global transfer information in JSON format.
// The return value is a JSON-formatted dictionary.
// The dictionary keys are:
// - "dl_info_speed": Global download rate
// - "dl_info_data": Data downloaded this session
// - "up_info_speed": Global upload rate
// - "up_info_data": Data uploaded this session
// - "dl_rate_limit": Download rate limit
// - "up_rate_limit": Upload rate limit
// - "dht_nodes": DHT nodes connected to
// - "connection_status": Connection status
void TransferController::infoAction()
{
const BitTorrent::SessionStatus &sessionStatus = BitTorrent::Session::instance()->status();
QJsonObject dict;
dict[KEY_TRANSFER_DLSPEED] = static_cast<qint64>(sessionStatus.payloadDownloadRate);
dict[KEY_TRANSFER_DLDATA] = static_cast<qint64>(sessionStatus.totalPayloadDownload);
dict[KEY_TRANSFER_UPSPEED] = static_cast<qint64>(sessionStatus.payloadUploadRate);
dict[KEY_TRANSFER_UPDATA] = static_cast<qint64>(sessionStatus.totalPayloadUpload);
dict[KEY_TRANSFER_DLRATELIMIT] = BitTorrent::Session::instance()->downloadSpeedLimit();
dict[KEY_TRANSFER_UPRATELIMIT] = BitTorrent::Session::instance()->uploadSpeedLimit();
dict[KEY_TRANSFER_DHT_NODES] = static_cast<qint64>(sessionStatus.dhtNodes);
if (!BitTorrent::Session::instance()->isListening())
dict[KEY_TRANSFER_CONNECTION_STATUS] = QLatin1String("disconnected");
else
dict[KEY_TRANSFER_CONNECTION_STATUS] = QLatin1String(sessionStatus.hasIncomingConnections ? "connected" : "firewalled");
setResult(dict);
}
void TransferController::uploadLimitAction()
{
setResult(QString::number(BitTorrent::Session::instance()->uploadSpeedLimit()));
}
void TransferController::downloadLimitAction()
{
setResult(QString::number(BitTorrent::Session::instance()->downloadSpeedLimit()));
}
void TransferController::setUploadLimitAction()
{
checkParams({"limit"});
qlonglong limit = params()["limit"].toLongLong();
if (limit == 0) limit = -1;
BitTorrent::Session::instance()->setUploadSpeedLimit(limit);
}
void TransferController::setDownloadLimitAction()
{
checkParams({"limit"});
qlonglong limit = params()["limit"].toLongLong();
if (limit == 0) limit = -1;
BitTorrent::Session::instance()->setDownloadSpeedLimit(limit);
}
void TransferController::toggleSpeedLimitsModeAction()
{
BitTorrent::Session *const session = BitTorrent::Session::instance();
session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled());
}
void TransferController::speedLimitsModeAction()
{
setResult(QString::number(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled()));
}

View File

@@ -0,0 +1,49 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 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.
*/
#pragma once
#include "apicontroller.h"
class TransferController : public APIController
{
Q_OBJECT
Q_DISABLE_COPY(TransferController)
public:
using APIController::APIController;
private slots:
void infoAction();
void speedLimitsModeAction();
void toggleSpeedLimitsModeAction();
void uploadLimitAction();
void downloadLimitAction();
void setUploadLimitAction();
void setDownloadLimitAction();
};