mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-23 08:48:07 -06:00
Change project directory structure.
Change project directory structure according to application structure. Change 'nox' configuration option to something more meaningful 'nogui'. Rename 'Icons' folder to 'icons' (similar to other folders). Partially add 'nowebui' option support. Remove QConf project file.
This commit is contained in:
35
src/core/core.pri
Normal file
35
src/core/core.pri
Normal file
@@ -0,0 +1,35 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
unix:!macx:dbus: include(qtnotify/qtnotify.pri)
|
||||
|
||||
include(qtlibtorrent/qtlibtorrent.pri)
|
||||
include(tracker/tracker.pri)
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/misc.h \
|
||||
$$PWD/fs_utils.h \
|
||||
$$PWD/downloadthread.h \
|
||||
$$PWD/torrentpersistentdata.h \
|
||||
$$PWD/filesystemwatcher.h \
|
||||
$$PWD/scannedfoldersmodel.h \
|
||||
$$PWD/qinisettings.h \
|
||||
$$PWD/smtp.h \
|
||||
$$PWD/dnsupdater.h \
|
||||
$$PWD/logger.h \
|
||||
$$PWD/httptypes.h \
|
||||
$$PWD/httprequestparser.h \
|
||||
$$PWD/httpresponsegenerator.h \
|
||||
$$PWD/preferences.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/downloadthread.cpp \
|
||||
$$PWD/scannedfoldersmodel.cpp \
|
||||
$$PWD/torrentpersistentdata.cpp \
|
||||
$$PWD/misc.cpp \
|
||||
$$PWD/fs_utils.cpp \
|
||||
$$PWD/smtp.cpp \
|
||||
$$PWD/dnsupdater.cpp \
|
||||
$$PWD/logger.cpp \
|
||||
$$PWD/httprequestparser.cpp \
|
||||
$$PWD/httpresponsegenerator.cpp \
|
||||
$$PWD/preferences.cpp
|
||||
297
src/core/dnsupdater.cpp
Normal file
297
src/core/dnsupdater.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QDebug>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#include <QUrlQuery>
|
||||
#endif
|
||||
#include "dnsupdater.h"
|
||||
#include "logger.h"
|
||||
|
||||
DNSUpdater::DNSUpdater(QObject *parent) :
|
||||
QObject(parent), m_state(OK), m_service(DNS::NONE)
|
||||
{
|
||||
updateCredentials();
|
||||
|
||||
// Load saved settings from previous session
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
m_lastIPCheckTime = pref->getDNSLastUpd();
|
||||
m_lastIP = QHostAddress(pref->getDNSLastIP());
|
||||
|
||||
// Start IP checking timer
|
||||
m_ipCheckTimer.setInterval(IP_CHECK_INTERVAL_MS);
|
||||
connect(&m_ipCheckTimer, SIGNAL(timeout()), SLOT(checkPublicIP()));
|
||||
m_ipCheckTimer.start();
|
||||
|
||||
// Check lastUpdate to avoid flooding
|
||||
if (!m_lastIPCheckTime.isValid() ||
|
||||
m_lastIPCheckTime.secsTo(QDateTime::currentDateTime())*1000 > IP_CHECK_INTERVAL_MS) {
|
||||
checkPublicIP();
|
||||
}
|
||||
}
|
||||
|
||||
DNSUpdater::~DNSUpdater() {
|
||||
// Save lastupdate time and last ip
|
||||
Preferences* const pref = Preferences::instance();
|
||||
pref->setDNSLastUpd(m_lastIPCheckTime);
|
||||
pref->setDNSLastIP(m_lastIP.toString());
|
||||
}
|
||||
|
||||
void DNSUpdater::checkPublicIP()
|
||||
{
|
||||
Q_ASSERT(m_state == OK);
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply*)),
|
||||
SLOT(ipRequestFinished(QNetworkReply*)));
|
||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl("http://checkip.dyndns.org"));
|
||||
request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org");
|
||||
manager->get(request);
|
||||
}
|
||||
|
||||
void DNSUpdater::ipRequestFinished(QNetworkReply *reply)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
if (reply->error()) {
|
||||
// Error
|
||||
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
|
||||
} else {
|
||||
// Parse response
|
||||
QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>");
|
||||
QString ret = reply->readAll();
|
||||
if (ipregex.indexIn(ret) >= 0) {
|
||||
QString ip_str = ipregex.cap(1);
|
||||
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ip_str;
|
||||
QHostAddress new_ip(ip_str);
|
||||
if (!new_ip.isNull()) {
|
||||
if (m_lastIP != new_ip) {
|
||||
qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS...";
|
||||
qDebug() << m_lastIP.toString() << "->" << new_ip.toString();
|
||||
m_lastIP = new_ip;
|
||||
updateDNSService();
|
||||
}
|
||||
} else {
|
||||
qWarning() << Q_FUNC_INFO << "Failed to construct a QHostAddress from the IP string";
|
||||
}
|
||||
} else {
|
||||
qWarning() << Q_FUNC_INFO << "Regular expression failed ot capture the IP address";
|
||||
}
|
||||
}
|
||||
// Clean up
|
||||
reply->deleteLater();
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void DNSUpdater::updateDNSService()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
// Prepare request
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply*)),
|
||||
SLOT(ipUpdateFinished(QNetworkReply*)));
|
||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||
QNetworkRequest request;
|
||||
request.setUrl(getUpdateUrl());
|
||||
request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org");
|
||||
manager->get(request);
|
||||
}
|
||||
|
||||
QUrl DNSUpdater::getUpdateUrl() const
|
||||
{
|
||||
QUrl url;
|
||||
#ifdef QT_NO_OPENSSL
|
||||
url.setScheme("http");
|
||||
#else
|
||||
url.setScheme("https");
|
||||
#endif
|
||||
url.setUserName(m_username);
|
||||
url.setPassword(m_password);
|
||||
|
||||
Q_ASSERT(!m_lastIP.isNull());
|
||||
// Service specific
|
||||
switch(m_service) {
|
||||
case DNS::DYNDNS:
|
||||
url.setHost("members.dyndns.org");
|
||||
break;
|
||||
case DNS::NOIP:
|
||||
url.setHost("dynupdate.no-ip.com");
|
||||
break;
|
||||
default:
|
||||
qWarning() << "Unrecognized Dynamic DNS service!";
|
||||
Q_ASSERT(0);
|
||||
}
|
||||
url.setPath("/nic/update");
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
url.addQueryItem("hostname", m_domain);
|
||||
url.addQueryItem("myip", m_lastIP.toString());
|
||||
#else
|
||||
QUrlQuery urlQuery(url);
|
||||
urlQuery.addQueryItem("hostname", m_domain);
|
||||
urlQuery.addQueryItem("myip", m_lastIP.toString());
|
||||
url.setQuery(urlQuery);
|
||||
#endif
|
||||
Q_ASSERT(url.isValid());
|
||||
|
||||
qDebug() << Q_FUNC_INFO << url.toString();
|
||||
return url;
|
||||
}
|
||||
|
||||
void DNSUpdater::ipUpdateFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error()) {
|
||||
// Error
|
||||
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
|
||||
} else {
|
||||
// Pase reply
|
||||
processIPUpdateReply(reply->readAll());
|
||||
}
|
||||
// Clean up
|
||||
reply->deleteLater();
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void DNSUpdater::processIPUpdateReply(const QString &reply)
|
||||
{
|
||||
Logger* const logger = Logger::instance();
|
||||
qDebug() << Q_FUNC_INFO << reply;
|
||||
QString code = reply.split(" ").first();
|
||||
qDebug() << Q_FUNC_INFO << "Code:" << code;
|
||||
if (code == "good" || code == "nochg") {
|
||||
logger->addMessage(tr("Your dynamic DNS was successfully updated."), Log::INFO);
|
||||
return;
|
||||
}
|
||||
if (code == "911" || code == "dnserr") {
|
||||
logger->addMessage(tr("Dynamic DNS error: The service is temporarily unavailable, it will be retried in 30 minutes."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
// It will retry in 30 minutes because the timer was not stopped
|
||||
return;
|
||||
}
|
||||
// Everything bellow is an error, stop updating until the user updates something
|
||||
m_ipCheckTimer.stop();
|
||||
m_lastIP.clear();
|
||||
if (code == "nohost") {
|
||||
logger->addMessage(tr("Dynamic DNS error: hostname supplied does not exist under specified account."), Log::CRITICAL);
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
if (code == "badauth") {
|
||||
logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL);
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
if (code == "badagent") {
|
||||
logger->addMessage(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please report a bug at http://bugs.qbittorrent.org."),
|
||||
Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
if (code == "!donator") {
|
||||
logger->addMessage(tr("Dynamic DNS error: %1 was returned by the service, please report a bug at http://bugs.qbittorrent.org.").arg("!donator"),
|
||||
Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
if (code == "abuse") {
|
||||
logger->addMessage(tr("Dynamic DNS error: Your username was blocked due to abuse."), Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DNSUpdater::updateCredentials()
|
||||
{
|
||||
if (m_state == FATAL) return;
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Logger* const logger = Logger::instance();
|
||||
bool change = false;
|
||||
// Get DNS service information
|
||||
if (m_service != pref->getDynDNSService()) {
|
||||
m_service = pref->getDynDNSService();
|
||||
change = true;
|
||||
}
|
||||
if (m_domain != pref->getDynDomainName()) {
|
||||
m_domain = pref->getDynDomainName();
|
||||
QRegExp domain_regex("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$");
|
||||
if (domain_regex.indexIn(m_domain) < 0) {
|
||||
logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
change = true;
|
||||
}
|
||||
if (m_username != pref->getDynDNSUsername()) {
|
||||
m_username = pref->getDynDNSUsername();
|
||||
if (m_username.length() < 4) {
|
||||
logger->addMessage(tr("Dynamic DNS error: supplied username is too short."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
change = true;
|
||||
}
|
||||
if (m_password != pref->getDynDNSPassword()) {
|
||||
m_password = pref->getDynDNSPassword();
|
||||
if (m_password.length() < 4) {
|
||||
logger->addMessage(tr("Dynamic DNS error: supplied password is too short."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
change = true;
|
||||
}
|
||||
|
||||
if (m_state == INVALID_CREDS && change) {
|
||||
m_state = OK; // Try again
|
||||
m_ipCheckTimer.start();
|
||||
checkPublicIP();
|
||||
}
|
||||
}
|
||||
|
||||
QUrl DNSUpdater::getRegistrationUrl(int service)
|
||||
{
|
||||
switch(service) {
|
||||
case DNS::DYNDNS:
|
||||
return QUrl("https://www.dyndns.com/account/services/hosts/add.html");
|
||||
case DNS::NOIP:
|
||||
return QUrl("http://www.no-ip.com/services/managed_dns/free_dynamic_dns.html");
|
||||
default:
|
||||
Q_ASSERT(0);
|
||||
}
|
||||
return QUrl();
|
||||
}
|
||||
81
src/core/dnsupdater.h
Normal file
81
src/core/dnsupdater.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef DNSUPDATER_H
|
||||
#define DNSUPDATER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkReply>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
#include "preferences.h"
|
||||
|
||||
/*!
|
||||
* Based on http://www.dyndns.com/developers/specs/
|
||||
*/
|
||||
class DNSUpdater : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DNSUpdater(QObject *parent = 0);
|
||||
~DNSUpdater();
|
||||
static QUrl getRegistrationUrl(int service);
|
||||
|
||||
public slots:
|
||||
void updateCredentials();
|
||||
|
||||
private slots:
|
||||
void checkPublicIP();
|
||||
void ipRequestFinished(QNetworkReply* reply);
|
||||
void updateDNSService();
|
||||
void ipUpdateFinished(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
QUrl getUpdateUrl() const;
|
||||
void processIPUpdateReply(const QString &reply);
|
||||
|
||||
private:
|
||||
QHostAddress m_lastIP;
|
||||
QDateTime m_lastIPCheckTime;
|
||||
QTimer m_ipCheckTimer;
|
||||
int m_state;
|
||||
// Service creds
|
||||
DNS::Service m_service;
|
||||
QString m_domain;
|
||||
QString m_username;
|
||||
QString m_password;
|
||||
|
||||
private:
|
||||
static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min
|
||||
enum State { OK, INVALID_CREDS, FATAL };
|
||||
};
|
||||
|
||||
#endif // DNSUPDATER_H
|
||||
312
src/core/downloadthread.cpp
Normal file
312
src/core/downloadthread.cpp
Normal file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QTemporaryFile>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkProxy>
|
||||
#include <QNetworkCookieJar>
|
||||
#include <QDebug>
|
||||
|
||||
#include "downloadthread.h"
|
||||
#include "preferences.h"
|
||||
#include "qinisettings.h"
|
||||
#include "fs_utils.h"
|
||||
#include <zlib.h>
|
||||
|
||||
/** Download Thread **/
|
||||
|
||||
DownloadThread::DownloadThread(QObject* parent) : QObject(parent) {
|
||||
connect(&m_networkManager, SIGNAL(finished (QNetworkReply*)), this, SLOT(processDlFinished(QNetworkReply*)));
|
||||
#ifndef QT_NO_OPENSSL
|
||||
connect(&m_networkManager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(ignoreSslErrors(QNetworkReply*,QList<QSslError>)));
|
||||
#endif
|
||||
}
|
||||
|
||||
QByteArray DownloadThread::gUncompress(Bytef *inData, size_t len) {
|
||||
if (len <= 4) {
|
||||
qWarning("gUncompress: Input data is truncated");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray result;
|
||||
|
||||
z_stream strm;
|
||||
static const int CHUNK_SIZE = 1024;
|
||||
char out[CHUNK_SIZE];
|
||||
|
||||
/* allocate inflate state */
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = len;
|
||||
strm.next_in = inData;
|
||||
|
||||
const int windowBits = 15;
|
||||
const int ENABLE_ZLIB_GZIP = 32;
|
||||
|
||||
int ret = inflateInit2(&strm, windowBits|ENABLE_ZLIB_GZIP ); // gzip decoding
|
||||
if (ret != Z_OK)
|
||||
return QByteArray();
|
||||
|
||||
// run inflate()
|
||||
do {
|
||||
strm.avail_out = CHUNK_SIZE;
|
||||
strm.next_out = reinterpret_cast<unsigned char*>(out);
|
||||
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
Q_ASSERT(ret != Z_STREAM_ERROR); // state not clobbered
|
||||
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
(void) inflateEnd(&strm);
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
result.append(out, CHUNK_SIZE - strm.avail_out);
|
||||
} while (!strm.avail_out);
|
||||
|
||||
// clean up and return
|
||||
inflateEnd(&strm);
|
||||
return result;
|
||||
}
|
||||
|
||||
void DownloadThread::processDlFinished(QNetworkReply* reply) {
|
||||
QString url = reply->url().toString();
|
||||
qDebug("Download finished: %s", qPrintable(url));
|
||||
// Check if the request was successful
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
// Failure
|
||||
qDebug("Download failure (%s), reason: %s", qPrintable(url), qPrintable(errorCodeToString(reply->error())));
|
||||
emit downloadFailure(url, errorCodeToString(reply->error()));
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
// Check if the server ask us to redirect somewhere lese
|
||||
const QVariant redirection = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if (redirection.isValid()) {
|
||||
// We should redirect
|
||||
QUrl newUrl = redirection.toUrl();
|
||||
// Resolve relative urls
|
||||
if (newUrl.isRelative())
|
||||
newUrl = reply->url().resolved(newUrl);
|
||||
const QString newUrlString = newUrl.toString();
|
||||
qDebug("Redirecting from %s to %s", qPrintable(url), qPrintable(newUrlString));
|
||||
// Redirect to magnet workaround
|
||||
if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) {
|
||||
qDebug("Magnet redirect detected.");
|
||||
reply->abort();
|
||||
emit magnetRedirect(newUrlString, url);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
m_redirectMapping.insert(newUrlString, url);
|
||||
// redirecting with first cookies
|
||||
downloadUrl(newUrlString, m_networkManager.cookieJar()->cookiesForUrl(url));
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
// Checking if it was redirected, restoring initial URL
|
||||
if (m_redirectMapping.contains(url)) {
|
||||
url = m_redirectMapping.take(url);
|
||||
}
|
||||
// Success
|
||||
QTemporaryFile *tmpfile = new QTemporaryFile;
|
||||
if (tmpfile->open()) {
|
||||
tmpfile->setAutoRemove(false);
|
||||
QString filePath = tmpfile->fileName();
|
||||
qDebug("Temporary filename is: %s", qPrintable(filePath));
|
||||
if (reply->isOpen() || reply->open(QIODevice::ReadOnly)) {
|
||||
QByteArray replyData = reply->readAll();
|
||||
if (reply->rawHeader("Content-Encoding") == "gzip") {
|
||||
// uncompress gzip reply
|
||||
replyData = gUncompress(reinterpret_cast<unsigned char*>(replyData.data()), replyData.length());
|
||||
}
|
||||
tmpfile->write(replyData);
|
||||
tmpfile->close();
|
||||
// XXX: tmpfile needs to be deleted on Windows before using the file
|
||||
// or it will complain that the file is used by another process.
|
||||
delete tmpfile;
|
||||
// Send finished signal
|
||||
emit downloadFinished(url, filePath);
|
||||
} else {
|
||||
delete tmpfile;
|
||||
fsutils::forceRemove(filePath);
|
||||
// Error when reading the request
|
||||
emit downloadFailure(url, tr("I/O Error"));
|
||||
}
|
||||
} else {
|
||||
delete tmpfile;
|
||||
emit downloadFailure(url, tr("I/O Error"));
|
||||
}
|
||||
// Clean up
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void DownloadThread::downloadTorrentUrl(const QString &url, const QList<QNetworkCookie>& cookies)
|
||||
{
|
||||
// Process request
|
||||
QNetworkReply *reply = downloadUrl(url, cookies);
|
||||
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(checkDownloadSize(qint64,qint64)));
|
||||
}
|
||||
|
||||
QNetworkReply* DownloadThread::downloadUrl(const QString &url, const QList<QNetworkCookie>& cookies) {
|
||||
// Update proxy settings
|
||||
applyProxySettings();
|
||||
// Set cookies
|
||||
if (!cookies.empty()) {
|
||||
qDebug("Setting %d cookies for url: %s", cookies.size(), qPrintable(url));
|
||||
m_networkManager.cookieJar()->setCookiesFromUrl(cookies, url);
|
||||
}
|
||||
// Process download request
|
||||
qDebug("url is %s", qPrintable(url));
|
||||
const QUrl qurl = QUrl::fromEncoded(url.toUtf8());
|
||||
QNetworkRequest request(qurl);
|
||||
// Spoof Firefox 3.5 user agent to avoid
|
||||
// Web server banning
|
||||
request.setRawHeader("User-Agent", "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5");
|
||||
qDebug("Downloading %s...", request.url().toEncoded().data());
|
||||
qDebug("%d cookies for this URL", m_networkManager.cookieJar()->cookiesForUrl(url).size());
|
||||
for (int i=0; i<m_networkManager.cookieJar()->cookiesForUrl(url).size(); ++i) {
|
||||
qDebug("%s=%s", m_networkManager.cookieJar()->cookiesForUrl(url).at(i).name().data(), m_networkManager.cookieJar()->cookiesForUrl(url).at(i).value().data());
|
||||
qDebug("Domain: %s, Path: %s", qPrintable(m_networkManager.cookieJar()->cookiesForUrl(url).at(i).domain()), qPrintable(m_networkManager.cookieJar()->cookiesForUrl(url).at(i).path()));
|
||||
}
|
||||
// accept gzip
|
||||
request.setRawHeader("Accept-Encoding", "gzip");
|
||||
return m_networkManager.get(request);
|
||||
}
|
||||
|
||||
void DownloadThread::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal) {
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply) return;
|
||||
if (bytesTotal > 0) {
|
||||
// Total number of bytes is available
|
||||
if (bytesTotal > 1048576*10) {
|
||||
// More than 10MB, this is probably not a torrent file, aborting...
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
} else {
|
||||
disconnect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(checkDownloadSize(qint64,qint64)));
|
||||
}
|
||||
} else {
|
||||
if (bytesReceived > 1048576*10) {
|
||||
// More than 10MB, this is probably not a torrent file, aborting...
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadThread::applyProxySettings() {
|
||||
QNetworkProxy proxy;
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
if (pref->isProxyEnabled()) {
|
||||
// Proxy enabled
|
||||
proxy.setHostName(pref->getProxyIp());
|
||||
proxy.setPort(pref->getProxyPort());
|
||||
// Default proxy type is HTTP, we must change if it is SOCKS5
|
||||
const int proxy_type = pref->getProxyType();
|
||||
if (proxy_type == Proxy::SOCKS5 || proxy_type == Proxy::SOCKS5_PW) {
|
||||
qDebug() << Q_FUNC_INFO << "using SOCKS proxy";
|
||||
proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||
} else {
|
||||
qDebug() << Q_FUNC_INFO << "using HTTP proxy";
|
||||
proxy.setType(QNetworkProxy::HttpProxy);
|
||||
}
|
||||
// Authentication?
|
||||
if (pref->isProxyAuthEnabled()) {
|
||||
qDebug("Proxy requires authentication, authenticating");
|
||||
proxy.setUser(pref->getProxyUsername());
|
||||
proxy.setPassword(pref->getProxyPassword());
|
||||
}
|
||||
} else {
|
||||
proxy.setType(QNetworkProxy::NoProxy);
|
||||
}
|
||||
m_networkManager.setProxy(proxy);
|
||||
}
|
||||
|
||||
QString DownloadThread::errorCodeToString(QNetworkReply::NetworkError status) {
|
||||
switch(status) {
|
||||
case QNetworkReply::HostNotFoundError:
|
||||
return tr("The remote host name was not found (invalid hostname)");
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return tr("The operation was canceled");
|
||||
case QNetworkReply::RemoteHostClosedError:
|
||||
return tr("The remote server closed the connection prematurely, before the entire reply was received and processed");
|
||||
case QNetworkReply::TimeoutError:
|
||||
return tr("The connection to the remote server timed out");
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
return tr("SSL/TLS handshake failed");
|
||||
case QNetworkReply::ConnectionRefusedError:
|
||||
return tr("The remote server refused the connection");
|
||||
case QNetworkReply::ProxyConnectionRefusedError:
|
||||
return tr("The connection to the proxy server was refused");
|
||||
case QNetworkReply::ProxyConnectionClosedError:
|
||||
return tr("The proxy server closed the connection prematurely");
|
||||
case QNetworkReply::ProxyNotFoundError:
|
||||
return tr("The proxy host name was not found");
|
||||
case QNetworkReply::ProxyTimeoutError:
|
||||
return tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent");
|
||||
case QNetworkReply::ProxyAuthenticationRequiredError:
|
||||
return tr("The proxy requires authentication in order to honour the request but did not accept any credentials offered");
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
return tr("The access to the remote content was denied (401)");
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
return tr("The operation requested on the remote content is not permitted");
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
return tr("The remote content was not found at the server (404)");
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted");
|
||||
case QNetworkReply::ProtocolUnknownError:
|
||||
return tr("The Network Access API cannot honor the request because the protocol is not known");
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
return tr("The requested operation is invalid for this protocol");
|
||||
case QNetworkReply::UnknownNetworkError:
|
||||
return tr("An unknown network-related error was detected");
|
||||
case QNetworkReply::UnknownProxyError:
|
||||
return tr("An unknown proxy-related error was detected");
|
||||
case QNetworkReply::UnknownContentError:
|
||||
return tr("An unknown error related to the remote content was detected");
|
||||
case QNetworkReply::ProtocolFailure:
|
||||
return tr("A breakdown in protocol was detected");
|
||||
default:
|
||||
return tr("Unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
void DownloadThread::ignoreSslErrors(QNetworkReply* reply, const QList<QSslError> &errors) {
|
||||
Q_UNUSED(errors)
|
||||
// Ignore all SSL errors
|
||||
reply->ignoreSslErrors();
|
||||
}
|
||||
#endif
|
||||
77
src/core/downloadthread.h
Normal file
77
src/core/downloadthread.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef DOWNLOADTHREAD_H
|
||||
#define DOWNLOADTHREAD_H
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkCookie>
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QSslError>
|
||||
#include <zlib.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QNetworkAccessManager;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class DownloadThread : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DownloadThread(QObject* parent = 0);
|
||||
QNetworkReply* downloadUrl(const QString &url, const QList<QNetworkCookie>& cookies = QList<QNetworkCookie>());
|
||||
void downloadTorrentUrl(const QString &url, const QList<QNetworkCookie>& cookies = QList<QNetworkCookie>());
|
||||
//void setProxy(QString IP, int port, QString username, QString password);
|
||||
|
||||
signals:
|
||||
void downloadFinished(const QString &url, const QString &file_path);
|
||||
void downloadFailure(const QString &url, const QString &reason);
|
||||
void magnetRedirect(const QString &url_new, const QString &url_old);
|
||||
|
||||
private slots:
|
||||
void processDlFinished(QNetworkReply* reply);
|
||||
void checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal);
|
||||
#ifndef QT_NO_OPENSSL
|
||||
void ignoreSslErrors(QNetworkReply*,const QList<QSslError>&);
|
||||
#endif
|
||||
|
||||
private:
|
||||
static QByteArray gUncompress(Bytef *inData, size_t len);
|
||||
QString errorCodeToString(QNetworkReply::NetworkError status);
|
||||
void applyProxySettings();
|
||||
|
||||
private:
|
||||
QNetworkAccessManager m_networkManager;
|
||||
QHash<QString, QString> m_redirectMapping;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
297
src/core/filesystemwatcher.h
Normal file
297
src/core/filesystemwatcher.h
Normal file
@@ -0,0 +1,297 @@
|
||||
#ifndef FILESYSTEMWATCHER_H
|
||||
#define FILESYSTEMWATCHER_H
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QDir>
|
||||
#include <QTimer>
|
||||
#include <QPointer>
|
||||
#include <QStringList>
|
||||
#include <QHash>
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#include <QSet>
|
||||
#include <iostream>
|
||||
#include <errno.h>
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
||||
#include <sys/param.h>
|
||||
#include <sys/mount.h>
|
||||
#include <string.h>
|
||||
#elif !defined Q_OS_HAIKU
|
||||
#include <sys/vfs.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "fs_utils.h"
|
||||
#include "misc.h"
|
||||
|
||||
#ifndef CIFS_MAGIC_NUMBER
|
||||
#define CIFS_MAGIC_NUMBER 0xFF534D42
|
||||
#endif
|
||||
|
||||
#ifndef NFS_SUPER_MAGIC
|
||||
#define NFS_SUPER_MAGIC 0x6969
|
||||
#endif
|
||||
|
||||
#ifndef SMB_SUPER_MAGIC
|
||||
#define SMB_SUPER_MAGIC 0x517B
|
||||
#endif
|
||||
|
||||
const int WATCH_INTERVAL = 10000; // 10 sec
|
||||
const int MAX_PARTIAL_RETRIES = 5;
|
||||
|
||||
/*
|
||||
* Subclassing QFileSystemWatcher in order to support Network File
|
||||
* System watching (NFS, CIFS) on Linux and Mac OS.
|
||||
*/
|
||||
class FileSystemWatcher: public QFileSystemWatcher {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
#ifndef Q_OS_WIN
|
||||
QList<QDir> watched_folders;
|
||||
QPointer<QTimer> watch_timer;
|
||||
#endif
|
||||
QStringList m_filters;
|
||||
// Partial torrents
|
||||
QHash<QString, int> m_partialTorrents;
|
||||
QPointer<QTimer> m_partialTorrentTimer;
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
private:
|
||||
static bool isNetworkFileSystem(QString path) {
|
||||
QString file = path;
|
||||
if (!file.endsWith("/"))
|
||||
file += "/";
|
||||
file += ".";
|
||||
struct statfs buf;
|
||||
if (!statfs(file.toLocal8Bit().constData(), &buf)) {
|
||||
#ifdef Q_OS_MAC
|
||||
// XXX: should we make sure HAVE_STRUCT_FSSTAT_F_FSTYPENAME is defined?
|
||||
return (strcmp(buf.f_fstypename, "nfs") == 0 || strcmp(buf.f_fstypename, "cifs") == 0 || strcmp(buf.f_fstypename, "smbfs") == 0);
|
||||
#else
|
||||
return (buf.f_type == (long)CIFS_MAGIC_NUMBER || buf.f_type == (long)NFS_SUPER_MAGIC || buf.f_type == (long)SMB_SUPER_MAGIC);
|
||||
#endif
|
||||
} else {
|
||||
std::cerr << "Error: statfs() call failed for " << qPrintable(file) << ". Supposing it is a local folder..." << std::endl;
|
||||
switch(errno) {
|
||||
case EACCES:
|
||||
std::cerr << "Search permission is denied for a component of the path prefix of the path" << std::endl;
|
||||
break;
|
||||
case EFAULT:
|
||||
std::cerr << "Buf or path points to an invalid address" << std::endl;
|
||||
break;
|
||||
case EINTR:
|
||||
std::cerr << "This call was interrupted by a signal" << std::endl;
|
||||
break;
|
||||
case EIO:
|
||||
std::cerr << "I/O Error" << std::endl;
|
||||
break;
|
||||
case ELOOP:
|
||||
std::cerr << "Too many symlinks" << std::endl;
|
||||
break;
|
||||
case ENAMETOOLONG:
|
||||
std::cerr << "path is too long" << std::endl;
|
||||
break;
|
||||
case ENOENT:
|
||||
std::cerr << "The file referred by path does not exist" << std::endl;
|
||||
break;
|
||||
case ENOMEM:
|
||||
std::cerr << "Insufficient kernel memory" << std::endl;
|
||||
break;
|
||||
case ENOSYS:
|
||||
std::cerr << "The file system does not detect this call" << std::endl;
|
||||
break;
|
||||
case ENOTDIR:
|
||||
std::cerr << "A component of the path is not a directory" << std::endl;
|
||||
break;
|
||||
case EOVERFLOW:
|
||||
std::cerr << "Some values were too large to be represented in the struct" << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Unknown error" << std::endl;
|
||||
}
|
||||
std::cerr << "Errno: " << errno << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
FileSystemWatcher(QObject *parent): QFileSystemWatcher(parent) {
|
||||
m_filters << "*.torrent" << "*.magnet";
|
||||
connect(this, SIGNAL(directoryChanged(QString)), this, SLOT(scanLocalFolder(QString)));
|
||||
}
|
||||
|
||||
~FileSystemWatcher() {
|
||||
#ifndef Q_OS_WIN
|
||||
if (watch_timer)
|
||||
delete watch_timer;
|
||||
#endif
|
||||
if (m_partialTorrentTimer)
|
||||
delete m_partialTorrentTimer;
|
||||
}
|
||||
|
||||
QStringList directories() const {
|
||||
QStringList dirs;
|
||||
#ifndef Q_OS_WIN
|
||||
if (watch_timer) {
|
||||
foreach (const QDir &dir, watched_folders)
|
||||
dirs << dir.canonicalPath();
|
||||
}
|
||||
#endif
|
||||
dirs << QFileSystemWatcher::directories();
|
||||
return dirs;
|
||||
}
|
||||
|
||||
void addPath(const QString & path) {
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
return;
|
||||
// Check if the path points to a network file system or not
|
||||
if (isNetworkFileSystem(path)) {
|
||||
// Network mode
|
||||
qDebug("Network folder detected: %s", qPrintable(path));
|
||||
qDebug("Using file polling mode instead of inotify...");
|
||||
watched_folders << dir;
|
||||
// Set up the watch timer
|
||||
if (!watch_timer) {
|
||||
watch_timer = new QTimer(this);
|
||||
connect(watch_timer, SIGNAL(timeout()), this, SLOT(scanNetworkFolders()));
|
||||
watch_timer->start(WATCH_INTERVAL); // 5 sec
|
||||
}
|
||||
} else {
|
||||
#endif
|
||||
// Normal mode
|
||||
qDebug("FS Watching is watching %s in normal mode", qPrintable(path));
|
||||
QFileSystemWatcher::addPath(path);
|
||||
scanLocalFolder(path);
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void removePath(const QString & path) {
|
||||
#ifndef Q_OS_WIN
|
||||
QDir dir(path);
|
||||
for (int i = 0; i < watched_folders.count(); ++i) {
|
||||
if (QDir(watched_folders.at(i)) == dir) {
|
||||
watched_folders.removeAt(i);
|
||||
if (watched_folders.isEmpty())
|
||||
delete watch_timer;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Normal mode
|
||||
QFileSystemWatcher::removePath(path);
|
||||
}
|
||||
|
||||
protected slots:
|
||||
void scanLocalFolder(QString path) {
|
||||
qDebug("scanLocalFolder(%s) called", qPrintable(path));
|
||||
QStringList torrents;
|
||||
// Local folders scan
|
||||
addTorrentsFromDir(QDir(path), torrents);
|
||||
// Report detected torrent files
|
||||
if (!torrents.empty()) {
|
||||
qDebug("The following files are being reported: %s", qPrintable(torrents.join("\n")));
|
||||
emit torrentsAdded(torrents);
|
||||
}
|
||||
}
|
||||
|
||||
void scanNetworkFolders() {
|
||||
#ifndef Q_OS_WIN
|
||||
qDebug("scanNetworkFolders() called");
|
||||
QStringList torrents;
|
||||
// Network folders scan
|
||||
foreach (const QDir &dir, watched_folders) {
|
||||
//qDebug("FSWatcher: Polling manually folder %s", qPrintable(dir.path()));
|
||||
addTorrentsFromDir(dir, torrents);
|
||||
}
|
||||
// Report detected torrent files
|
||||
if (!torrents.empty()) {
|
||||
qDebug("The following files are being reported: %s", qPrintable(torrents.join("\n")));
|
||||
emit torrentsAdded(torrents);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void processPartialTorrents() {
|
||||
QStringList no_longer_partial;
|
||||
|
||||
// Check which torrents are still partial
|
||||
foreach (const QString& torrent_path, m_partialTorrents.keys()) {
|
||||
if (!QFile::exists(torrent_path)) {
|
||||
m_partialTorrents.remove(torrent_path);
|
||||
continue;
|
||||
}
|
||||
if (fsutils::isValidTorrentFile(torrent_path)) {
|
||||
no_longer_partial << torrent_path;
|
||||
m_partialTorrents.remove(torrent_path);
|
||||
} else {
|
||||
if (m_partialTorrents[torrent_path] >= MAX_PARTIAL_RETRIES) {
|
||||
m_partialTorrents.remove(torrent_path);
|
||||
QFile::rename(torrent_path, torrent_path+".invalid");
|
||||
} else {
|
||||
m_partialTorrents[torrent_path]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the partial timer if necessary
|
||||
if (m_partialTorrents.empty()) {
|
||||
m_partialTorrentTimer->stop();
|
||||
m_partialTorrentTimer->deleteLater();
|
||||
qDebug("No longer any partial torrent.");
|
||||
} else {
|
||||
qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count());
|
||||
m_partialTorrentTimer->start(WATCH_INTERVAL);
|
||||
}
|
||||
// Notify of new torrents
|
||||
if (!no_longer_partial.isEmpty())
|
||||
emit torrentsAdded(no_longer_partial);
|
||||
}
|
||||
|
||||
signals:
|
||||
void torrentsAdded(QStringList &pathList);
|
||||
|
||||
private:
|
||||
void startPartialTorrentTimer() {
|
||||
Q_ASSERT(!m_partialTorrents.isEmpty());
|
||||
if (!m_partialTorrentTimer) {
|
||||
m_partialTorrentTimer = new QTimer();
|
||||
connect(m_partialTorrentTimer, SIGNAL(timeout()), SLOT(processPartialTorrents()));
|
||||
m_partialTorrentTimer->setSingleShot(true);
|
||||
m_partialTorrentTimer->start(WATCH_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
void addTorrentsFromDir(const QDir &dir, QStringList &torrents) {
|
||||
const QStringList files = dir.entryList(m_filters, QDir::Files, QDir::Unsorted);
|
||||
foreach (const QString &file, files) {
|
||||
const QString file_abspath = dir.absoluteFilePath(file);
|
||||
if (file_abspath.endsWith(".magnet")) {
|
||||
QFile f(file_abspath);
|
||||
if (f.open(QIODevice::ReadOnly)
|
||||
&& !misc::magnetUriToHash(QString::fromLocal8Bit(f.readAll())).isEmpty()) {
|
||||
torrents << file_abspath;
|
||||
}
|
||||
} else if (fsutils::isValidTorrentFile(file_abspath)) {
|
||||
torrents << file_abspath;
|
||||
} else {
|
||||
if (!m_partialTorrents.contains(file_abspath)) {
|
||||
qDebug("Partial torrent detected at: %s", qPrintable(file_abspath));
|
||||
qDebug("Delay the file's processing...");
|
||||
m_partialTorrents.insert(file_abspath, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!m_partialTorrents.empty())
|
||||
startPartialTorrentTimer();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // FILESYSTEMWATCHER_H
|
||||
531
src/core/fs_utils.cpp
Normal file
531
src/core/fs_utils.cpp
Normal file
@@ -0,0 +1,531 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2012 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "fs_utils.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
#ifdef DISABLE_GUI
|
||||
#include <QCoreApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <Carbon/Carbon.h>
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
||||
#include <sys/param.h>
|
||||
#include <sys/mount.h>
|
||||
#elif defined(Q_OS_HAIKU)
|
||||
#include <kernel/fs_info.h>
|
||||
#else
|
||||
#include <sys/vfs.h>
|
||||
#endif
|
||||
#else
|
||||
#include <shlobj.h>
|
||||
#include <winbase.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
#include <QDesktopServices>
|
||||
#else
|
||||
#include <QStandardPaths>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using namespace libtorrent;
|
||||
|
||||
/**
|
||||
* Converts a path to a string suitable for display.
|
||||
* This function makes sure the directory separator used is consistent
|
||||
* with the OS being run.
|
||||
*/
|
||||
QString fsutils::toNativePath(const QString& path) {
|
||||
return QDir::toNativeSeparators(path);
|
||||
}
|
||||
|
||||
QString fsutils::fromNativePath(const QString &path) {
|
||||
return QDir::fromNativeSeparators(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension part of a file name.
|
||||
*/
|
||||
QString fsutils::fileExtension(const QString &filename) {
|
||||
QString ext = QString(filename).remove(".!qB");
|
||||
const int point_index = ext.lastIndexOf(".");
|
||||
return (point_index >= 0) ? ext.mid(point_index + 1) : QString();
|
||||
}
|
||||
|
||||
QString fsutils::fileName(const QString& file_path) {
|
||||
QString path = fsutils::fromNativePath(file_path);
|
||||
const int slash_index = path.lastIndexOf("/");
|
||||
if (slash_index == -1)
|
||||
return path;
|
||||
return path.mid(slash_index + 1);
|
||||
}
|
||||
|
||||
QString fsutils::folderName(const QString& file_path) {
|
||||
QString path = fsutils::fromNativePath(file_path);
|
||||
const int slash_index = path.lastIndexOf("/");
|
||||
if (slash_index == -1)
|
||||
return path;
|
||||
return path.left(slash_index);
|
||||
}
|
||||
|
||||
bool fsutils::isValidTorrentFile(const QString& torrent_path) {
|
||||
try {
|
||||
boost::intrusive_ptr<libtorrent::torrent_info> t = new torrent_info(fsutils::toNativePath(torrent_path).toUtf8().constData());
|
||||
if (!t->is_valid() || t->num_files() == 0)
|
||||
return false;
|
||||
} catch(std::exception&) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an empty folder tree.
|
||||
*
|
||||
* This function will also remove .DS_Store files on Mac OS and
|
||||
* Thumbs.db on Windows.
|
||||
*/
|
||||
bool fsutils::smartRemoveEmptyFolderTree(const QString& dir_path) {
|
||||
qDebug() << Q_FUNC_INFO << dir_path;
|
||||
if (dir_path.isEmpty())
|
||||
return false;
|
||||
|
||||
QDir dir(dir_path);
|
||||
if (!dir.exists())
|
||||
return true;
|
||||
|
||||
// Remove Files created by the OS
|
||||
#if defined Q_OS_MAC
|
||||
fsutils::forceRemove(dir_path + QLatin1String("/.DS_Store"));
|
||||
#elif defined Q_OS_WIN
|
||||
fsutils::forceRemove(dir_path + QLatin1String("/Thumbs.db"));
|
||||
#endif
|
||||
|
||||
QFileInfoList sub_files = dir.entryInfoList();
|
||||
foreach (const QFileInfo& info, sub_files) {
|
||||
QString sub_name = info.fileName();
|
||||
if (sub_name == "." || sub_name == "..")
|
||||
continue;
|
||||
|
||||
QString sub_path = info.absoluteFilePath();
|
||||
qDebug() << Q_FUNC_INFO << "sub file: " << sub_path;
|
||||
if (info.isDir()) {
|
||||
if (!smartRemoveEmptyFolderTree(sub_path)) {
|
||||
qWarning() << Q_FUNC_INFO << "Failed to remove folder: " << sub_path;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (info.isHidden()) {
|
||||
qDebug() << Q_FUNC_INFO << "Removing hidden file: " << sub_path;
|
||||
if (!fsutils::forceRemove(sub_path)) {
|
||||
qWarning() << Q_FUNC_INFO << "Failed to remove " << sub_path;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
qWarning() << Q_FUNC_INFO << "Folder is not empty, aborting. Found: " << sub_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
qDebug() << Q_FUNC_INFO << "Calling rmdir on " << dir_path;
|
||||
return QDir().rmdir(dir_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file with the given file_path.
|
||||
*
|
||||
* This function will try to fix the file permissions before removing it.
|
||||
*/
|
||||
bool fsutils::forceRemove(const QString& file_path) {
|
||||
QFile f(file_path);
|
||||
if (!f.exists())
|
||||
return true;
|
||||
// Make sure we have read/write permissions
|
||||
f.setPermissions(f.permissions()|QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser);
|
||||
// Remove the file
|
||||
return f.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of a file.
|
||||
* If the file is a folder, it will compute its size based on its content.
|
||||
*
|
||||
* Returns -1 in case of error.
|
||||
*/
|
||||
qint64 fsutils::computePathSize(const QString& path) {
|
||||
// Check if it is a file
|
||||
QFileInfo fi(path);
|
||||
if (!fi.exists()) return -1;
|
||||
if (fi.isFile()) return fi.size();
|
||||
// Compute folder size based on its content
|
||||
qint64 size = 0;
|
||||
foreach (const QFileInfo &subfi, QDir(path).entryInfoList(QDir::Dirs|QDir::Files)) {
|
||||
if (subfi.fileName().startsWith(".")) continue;
|
||||
if (subfi.isDir())
|
||||
size += fsutils::computePathSize(subfi.absoluteFilePath());
|
||||
else
|
||||
size += subfi.size();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes deep comparison of two files to make sure they are identical.
|
||||
*/
|
||||
bool fsutils::sameFiles(const QString& path1, const QString& path2) {
|
||||
QFile f1(path1), f2(path2);
|
||||
if (!f1.exists() || !f2.exists()) return false;
|
||||
if (f1.size() != f2.size()) return false;
|
||||
if (!f1.open(QIODevice::ReadOnly)) return false;
|
||||
if (!f2.open(QIODevice::ReadOnly)) {
|
||||
f1.close();
|
||||
return false;
|
||||
}
|
||||
bool same = true;
|
||||
while(!f1.atEnd() && !f2.atEnd()) {
|
||||
if (f1.read(1024) != f2.read(1024)) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
f1.close(); f2.close();
|
||||
return same;
|
||||
}
|
||||
|
||||
QString fsutils::updateLabelInSavePath(const QString& defaultSavePath, const QString& save_path, const QString& old_label, const QString& new_label) {
|
||||
if (old_label == new_label) return fsutils::fromNativePath(save_path);
|
||||
QString defaultPath = fsutils::fromNativePath(defaultSavePath);
|
||||
QString path = fsutils::fromNativePath(save_path);
|
||||
qDebug("UpdateLabelInSavePath(%s, %s, %s)", qPrintable(path), qPrintable(old_label), qPrintable(new_label));
|
||||
if (!path.startsWith(defaultPath)) return path;
|
||||
QString new_save_path = path;
|
||||
new_save_path.remove(defaultPath);
|
||||
QStringList path_parts = new_save_path.split("/", QString::SkipEmptyParts);
|
||||
if (path_parts.empty()) {
|
||||
if (!new_label.isEmpty())
|
||||
path_parts << new_label;
|
||||
} else {
|
||||
if (old_label.isEmpty() || path_parts.first() != old_label) {
|
||||
if (path_parts.first() != new_label)
|
||||
path_parts.prepend(new_label);
|
||||
} else {
|
||||
if (new_label.isEmpty()) {
|
||||
path_parts.removeAt(0);
|
||||
} else {
|
||||
if (path_parts.first() != new_label)
|
||||
path_parts.replace(0, new_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
new_save_path = defaultPath;
|
||||
if (!new_save_path.endsWith("/")) new_save_path += "/";
|
||||
new_save_path += path_parts.join("/");
|
||||
qDebug("New save path is %s", qPrintable(new_save_path));
|
||||
return new_save_path;
|
||||
}
|
||||
|
||||
QString fsutils::toValidFileSystemName(QString filename) {
|
||||
qDebug("toValidFSName: %s", qPrintable(filename));
|
||||
const QRegExp regex("[\\\\/:?\"*<>|]");
|
||||
filename.replace(regex, " ");
|
||||
qDebug("toValidFSName, result: %s", qPrintable(filename));
|
||||
return filename.trimmed();
|
||||
}
|
||||
|
||||
bool fsutils::isValidFileSystemName(const QString& filename) {
|
||||
if (filename.isEmpty()) return false;
|
||||
const QRegExp regex("[\\\\/:?\"*<>|]");
|
||||
return !filename.contains(regex);
|
||||
}
|
||||
|
||||
long long fsutils::freeDiskSpaceOnPath(QString path) {
|
||||
if (path.isEmpty()) return -1;
|
||||
QDir dir_path(path);
|
||||
if (!dir_path.exists()) {
|
||||
QStringList parts = path.split("/");
|
||||
while (parts.size() > 1 && !QDir(parts.join("/")).exists()) {
|
||||
parts.removeLast();
|
||||
}
|
||||
dir_path = QDir(parts.join("/"));
|
||||
if (!dir_path.exists()) return -1;
|
||||
}
|
||||
Q_ASSERT(dir_path.exists());
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
unsigned long long available;
|
||||
#ifdef Q_OS_HAIKU
|
||||
const QString statfs_path = dir_path.path()+"/.";
|
||||
dev_t device = dev_for_path (qPrintable(statfs_path));
|
||||
if (device >= 0) {
|
||||
fs_info info;
|
||||
if(fs_stat_dev(device, &info)==B_OK){
|
||||
available = ((unsigned long long)(info.free_blocks*info.block_size));
|
||||
return available;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
#else
|
||||
struct statfs stats;
|
||||
const QString statfs_path = dir_path.path()+"/.";
|
||||
const int ret = statfs (qPrintable(statfs_path), &stats) ;
|
||||
if (ret == 0) {
|
||||
available = ((unsigned long long)stats.f_bavail) *
|
||||
((unsigned long long)stats.f_bsize) ;
|
||||
return available;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
typedef BOOL (WINAPI *GetDiskFreeSpaceEx_t)(LPCTSTR,
|
||||
PULARGE_INTEGER,
|
||||
PULARGE_INTEGER,
|
||||
PULARGE_INTEGER);
|
||||
GetDiskFreeSpaceEx_t
|
||||
pGetDiskFreeSpaceEx = (GetDiskFreeSpaceEx_t)::GetProcAddress
|
||||
(
|
||||
::GetModuleHandle(TEXT("kernel32.dll")),
|
||||
"GetDiskFreeSpaceExW"
|
||||
);
|
||||
if ( pGetDiskFreeSpaceEx )
|
||||
{
|
||||
ULARGE_INTEGER bytesFree, bytesTotal;
|
||||
unsigned long long *ret;
|
||||
if (pGetDiskFreeSpaceEx((LPCTSTR)(fsutils::toNativePath(dir_path.path())).utf16(), &bytesFree, &bytesTotal, NULL)) {
|
||||
ret = (unsigned long long*)&bytesFree;
|
||||
return *ret;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QString fsutils::branchPath(const QString& file_path, QString* removed) {
|
||||
QString ret = fsutils::fromNativePath(file_path);
|
||||
if (ret.endsWith("/"))
|
||||
ret.chop(1);
|
||||
const int slashIndex = ret.lastIndexOf("/");
|
||||
if (slashIndex >= 0) {
|
||||
if (removed)
|
||||
*removed = ret.mid(slashIndex + 1);
|
||||
ret = ret.left(slashIndex);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool fsutils::sameFileNames(const QString &first, const QString &second) {
|
||||
#if defined(Q_OS_UNIX) || defined(Q_WS_QWS)
|
||||
return QString::compare(first, second, Qt::CaseSensitive) == 0;
|
||||
#else
|
||||
return QString::compare(first, second, Qt::CaseInsensitive) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString fsutils::expandPath(const QString &path) {
|
||||
QString ret = fsutils::fromNativePath(path.trimmed());
|
||||
if (ret.isEmpty())
|
||||
return ret;
|
||||
|
||||
return QDir::cleanPath(ret);
|
||||
}
|
||||
|
||||
QString fsutils::expandPathAbs(const QString& path) {
|
||||
QString ret = fsutils::expandPath(path);
|
||||
|
||||
if (!QDir::isAbsolutePath(ret))
|
||||
ret = QDir(ret).absolutePath();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString fsutils::QDesktopServicesDataLocation() {
|
||||
QString result;
|
||||
#ifdef Q_OS_WIN
|
||||
LPWSTR path=new WCHAR[256];
|
||||
if (SHGetSpecialFolderPath(0, path, CSIDL_LOCAL_APPDATA, FALSE))
|
||||
result = fsutils::fromNativePath(QString::fromWCharArray(path));
|
||||
if (!QCoreApplication::applicationName().isEmpty())
|
||||
result += QLatin1String("/") + qApp->applicationName();
|
||||
#else
|
||||
#ifdef Q_OS_MAC
|
||||
FSRef ref;
|
||||
OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType, false, &ref);
|
||||
if (err)
|
||||
return QString();
|
||||
QString path;
|
||||
QByteArray ba(2048, 0);
|
||||
if (FSRefMakePath(&ref, reinterpret_cast<UInt8 *>(ba.data()), ba.size()) == noErr)
|
||||
result = QString::fromUtf8(ba).normalized(QString::NormalizationForm_C);
|
||||
result += QLatin1Char('/') + qApp->applicationName();
|
||||
#else
|
||||
QString xdgDataHome = QLatin1String(qgetenv("XDG_DATA_HOME"));
|
||||
if (xdgDataHome.isEmpty())
|
||||
xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
|
||||
xdgDataHome += QLatin1String("/data/")
|
||||
+ qApp->applicationName();
|
||||
result = xdgDataHome;
|
||||
#endif
|
||||
#endif
|
||||
if (!result.endsWith("/"))
|
||||
result += "/";
|
||||
return result;
|
||||
}
|
||||
|
||||
QString fsutils::QDesktopServicesCacheLocation() {
|
||||
QString result;
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
|
||||
result = QDesktopServicesDataLocation() + QLatin1String("cache");
|
||||
#else
|
||||
#ifdef Q_OS_MAC
|
||||
// http://developer.apple.com/documentation/Carbon/Reference/Folder_Manager/Reference/reference.html
|
||||
FSRef ref;
|
||||
OSErr err = FSFindFolder(kUserDomain, kCachedDataFolderType, false, &ref);
|
||||
if (err)
|
||||
return QString();
|
||||
QByteArray ba(2048, 0);
|
||||
if (FSRefMakePath(&ref, reinterpret_cast<UInt8 *>(ba.data()), ba.size()) == noErr)
|
||||
result = QString::fromUtf8(ba).normalized(QString::NormalizationForm_C);
|
||||
result += QLatin1Char('/') + qApp->applicationName();
|
||||
#else
|
||||
QString xdgCacheHome = QLatin1String(qgetenv("XDG_CACHE_HOME"));
|
||||
if (xdgCacheHome.isEmpty())
|
||||
xdgCacheHome = QDir::homePath() + QLatin1String("/.cache");
|
||||
xdgCacheHome += QLatin1Char('/') + QCoreApplication::applicationName();
|
||||
result = xdgCacheHome;
|
||||
#endif
|
||||
#endif
|
||||
if (!result.endsWith("/"))
|
||||
result += "/";
|
||||
return result;
|
||||
}
|
||||
|
||||
QString fsutils::QDesktopServicesDownloadLocation() {
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
|
||||
// as long as it stays WinXP like we do the same on OS/2
|
||||
// TODO: Use IKnownFolderManager to get path of FOLDERID_Downloads
|
||||
// instead of hardcoding "Downloads"
|
||||
// Unfortunately, this would break compatibility with WinXP
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
return QDir(QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation)).absoluteFilePath(
|
||||
QCoreApplication::translate("fsutils", "Downloads"));
|
||||
#else
|
||||
return QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).absoluteFilePath(
|
||||
QCoreApplication::translate("fsutils", "Downloads"));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
||||
QString save_path;
|
||||
// Default save path on Linux
|
||||
QString config_path = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_HOME").constData());
|
||||
if (config_path.isEmpty())
|
||||
config_path = QDir::home().absoluteFilePath(".config");
|
||||
|
||||
QString user_dirs_file = config_path + "/user-dirs.dirs";
|
||||
if (QFile::exists(user_dirs_file)) {
|
||||
QSettings settings(user_dirs_file, QSettings::IniFormat);
|
||||
// We need to force UTF-8 encoding here since this is not
|
||||
// the default for Ini files.
|
||||
settings.setIniCodec("UTF-8");
|
||||
QString xdg_download_dir = settings.value("XDG_DOWNLOAD_DIR").toString();
|
||||
if (!xdg_download_dir.isEmpty()) {
|
||||
// Resolve $HOME environment variables
|
||||
xdg_download_dir.replace("$HOME", QDir::homePath());
|
||||
save_path = xdg_download_dir;
|
||||
qDebug() << Q_FUNC_INFO << "SUCCESS: Using XDG path for downloads: " << save_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
if (!save_path.isEmpty() && !QFile::exists(save_path)) {
|
||||
QDir().mkpath(save_path);
|
||||
}
|
||||
|
||||
if (save_path.isEmpty() || !QFile::exists(save_path)) {
|
||||
save_path = QDir::home().absoluteFilePath(QCoreApplication::translate("fsutils", "Downloads"));
|
||||
qDebug() << Q_FUNC_INFO << "using" << save_path << "as fallback since the XDG detection did not work";
|
||||
}
|
||||
|
||||
return save_path;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// TODO: How to support this on Mac OS X?
|
||||
#endif
|
||||
|
||||
// Fallback
|
||||
return QDir::home().absoluteFilePath(QCoreApplication::translate("fsutils", "Downloads"));
|
||||
}
|
||||
|
||||
QString fsutils::searchEngineLocation() {
|
||||
QString folder = "nova";
|
||||
if (misc::pythonVersion() >= 3)
|
||||
folder = "nova3";
|
||||
const QString location = fsutils::expandPathAbs(QDesktopServicesDataLocation()
|
||||
+ folder);
|
||||
QDir locationDir(location);
|
||||
if (!locationDir.exists())
|
||||
locationDir.mkpath(locationDir.absolutePath());
|
||||
return location;
|
||||
}
|
||||
|
||||
QString fsutils::BTBackupLocation() {
|
||||
const QString location = fsutils::expandPathAbs(QDesktopServicesDataLocation()
|
||||
+ "BT_backup");
|
||||
QDir locationDir(location);
|
||||
if (!locationDir.exists())
|
||||
locationDir.mkpath(locationDir.absolutePath());
|
||||
return location;
|
||||
}
|
||||
|
||||
QString fsutils::cacheLocation() {
|
||||
QString location = fsutils::expandPathAbs(QDesktopServicesCacheLocation());
|
||||
QDir locationDir(location);
|
||||
if (!locationDir.exists())
|
||||
locationDir.mkpath(locationDir.absolutePath());
|
||||
return location;
|
||||
}
|
||||
73
src/core/fs_utils.h
Normal file
73
src/core/fs_utils.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2012 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef FS_UTILS_H
|
||||
#define FS_UTILS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* Utility functions related to file system.
|
||||
*/
|
||||
namespace fsutils
|
||||
{
|
||||
|
||||
QString toNativePath(const QString& path);
|
||||
QString fromNativePath(const QString& path);
|
||||
QString fileExtension(const QString& filename);
|
||||
QString fileName(const QString& file_path);
|
||||
QString folderName(const QString& file_path);
|
||||
qint64 computePathSize(const QString& path);
|
||||
bool sameFiles(const QString& path1, const QString& path2);
|
||||
QString updateLabelInSavePath(const QString &defaultSavePath, const QString &save_path, const QString& old_label, const QString& new_label);
|
||||
QString toValidFileSystemName(QString filename);
|
||||
bool isValidFileSystemName(const QString& filename);
|
||||
long long freeDiskSpaceOnPath(QString path);
|
||||
QString branchPath(const QString& file_path, QString* removed = 0);
|
||||
bool sameFileNames(const QString& first, const QString& second);
|
||||
QString expandPath(const QString& path);
|
||||
QString expandPathAbs(const QString& path);
|
||||
bool isValidTorrentFile(const QString& path);
|
||||
bool smartRemoveEmptyFolderTree(const QString& dir_path);
|
||||
bool forceRemove(const QString& file_path);
|
||||
|
||||
/* Ported from Qt4 to drop dependency on QtGui */
|
||||
QString QDesktopServicesDataLocation();
|
||||
QString QDesktopServicesCacheLocation();
|
||||
QString QDesktopServicesDownloadLocation();
|
||||
/* End of Qt4 code */
|
||||
QString searchEngineLocation();
|
||||
QString BTBackupLocation();
|
||||
QString cacheLocation();
|
||||
|
||||
}
|
||||
|
||||
#endif // FS_UTILS_H
|
||||
|
||||
375
src/core/httprequestparser.cpp
Normal file
375
src/core/httprequestparser.cpp
Normal file
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
//#include <QVariant>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#include <QUrlQuery>
|
||||
#endif
|
||||
#include <QDir>
|
||||
#include <QTemporaryFile>
|
||||
#include <QDebug>
|
||||
#include "httprequestparser.h"
|
||||
|
||||
const QByteArray EOL("\r\n");
|
||||
const QByteArray EOH("\r\n\r\n");
|
||||
|
||||
inline QString unquoted(const QString& str)
|
||||
{
|
||||
if ((str[0] == '\"') && (str[str.length() - 1] == '\"'))
|
||||
return str.mid(1, str.length() - 2);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
HttpRequestParser::ErrorCode HttpRequestParser::parse(const QByteArray& data, HttpRequest& request, uint maxContentLength)
|
||||
{
|
||||
return HttpRequestParser(maxContentLength).parseHttpRequest(data, request);
|
||||
}
|
||||
|
||||
HttpRequestParser::HttpRequestParser(uint maxContentLength)
|
||||
: maxContentLength_(maxContentLength)
|
||||
{
|
||||
}
|
||||
|
||||
HttpRequestParser::ErrorCode HttpRequestParser::parseHttpRequest(const QByteArray& data, HttpRequest& request)
|
||||
{
|
||||
request_ = HttpRequest();
|
||||
|
||||
// Parse HTTP request header
|
||||
const int header_end = data.indexOf(EOH);
|
||||
if (header_end < 0)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||
return IncompleteRequest;
|
||||
}
|
||||
|
||||
if (!parseHttpHeader(data.left(header_end)))
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "header parsing error";
|
||||
return BadRequest;
|
||||
}
|
||||
|
||||
// Parse HTTP request message
|
||||
int content_length = 0;
|
||||
if (request_.headers.contains("content-length"))
|
||||
{
|
||||
content_length = request_.headers["content-length"].toInt();
|
||||
if (content_length > static_cast<int>(maxContentLength_))
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
||||
return BadRequest;
|
||||
}
|
||||
|
||||
QByteArray content = data.mid(header_end + EOH.length(), content_length);
|
||||
if (content.length() < content_length)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||
return IncompleteRequest;
|
||||
}
|
||||
|
||||
if (!parseContent(content))
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "message parsing error";
|
||||
return BadRequest;
|
||||
}
|
||||
}
|
||||
|
||||
// qDebug() << Q_FUNC_INFO;
|
||||
// qDebug() << "HTTP Request header:";
|
||||
// qDebug() << data.left(header_end) << "\n";
|
||||
|
||||
request = request_;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
bool HttpRequestParser::parseStartingLine(const QString &line)
|
||||
{
|
||||
const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$");
|
||||
|
||||
if (rx.indexIn(line.trimmed()) >= 0)
|
||||
{
|
||||
request_.method = rx.cap(1);
|
||||
|
||||
QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1());
|
||||
request_.path = url.path(); // Path
|
||||
|
||||
// Parse GET parameters
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
||||
#else
|
||||
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems());
|
||||
#endif
|
||||
while (i.hasNext())
|
||||
{
|
||||
QPair<QString, QString> pair = i.next();
|
||||
request_.gets[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HttpRequestParser::parseHeaderLine(const QString &line, QPair<QString, QString>& out)
|
||||
{
|
||||
int i = line.indexOf(QLatin1Char(':'));
|
||||
if (i == -1)
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||
return false;
|
||||
}
|
||||
|
||||
out = qMakePair(line.left(i).trimmed().toLower(), line.mid(i + 1).trimmed());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpRequestParser::parseHttpHeader(const QByteArray &data)
|
||||
{
|
||||
QString str = QString::fromUtf8(data);
|
||||
QStringList lines = str.trimmed().split(EOL);
|
||||
|
||||
QStringList headerLines;
|
||||
foreach (const QString& line, lines)
|
||||
{
|
||||
if (line[0].isSpace()) // header line continuation
|
||||
{
|
||||
if (!headerLines.isEmpty()) // really continuation
|
||||
{
|
||||
headerLines.last() += QLatin1Char(' ');
|
||||
headerLines.last() += line.trimmed();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headerLines.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (headerLines.isEmpty())
|
||||
return false; // Empty header
|
||||
|
||||
QStringList::Iterator it = headerLines.begin();
|
||||
if (!parseStartingLine(*it))
|
||||
return false;
|
||||
|
||||
++it;
|
||||
for (; it != headerLines.end(); ++it)
|
||||
{
|
||||
QPair<QString, QString> header;
|
||||
if (!parseHeaderLine(*it, header))
|
||||
return false;
|
||||
|
||||
request_.headers[header.first] = header.second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<QByteArray> HttpRequestParser::splitMultipartData(const QByteArray& data, const QByteArray& boundary)
|
||||
{
|
||||
QList<QByteArray> ret;
|
||||
QByteArray sep = boundary + EOL;
|
||||
const int sepLength = sep.size();
|
||||
|
||||
int start = 0, end = 0;
|
||||
if ((end = data.indexOf(sep, start)) >= 0)
|
||||
{
|
||||
start = end + sepLength; // skip first boundary
|
||||
|
||||
while ((end = data.indexOf(sep, start)) >= 0)
|
||||
{
|
||||
ret << data.mid(start, end - start);
|
||||
start = end + sepLength;
|
||||
}
|
||||
|
||||
// last or single part
|
||||
sep = boundary + "--" + EOL;
|
||||
if ((end = data.indexOf(sep, start)) >= 0)
|
||||
ret << data.mid(start, end - start);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HttpRequestParser::parseContent(const QByteArray& data)
|
||||
{
|
||||
// Parse message content
|
||||
qDebug() << Q_FUNC_INFO << "Content-Length: " << request_.headers["content-length"];
|
||||
qDebug() << Q_FUNC_INFO << "data.size(): " << data.size();
|
||||
|
||||
// Parse url-encoded POST data
|
||||
if (request_.headers["content-type"].startsWith("application/x-www-form-urlencoded"))
|
||||
{
|
||||
QUrl url;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
url.setEncodedQuery(data);
|
||||
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
||||
#else
|
||||
url.setQuery(data);
|
||||
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems(QUrl::FullyDecoded));
|
||||
#endif
|
||||
while (i.hasNext())
|
||||
{
|
||||
QPair<QString, QString> pair = i.next();
|
||||
request_.posts[pair.first.toLower()] = pair.second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse multipart/form data (torrent file)
|
||||
/**
|
||||
data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5")
|
||||
|
||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||
Content-Disposition: form-data; name=\"Filename\"
|
||||
|
||||
PB020344.torrent
|
||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||
Content-Disposition: form-data; name=\"torrentfile"; filename=\"PB020344.torrent\"
|
||||
Content-Type: application/x-bittorrent
|
||||
|
||||
BINARY DATA IS HERE
|
||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||
Content-Disposition: form-data; name=\"Upload\"
|
||||
|
||||
Submit Query
|
||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5--
|
||||
**/
|
||||
QString content_type = request_.headers["content-type"];
|
||||
if (content_type.startsWith("multipart/form-data"))
|
||||
{
|
||||
const QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\"");
|
||||
const QRegExp boundaryRegexNotQuoted("boundary=([\\w'()+,-\\./:=\\?]+)");
|
||||
|
||||
QByteArray boundary;
|
||||
if (boundaryRegexQuoted.indexIn(content_type) < 0)
|
||||
{
|
||||
if (boundaryRegexNotQuoted.indexIn(content_type) < 0)
|
||||
{
|
||||
qWarning() << "Could not find boundary in multipart/form-data header!";
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1();
|
||||
}
|
||||
|
||||
qDebug() << "Boundary is " << boundary;
|
||||
QList<QByteArray> parts = splitMultipartData(data, boundary);
|
||||
qDebug() << parts.size() << "parts in data";
|
||||
|
||||
foreach (const QByteArray& part, parts)
|
||||
{
|
||||
if (!parseFormData(part))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(content_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HttpRequestParser::parseFormData(const QByteArray& data)
|
||||
{
|
||||
// Parse form data header
|
||||
const int header_end = data.indexOf(EOH);
|
||||
if (header_end < 0)
|
||||
{
|
||||
qDebug() << "Invalid form data: \n" << data;
|
||||
return false;
|
||||
}
|
||||
|
||||
QString header_str = QString::fromUtf8(data.left(header_end));
|
||||
QStringList lines = header_str.trimmed().split(EOL);
|
||||
QStringMap headers;
|
||||
foreach (const QString& line, lines)
|
||||
{
|
||||
QPair<QString, QString> header;
|
||||
if (!parseHeaderLine(line, header))
|
||||
return false;
|
||||
|
||||
headers[header.first] = header.second;
|
||||
}
|
||||
|
||||
QStringMap disposition;
|
||||
if (!headers.contains("content-disposition") ||
|
||||
!parseHeaderValue(headers["content-disposition"], disposition) ||
|
||||
!disposition.contains("name"))
|
||||
{
|
||||
qDebug() << "Invalid form data header: \n" << header_str;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (disposition.contains("filename"))
|
||||
{
|
||||
UploadedFile ufile;
|
||||
ufile.filename = disposition["filename"];
|
||||
ufile.type = disposition["content-type"];
|
||||
ufile.data = data.mid(header_end + EOH.length());
|
||||
|
||||
request_.files[disposition["name"]] = ufile;
|
||||
}
|
||||
else
|
||||
{
|
||||
request_.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpRequestParser::parseHeaderValue(const QString& value, QStringMap& out)
|
||||
{
|
||||
QStringList items = value.split(QLatin1Char(';'));
|
||||
out[""] = items[0];
|
||||
|
||||
for (QStringList::size_type i = 1; i < items.size(); ++i)
|
||||
{
|
||||
int pos = items[i].indexOf("=");
|
||||
if (pos < 0)
|
||||
return false;
|
||||
|
||||
out[items[i].left(pos).trimmed()] = unquoted(items[i].mid(pos + 1).trimmed());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
64
src/core/httprequestparser.h
Normal file
64
src/core/httprequestparser.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef HTTPREQUESTPARSER_H
|
||||
#define HTTPREQUESTPARSER_H
|
||||
|
||||
#include "httptypes.h"
|
||||
|
||||
class HttpRequestParser
|
||||
{
|
||||
public:
|
||||
enum ErrorCode { NoError = 0, IncompleteRequest, BadRequest };
|
||||
|
||||
// when result != NoError parsed request is undefined
|
||||
// Warning! Header names are converted to lower-case.
|
||||
static ErrorCode parse(const QByteArray& data, HttpRequest& request, uint maxContentLength = 10000000 /* ~10MB */);
|
||||
|
||||
private:
|
||||
HttpRequestParser(uint maxContentLength);
|
||||
|
||||
ErrorCode parseHttpRequest(const QByteArray& data, HttpRequest& request);
|
||||
|
||||
bool parseHttpHeader(const QByteArray& data);
|
||||
bool parseStartingLine(const QString &line);
|
||||
bool parseContent(const QByteArray& data);
|
||||
bool parseFormData(const QByteArray& data);
|
||||
QList<QByteArray> splitMultipartData(const QByteArray& data, const QByteArray& boundary);
|
||||
|
||||
static bool parseHeaderLine(const QString& line, QPair<QString, QString>& out);
|
||||
static bool parseHeaderValue(const QString& value, QStringMap& out);
|
||||
|
||||
const uint maxContentLength_;
|
||||
HttpRequest request_;
|
||||
};
|
||||
|
||||
#endif
|
||||
131
src/core/httpresponsegenerator.cpp
Normal file
131
src/core/httpresponsegenerator.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <zlib.h>
|
||||
#include "httpresponsegenerator.h"
|
||||
|
||||
bool gCompress(QByteArray data, QByteArray& dest_buffer);
|
||||
|
||||
QByteArray HttpResponseGenerator::generate(HttpResponse response)
|
||||
{
|
||||
if (response.headers[HEADER_CONTENT_ENCODING] == "gzip")
|
||||
{
|
||||
// A gzip seems to have 23 bytes overhead.
|
||||
// Also "Content-Encoding: gzip\r\n" is 26 bytes long
|
||||
// So we only benefit from gzip if the message is bigger than 23+26 = 49
|
||||
// If the message is smaller than 49 bytes we actually send MORE data if we gzip
|
||||
QByteArray dest_buf;
|
||||
if ((response.content.size() > 49) && (gCompress(response.content, dest_buf)))
|
||||
{
|
||||
response.content = dest_buf;
|
||||
}
|
||||
else
|
||||
{
|
||||
response.headers.remove(HEADER_CONTENT_ENCODING);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.content.length() > 0)
|
||||
response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length());
|
||||
|
||||
QString ret(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n"));
|
||||
|
||||
QString header;
|
||||
foreach (const QString& key, response.headers.keys())
|
||||
header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]);
|
||||
|
||||
ret = ret.arg(response.status.code).arg(response.status.text).arg(header);
|
||||
|
||||
// qDebug() << Q_FUNC_INFO;
|
||||
// qDebug() << "HTTP Response header:";
|
||||
// qDebug() << ret;
|
||||
|
||||
return ret.toUtf8() + response.content;
|
||||
}
|
||||
|
||||
bool gCompress(QByteArray data, QByteArray& dest_buffer)
|
||||
{
|
||||
static const int BUFSIZE = 128 * 1024;
|
||||
char tmp_buf[BUFSIZE];
|
||||
int ret;
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.next_in = reinterpret_cast<unsigned char*>(data.data());
|
||||
strm.avail_in = data.length();
|
||||
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
||||
strm.avail_out = BUFSIZE;
|
||||
|
||||
//windowBits = 15+16 to enable gzip
|
||||
//From the zlib manual: windowBits can also be greater than 15 for optional gzip encoding. Add 16 to windowBits
|
||||
//to write a simple gzip header and trailer around the compressed data instead of a zlib wrapper.
|
||||
ret = deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY);
|
||||
|
||||
if (ret != Z_OK)
|
||||
return false;
|
||||
|
||||
while (strm.avail_in != 0)
|
||||
{
|
||||
ret = deflate(&strm, Z_NO_FLUSH);
|
||||
if (ret != Z_OK)
|
||||
return false;
|
||||
|
||||
if (strm.avail_out == 0)
|
||||
{
|
||||
dest_buffer.append(tmp_buf, BUFSIZE);
|
||||
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
||||
strm.avail_out = BUFSIZE;
|
||||
}
|
||||
}
|
||||
|
||||
int deflate_res = Z_OK;
|
||||
while (deflate_res == Z_OK)
|
||||
{
|
||||
if (strm.avail_out == 0)
|
||||
{
|
||||
dest_buffer.append(tmp_buf, BUFSIZE);
|
||||
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
||||
strm.avail_out = BUFSIZE;
|
||||
}
|
||||
|
||||
deflate_res = deflate(&strm, Z_FINISH);
|
||||
}
|
||||
|
||||
if (deflate_res != Z_STREAM_END)
|
||||
return false;
|
||||
|
||||
dest_buffer.append(tmp_buf, BUFSIZE - strm.avail_out);
|
||||
deflateEnd(&strm);
|
||||
|
||||
return true;
|
||||
}
|
||||
44
src/core/httpresponsegenerator.h
Normal file
44
src/core/httpresponsegenerator.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
|
||||
#ifndef HTTPRESPONSEGENERATOR_H
|
||||
#define HTTPRESPONSEGENERATOR_H
|
||||
|
||||
#include "httptypes.h"
|
||||
|
||||
class HttpResponseGenerator
|
||||
{
|
||||
public:
|
||||
static QByteArray generate(HttpResponse response);
|
||||
};
|
||||
|
||||
#endif
|
||||
90
src/core/httptypes.h
Normal file
90
src/core/httptypes.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef HTTPTYPES_H
|
||||
#define HTTPTYPES_H
|
||||
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QHostAddress>
|
||||
|
||||
typedef QMap<QString, QString> QStringMap;
|
||||
|
||||
const QString HEADER_SET_COOKIE = "Set-Cookie";
|
||||
const QString HEADER_CONTENT_TYPE = "Content-Type";
|
||||
const QString HEADER_CONTENT_ENCODING = "Content-Encoding";
|
||||
const QString HEADER_CONTENT_LENGTH = "Content-Length";
|
||||
const QString HEADER_CACHE_CONTROL = "Cache-Control";
|
||||
|
||||
const QString CONTENT_TYPE_CSS = "text/css; charset=UTF-8";
|
||||
const QString CONTENT_TYPE_GIF = "image/gif";
|
||||
const QString CONTENT_TYPE_HTML = "text/html; charset=UTF-8";
|
||||
const QString CONTENT_TYPE_JS = "text/javascript; charset=UTF-8";
|
||||
const QString CONTENT_TYPE_PNG = "image/png";
|
||||
const QString CONTENT_TYPE_TXT = "text/plain; charset=UTF-8";
|
||||
|
||||
struct HttpEnvironment
|
||||
{
|
||||
QHostAddress clientAddress;
|
||||
};
|
||||
|
||||
struct UploadedFile
|
||||
{
|
||||
QString filename; // original filename
|
||||
QString type; // MIME type
|
||||
QByteArray data; // File data
|
||||
};
|
||||
|
||||
struct HttpRequest
|
||||
{
|
||||
QString method;
|
||||
QString path;
|
||||
QStringMap headers;
|
||||
QStringMap gets;
|
||||
QStringMap posts;
|
||||
QMap<QString, UploadedFile> files;
|
||||
};
|
||||
|
||||
struct HttpResponseStatus
|
||||
{
|
||||
uint code;
|
||||
QString text;
|
||||
|
||||
HttpResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {}
|
||||
};
|
||||
|
||||
struct HttpResponse
|
||||
{
|
||||
HttpResponseStatus status;
|
||||
QStringMap headers;
|
||||
QByteArray content;
|
||||
|
||||
HttpResponse(uint code = 200, const QString& text = "OK"): status(code, text) {}
|
||||
};
|
||||
|
||||
#endif // HTTPTYPES_H
|
||||
127
src/core/logger.cpp
Normal file
127
src/core/logger.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "logger.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
namespace Log
|
||||
{
|
||||
Msg::Msg() {}
|
||||
|
||||
Msg::Msg(int id, MsgType type, const QString &message)
|
||||
: id(id)
|
||||
, timestamp(QDateTime::currentMSecsSinceEpoch())
|
||||
, type(type)
|
||||
, message(message)
|
||||
{
|
||||
}
|
||||
|
||||
Peer::Peer() {}
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
Peer::Peer(int id, const QString &ip, bool blocked)
|
||||
#else
|
||||
Peer::Peer(int id, const QString &ip, bool blocked, const QString &reason)
|
||||
#endif
|
||||
: id(id)
|
||||
, timestamp(QDateTime::currentMSecsSinceEpoch())
|
||||
, ip(ip)
|
||||
, blocked(blocked)
|
||||
#if LIBTORRENT_VERSION_NUM >= 10000
|
||||
, reason(reason)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Logger* Logger::m_instance = 0;
|
||||
|
||||
Logger::Logger()
|
||||
: lock(QReadWriteLock::Recursive)
|
||||
, msgCounter(0)
|
||||
, peerCounter(0)
|
||||
{
|
||||
}
|
||||
|
||||
Logger::~Logger() {}
|
||||
|
||||
Logger * Logger::instance()
|
||||
{
|
||||
if (!m_instance)
|
||||
m_instance = new Logger;
|
||||
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
void Logger::drop()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::addMessage(const QString &message, const Log::MsgType &type)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
Log::Msg temp(msgCounter++, type, message);
|
||||
m_messages.push_back(temp);
|
||||
|
||||
if (m_messages.size() >= MAX_LOG_MESSAGES)
|
||||
m_messages.pop_front();
|
||||
|
||||
emit newLogMessage(temp);
|
||||
}
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
void Logger::addPeer(const QString &ip, bool blocked)
|
||||
#else
|
||||
void Logger::addPeer(const QString &ip, bool blocked, const QString &reason)
|
||||
#endif
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
Log::Peer temp(peerCounter++, ip, blocked);
|
||||
#else
|
||||
Log::Peer temp(peerCounter++, ip, blocked, reason);
|
||||
#endif
|
||||
m_peers.push_back(temp);
|
||||
|
||||
if (m_peers.size() >= MAX_LOG_MESSAGES)
|
||||
m_peers.pop_front();
|
||||
|
||||
emit newLogPeer(temp);
|
||||
}
|
||||
|
||||
QVector<Log::Msg> Logger::getMessages(int lastKnownId) const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
|
||||
int diff = msgCounter - lastKnownId - 1;
|
||||
int size = m_messages.size();
|
||||
|
||||
if ((lastKnownId == -1) || (diff >= size))
|
||||
return m_messages;
|
||||
|
||||
if (diff <= 0)
|
||||
return QVector<Log::Msg>();
|
||||
|
||||
return m_messages.mid(size - diff);
|
||||
}
|
||||
|
||||
QVector<Log::Peer> Logger::getPeers(int lastKnownId) const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
|
||||
int diff = peerCounter - lastKnownId - 1;
|
||||
int size = m_peers.size();
|
||||
|
||||
if ((lastKnownId == -1) || (diff >= size))
|
||||
return m_peers;
|
||||
|
||||
if (diff <= 0)
|
||||
return QVector<Log::Peer>();
|
||||
|
||||
return m_peers.mid(size - diff);
|
||||
}
|
||||
83
src/core/logger.h
Normal file
83
src/core/logger.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QReadWriteLock>
|
||||
#include <QObject>
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
const int MAX_LOG_MESSAGES = 1000;
|
||||
|
||||
namespace Log
|
||||
{
|
||||
enum MsgType
|
||||
{
|
||||
NORMAL,
|
||||
INFO,
|
||||
WARNING,
|
||||
CRITICAL //ERROR is defined by libtorrent and results in compiler error
|
||||
};
|
||||
|
||||
struct Msg
|
||||
{
|
||||
Msg();
|
||||
Msg(int id, MsgType type, const QString &message);
|
||||
int id;
|
||||
qint64 timestamp;
|
||||
MsgType type;
|
||||
QString message;
|
||||
};
|
||||
|
||||
struct Peer
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
Peer(int id, const QString &ip, bool blocked);
|
||||
#else
|
||||
Peer(int id, const QString &ip, bool blocked, const QString &reason);
|
||||
#endif
|
||||
Peer();
|
||||
int id;
|
||||
qint64 timestamp;
|
||||
QString ip;
|
||||
bool blocked;
|
||||
#if LIBTORRENT_VERSION_NUM >= 10000
|
||||
QString reason;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
class Logger : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Logger)
|
||||
|
||||
public:
|
||||
static Logger* instance();
|
||||
static void drop();
|
||||
~Logger();
|
||||
|
||||
void addMessage(const QString &message, const Log::MsgType &type = Log::NORMAL);
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
void addPeer(const QString &ip, bool blocked);
|
||||
#else
|
||||
void addPeer(const QString &ip, bool blocked, const QString &reason = QString());
|
||||
#endif
|
||||
QVector<Log::Msg> getMessages(int lastKnownId = -1) const;
|
||||
QVector<Log::Peer> getPeers(int lastKnownId = -1) const;
|
||||
|
||||
signals:
|
||||
void newLogMessage(const Log::Msg &message);
|
||||
void newLogPeer(const Log::Peer &peer);
|
||||
|
||||
private:
|
||||
Logger();
|
||||
static Logger* m_instance;
|
||||
QVector<Log::Msg> m_messages;
|
||||
QVector<Log::Peer> m_peers;
|
||||
mutable QReadWriteLock lock;
|
||||
int msgCounter;
|
||||
int peerCounter;
|
||||
};
|
||||
|
||||
#endif // LOGGER_H
|
||||
669
src/core/misc.cpp
Normal file
669
src/core/misc.cpp
Normal file
@@ -0,0 +1,669 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QUrl>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QLocale>
|
||||
#include <QThread>
|
||||
|
||||
#ifdef DISABLE_GUI
|
||||
#include <QCoreApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <PowrProf.h>
|
||||
const int UNLEN = 256;
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <Carbon/Carbon.h>
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB)
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusMessage>
|
||||
#endif
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
#include <libtorrent/peer_id.hpp>
|
||||
#else
|
||||
#include <libtorrent/sha1_hash.hpp>
|
||||
#endif
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
|
||||
using namespace libtorrent;
|
||||
|
||||
static struct { const char *source; const char *comment; } units[] = {
|
||||
QT_TRANSLATE_NOOP3("misc", "B", "bytes"),
|
||||
QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"),
|
||||
QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"),
|
||||
QT_TRANSLATE_NOOP3("misc", "GiB", "gibibytes (1024 mibibytes)"),
|
||||
QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)")
|
||||
};
|
||||
|
||||
QString misc::toQString(const std::string &str)
|
||||
{
|
||||
return QString::fromLocal8Bit(str.c_str());
|
||||
}
|
||||
|
||||
QString misc::toQString(const char* str)
|
||||
{
|
||||
return QString::fromLocal8Bit(str);
|
||||
}
|
||||
|
||||
QString misc::toQStringU(const std::string &str)
|
||||
{
|
||||
return QString::fromUtf8(str.c_str());
|
||||
}
|
||||
|
||||
QString misc::toQStringU(const char* str)
|
||||
{
|
||||
return QString::fromUtf8(str);
|
||||
}
|
||||
|
||||
QString misc::toQString(const libtorrent::sha1_hash &hash)
|
||||
{
|
||||
char out[41];
|
||||
libtorrent::to_hex((char const*)&hash[0], libtorrent::sha1_hash::size, out);
|
||||
return QString(out);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
void misc::shutdownComputer(shutDownAction action)
|
||||
{
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB)
|
||||
// Use dbus to power off / suspend the system
|
||||
if (action != SHUTDOWN_COMPUTER) {
|
||||
// Some recent systems use systemd's logind
|
||||
QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager", QDBusConnection::systemBus());
|
||||
if (login1Iface.isValid()) {
|
||||
if (action == SUSPEND_COMPUTER)
|
||||
login1Iface.call("Suspend", false);
|
||||
else
|
||||
login1Iface.call("Hibernate", false);
|
||||
return;
|
||||
}
|
||||
// Else, other recent systems use UPower
|
||||
QDBusInterface upowerIface("org.freedesktop.UPower", "/org/freedesktop/UPower",
|
||||
"org.freedesktop.UPower", QDBusConnection::systemBus());
|
||||
if (upowerIface.isValid()) {
|
||||
if (action == SUSPEND_COMPUTER)
|
||||
upowerIface.call("Suspend");
|
||||
else
|
||||
upowerIface.call("Hibernate");
|
||||
return;
|
||||
}
|
||||
// HAL (older systems)
|
||||
QDBusInterface halIface("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer",
|
||||
"org.freedesktop.Hal.Device.SystemPowerManagement",
|
||||
QDBusConnection::systemBus());
|
||||
if (action == SUSPEND_COMPUTER)
|
||||
halIface.call("Suspend", 5);
|
||||
else
|
||||
halIface.call("Hibernate");
|
||||
}
|
||||
else {
|
||||
// Some recent systems use systemd's logind
|
||||
QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager", QDBusConnection::systemBus());
|
||||
if (login1Iface.isValid()) {
|
||||
login1Iface.call("PowerOff", false);
|
||||
return;
|
||||
}
|
||||
// Else, other recent systems use ConsoleKit
|
||||
QDBusInterface consolekitIface("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager",
|
||||
"org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus());
|
||||
if (consolekitIface.isValid()) {
|
||||
consolekitIface.call("Stop");
|
||||
return;
|
||||
}
|
||||
// HAL (older systems)
|
||||
QDBusInterface halIface("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer",
|
||||
"org.freedesktop.Hal.Device.SystemPowerManagement",
|
||||
QDBusConnection::systemBus());
|
||||
halIface.call("Shutdown");
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_MAC
|
||||
AEEventID EventToSend;
|
||||
if (action != SHUTDOWN_COMPUTER)
|
||||
EventToSend = kAESleep;
|
||||
else
|
||||
EventToSend = kAEShutDown;
|
||||
AEAddressDesc targetDesc;
|
||||
static const ProcessSerialNumber kPSNOfSystemProcess = { 0, kSystemProcess };
|
||||
AppleEvent eventReply = {typeNull, NULL};
|
||||
AppleEvent appleEventToSend = {typeNull, NULL};
|
||||
|
||||
OSStatus error = noErr;
|
||||
|
||||
error = AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess,
|
||||
sizeof(kPSNOfSystemProcess), &targetDesc);
|
||||
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
error = AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc,
|
||||
kAutoGenerateReturnID, kAnyTransactionID, &appleEventToSend);
|
||||
|
||||
AEDisposeDesc(&targetDesc);
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
error = AESend(&appleEventToSend, &eventReply, kAENoReply,
|
||||
kAENormalPriority, kAEDefaultTimeout, NULL, NULL);
|
||||
|
||||
AEDisposeDesc(&appleEventToSend);
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
AEDisposeDesc(&eventReply);
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
HANDLE hToken; // handle to process token
|
||||
TOKEN_PRIVILEGES tkp; // pointer to token structure
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
|
||||
return;
|
||||
// Get the LUID for shutdown privilege.
|
||||
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
|
||||
&tkp.Privileges[0].Luid);
|
||||
|
||||
tkp.PrivilegeCount = 1; // one privilege to set
|
||||
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
// Get shutdown privilege for this process.
|
||||
|
||||
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
|
||||
(PTOKEN_PRIVILEGES) NULL, 0);
|
||||
|
||||
// Cannot test the return value of AdjustTokenPrivileges.
|
||||
|
||||
if (GetLastError() != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
if (action == SUSPEND_COMPUTER)
|
||||
SetSuspendState(false, false, false);
|
||||
else if (action == HIBERNATE_COMPUTER)
|
||||
SetSuspendState(true, false, false);
|
||||
else
|
||||
InitiateSystemShutdownA(0, QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete.").toLocal8Bit().data(), 10, true, false);
|
||||
|
||||
// Disable shutdown privilege.
|
||||
tkp.Privileges[0].Attributes = 0;
|
||||
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
|
||||
(PTOKEN_PRIVILEGES) NULL, 0);
|
||||
#endif
|
||||
}
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
// Get screen center
|
||||
QPoint misc::screenCenter(QWidget *win)
|
||||
{
|
||||
int scrn = 0;
|
||||
const QWidget *w = win->window();
|
||||
|
||||
if (w)
|
||||
scrn = QApplication::desktop()->screenNumber(w);
|
||||
else if (QApplication::desktop()->isVirtualDesktop())
|
||||
scrn = QApplication::desktop()->screenNumber(QCursor::pos());
|
||||
else
|
||||
scrn = QApplication::desktop()->screenNumber(win);
|
||||
|
||||
QRect desk(QApplication::desktop()->availableGeometry(scrn));
|
||||
return QPoint((desk.width() - win->frameGeometry().width()) / 2, (desk.height() - win->frameGeometry().height()) / 2);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Detects the version of python by calling
|
||||
* "python --version" and parsing the output.
|
||||
*/
|
||||
int misc::pythonVersion()
|
||||
{
|
||||
static int version = -1;
|
||||
if (version < 0) {
|
||||
QProcess python_proc;
|
||||
python_proc.start("python", QStringList() << "--version", QIODevice::ReadOnly);
|
||||
if (!python_proc.waitForFinished()) return -1;
|
||||
if (python_proc.exitCode() < 0) return -1;
|
||||
QByteArray output = python_proc.readAllStandardOutput();
|
||||
if (output.isEmpty())
|
||||
output = python_proc.readAllStandardError();
|
||||
const QByteArray version_str = output.split(' ').last();
|
||||
qDebug() << "Python version is:" << version_str.trimmed();
|
||||
if (version_str.startsWith("3."))
|
||||
version = 3;
|
||||
else
|
||||
version = 2;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
// return best userfriendly storage unit (B, KiB, MiB, GiB, TiB)
|
||||
// use Binary prefix standards from IEC 60027-2
|
||||
// see http://en.wikipedia.org/wiki/Kilobyte
|
||||
// value must be given in bytes
|
||||
// to send numbers instead of strings with suffixes
|
||||
QString misc::friendlyUnit(qreal val, bool is_speed)
|
||||
{
|
||||
if (val < 0)
|
||||
return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
|
||||
int i = 0;
|
||||
while(val >= 1024. && i++<6)
|
||||
val /= 1024.;
|
||||
QString ret;
|
||||
if (i == 0)
|
||||
ret = QString::number((long)val) + " " + QCoreApplication::translate("misc", units[0].source, units[0].comment);
|
||||
else
|
||||
ret = accurateDoubleToString(val, 1) + " " + QCoreApplication::translate("misc", units[i].source, units[i].comment);
|
||||
if (is_speed)
|
||||
ret += QCoreApplication::translate("misc", "/s", "per second");
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool misc::isPreviewable(const QString& extension)
|
||||
{
|
||||
static QSet<QString> multimedia_extensions;
|
||||
if (multimedia_extensions.empty()) {
|
||||
multimedia_extensions.insert("3GP");
|
||||
multimedia_extensions.insert("AAC");
|
||||
multimedia_extensions.insert("AC3");
|
||||
multimedia_extensions.insert("AIF");
|
||||
multimedia_extensions.insert("AIFC");
|
||||
multimedia_extensions.insert("AIFF");
|
||||
multimedia_extensions.insert("ASF");
|
||||
multimedia_extensions.insert("AU");
|
||||
multimedia_extensions.insert("AVI");
|
||||
multimedia_extensions.insert("FLAC");
|
||||
multimedia_extensions.insert("FLV");
|
||||
multimedia_extensions.insert("M3U");
|
||||
multimedia_extensions.insert("M4A");
|
||||
multimedia_extensions.insert("M4P");
|
||||
multimedia_extensions.insert("M4V");
|
||||
multimedia_extensions.insert("MID");
|
||||
multimedia_extensions.insert("MKV");
|
||||
multimedia_extensions.insert("MOV");
|
||||
multimedia_extensions.insert("MP2");
|
||||
multimedia_extensions.insert("MP3");
|
||||
multimedia_extensions.insert("MP4");
|
||||
multimedia_extensions.insert("MPC");
|
||||
multimedia_extensions.insert("MPE");
|
||||
multimedia_extensions.insert("MPEG");
|
||||
multimedia_extensions.insert("MPG");
|
||||
multimedia_extensions.insert("MPP");
|
||||
multimedia_extensions.insert("OGG");
|
||||
multimedia_extensions.insert("OGM");
|
||||
multimedia_extensions.insert("OGV");
|
||||
multimedia_extensions.insert("QT");
|
||||
multimedia_extensions.insert("RA");
|
||||
multimedia_extensions.insert("RAM");
|
||||
multimedia_extensions.insert("RM");
|
||||
multimedia_extensions.insert("RMV");
|
||||
multimedia_extensions.insert("RMVB");
|
||||
multimedia_extensions.insert("SWA");
|
||||
multimedia_extensions.insert("SWF");
|
||||
multimedia_extensions.insert("VOB");
|
||||
multimedia_extensions.insert("WAV");
|
||||
multimedia_extensions.insert("WMA");
|
||||
multimedia_extensions.insert("WMV");
|
||||
}
|
||||
|
||||
if (extension.isEmpty())
|
||||
return false;
|
||||
|
||||
return multimedia_extensions.contains(extension.toUpper());
|
||||
}
|
||||
|
||||
QString misc::bcLinkToMagnet(QString bc_link)
|
||||
{
|
||||
QByteArray raw_bc = bc_link.toUtf8();
|
||||
raw_bc = raw_bc.mid(8); // skip bc://bt/
|
||||
raw_bc = QByteArray::fromBase64(raw_bc); // Decode base64
|
||||
// Format is now AA/url_encoded_filename/size_bytes/info_hash/ZZ
|
||||
QStringList parts = QString(raw_bc).split("/");
|
||||
if (parts.size() != 5) return QString::null;
|
||||
QString filename = parts.at(1);
|
||||
QString hash = parts.at(3);
|
||||
QString magnet = "magnet:?xt=urn:btih:" + hash;
|
||||
magnet += "&dn=" + filename;
|
||||
return magnet;
|
||||
}
|
||||
|
||||
QString misc::magnetUriToName(const QString& magnet_uri)
|
||||
{
|
||||
add_torrent_params p;
|
||||
error_code ec;
|
||||
parse_magnet_uri(magnet_uri.toUtf8().constData(), p, ec);
|
||||
|
||||
if (ec)
|
||||
return QString::null;
|
||||
return toQStringU(p.name);
|
||||
}
|
||||
|
||||
QList<QUrl> misc::magnetUriToTrackers(const QString& magnet_uri)
|
||||
{
|
||||
QList<QUrl> trackers;
|
||||
add_torrent_params p;
|
||||
error_code ec;
|
||||
parse_magnet_uri(magnet_uri.toUtf8().constData(), p, ec);
|
||||
|
||||
if (ec)
|
||||
return trackers;
|
||||
|
||||
for (std::vector<std::string>::const_iterator i = p.trackers.begin(), e = p.trackers.end(); i != e; ++i)
|
||||
trackers.push_back(QUrl(toQStringU(*i)));
|
||||
|
||||
return trackers;
|
||||
}
|
||||
|
||||
QString misc::magnetUriToHash(const QString& magnet_uri)
|
||||
{
|
||||
add_torrent_params p;
|
||||
error_code ec;
|
||||
parse_magnet_uri(magnet_uri.toUtf8().constData(), p, ec);
|
||||
|
||||
if (ec)
|
||||
return QString::null;
|
||||
return toQString(p.info_hash);
|
||||
}
|
||||
|
||||
// Take a number of seconds and return an user-friendly
|
||||
// time duration like "1d 2h 10m".
|
||||
QString misc::userFriendlyDuration(qlonglong seconds)
|
||||
{
|
||||
if (seconds < 0 || seconds >= MAX_ETA)
|
||||
return QString::fromUtf8("∞");
|
||||
if (seconds == 0)
|
||||
return "0";
|
||||
if (seconds < 60)
|
||||
return QCoreApplication::translate("misc", "< 1m", "< 1 minute");
|
||||
int minutes = seconds / 60;
|
||||
if (minutes < 60)
|
||||
return QCoreApplication::translate("misc", "%1m","e.g: 10minutes").arg(QString::number(minutes));
|
||||
int hours = minutes / 60;
|
||||
minutes = minutes - hours * 60;
|
||||
if (hours < 24)
|
||||
return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours)).arg(QString::number(minutes));
|
||||
int days = hours / 24;
|
||||
hours = hours - days * 24;
|
||||
if (days < 100)
|
||||
return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2days 10hours").arg(QString::number(days)).arg(QString::number(hours));
|
||||
return QString::fromUtf8("∞");
|
||||
}
|
||||
|
||||
QString misc::getUserIDString()
|
||||
{
|
||||
QString uid = "0";
|
||||
#ifdef Q_OS_WIN
|
||||
char buffer[UNLEN + 1] = {0};
|
||||
DWORD buffer_len = UNLEN + 1;
|
||||
if (!GetUserNameA(buffer, &buffer_len))
|
||||
uid = QString(buffer);
|
||||
#else
|
||||
uid = QString::number(getuid());
|
||||
#endif
|
||||
return uid;
|
||||
}
|
||||
|
||||
QStringList misc::toStringList(const QList<bool> &l)
|
||||
{
|
||||
QStringList ret;
|
||||
foreach (const bool &b, l)
|
||||
ret << (b ? "1" : "0");
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<int> misc::intListfromStringList(const QStringList &l)
|
||||
{
|
||||
QList<int> ret;
|
||||
foreach (const QString &s, l)
|
||||
ret << s.toInt();
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<bool> misc::boolListfromStringList(const QStringList &l)
|
||||
{
|
||||
QList<bool> ret;
|
||||
foreach (const QString &s, l)
|
||||
ret << (s=="1");
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool misc::isUrl(const QString &s)
|
||||
{
|
||||
const QString scheme = QUrl(s).scheme();
|
||||
QRegExp is_url("http[s]?|ftp", Qt::CaseInsensitive);
|
||||
return is_url.exactMatch(scheme);
|
||||
}
|
||||
|
||||
QString misc::parseHtmlLinks(const QString &raw_text)
|
||||
{
|
||||
QString result = raw_text;
|
||||
static QRegExp reURL(
|
||||
"(\\s|^)" //start with whitespace or beginning of line
|
||||
"("
|
||||
"(" //case 1 -- URL with scheme
|
||||
"(http(s?))\\://" //start with scheme
|
||||
"([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
|
||||
"([a-zA-Z0-9\\?%=&/_\\.:#;-]+)" // everything to 1st non-URI char, must be at least one char after the previous dot (cannot use ".*" because it can be too greedy)
|
||||
")"
|
||||
"|"
|
||||
"(" //case 2a -- no scheme, contains common TLD example.com
|
||||
"([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
|
||||
"(?=" // must be followed by TLD
|
||||
"AERO|aero|" //N.B. assertions are non-capturing
|
||||
"ARPA|arpa|"
|
||||
"ASIA|asia|"
|
||||
"BIZ|biz|"
|
||||
"CAT|cat|"
|
||||
"COM|com|"
|
||||
"COOP|coop|"
|
||||
"EDU|edu|"
|
||||
"GOV|gov|"
|
||||
"INFO|info|"
|
||||
"INT|int|"
|
||||
"JOBS|jobs|"
|
||||
"MIL|mil|"
|
||||
"MOBI|mobi|"
|
||||
"MUSEUM|museum|"
|
||||
"NAME|name|"
|
||||
"NET|net|"
|
||||
"ORG|org|"
|
||||
"PRO|pro|"
|
||||
"RO|ro|"
|
||||
"RU|ru|"
|
||||
"TEL|tel|"
|
||||
"TRAVEL|travel"
|
||||
")"
|
||||
"([a-zA-Z0-9\\?%=&/_\\.:#;-]+)" // everything to 1st non-URI char, must be at least one char after the previous dot (cannot use ".*" because it can be too greedy)
|
||||
")"
|
||||
"|"
|
||||
"(" // case 2b no scheme, no TLD, must have at least 2 aphanum strings plus uncommon TLD string --> del.icio.us
|
||||
"([a-zA-Z0-9_-]+\\.) {2,}" //2 or more domainpart. --> del.icio.
|
||||
"[a-zA-Z]{2,}" //one ab (2 char or longer) --> us
|
||||
"([a-zA-Z0-9\\?%=&/_\\.:#;-]*)" // everything to 1st non-URI char, maybe nothing in case of del.icio.us/path
|
||||
")"
|
||||
")"
|
||||
);
|
||||
|
||||
|
||||
// Capture links
|
||||
result.replace(reURL, "\\1<a href=\"\\2\">\\2</a>");
|
||||
|
||||
// Capture links without scheme
|
||||
static QRegExp reNoScheme("<a\\s+href=\"(?!http(s?))([a-zA-Z0-9\\?%=&/_\\.-:#]+)\\s*\">");
|
||||
result.replace(reNoScheme, "<a href=\"http://\\1\">");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString misc::toQString(time_t t)
|
||||
{
|
||||
return QDateTime::fromTime_t(t).toString(Qt::DefaultLocaleLongDate);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
bool misc::naturalSort(QString left, QString right, bool &result) // uses lessThan comparison
|
||||
{ // Return value indicates if functions was successful
|
||||
// result argument will contain actual comparison result if function was successful
|
||||
int posL = 0;
|
||||
int posR = 0;
|
||||
do {
|
||||
for (;; ) {
|
||||
if (posL == left.size() || posR == right.size())
|
||||
return false; // No data
|
||||
|
||||
QChar leftChar = left.at(posL);
|
||||
QChar rightChar = right.at(posR);
|
||||
bool leftCharIsDigit = leftChar.isDigit();
|
||||
bool rightCharIsDigit = rightChar.isDigit();
|
||||
if (leftCharIsDigit != rightCharIsDigit)
|
||||
return false; // Digit positions mismatch
|
||||
|
||||
if (leftCharIsDigit)
|
||||
break; // Both are digit, break this loop and compare numbers
|
||||
|
||||
if (leftChar != rightChar)
|
||||
return false; // Strings' subsets before digit do not match
|
||||
|
||||
++posL;
|
||||
++posR;
|
||||
}
|
||||
|
||||
QString temp;
|
||||
while (posL < left.size()) {
|
||||
if (left.at(posL).isDigit())
|
||||
temp += left.at(posL);
|
||||
else
|
||||
break;
|
||||
posL++;
|
||||
}
|
||||
int numL = temp.toInt();
|
||||
temp.clear();
|
||||
|
||||
while (posR < right.size()) {
|
||||
if (right.at(posR).isDigit())
|
||||
temp += right.at(posR);
|
||||
else
|
||||
break;
|
||||
posR++;
|
||||
}
|
||||
int numR = temp.toInt();
|
||||
|
||||
if (numL != numR) {
|
||||
result = (numL < numR);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Strings + digits do match and we haven't hit string end
|
||||
// Do another round
|
||||
|
||||
} while (true);
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// to send numbers instead of strings with suffixes
|
||||
QString misc::accurateDoubleToString(const double &n, const int &precision)
|
||||
{
|
||||
/* HACK because QString rounds up. Eg QString::number(0.999*100.0, 'f' ,1) == 99.9
|
||||
** but QString::number(0.9999*100.0, 'f' ,1) == 100.0 The problem manifests when
|
||||
** the number has more digits after the decimal than we want AND the digit after
|
||||
** our 'wanted' is >= 5. In this case our last digit gets rounded up. So for each
|
||||
** precision we add an extra 0 behind 1 in the below algorithm. */
|
||||
|
||||
double prec = std::pow(10.0, precision);
|
||||
return QLocale::system().toString(std::floor(n * prec) / prec, 'f', precision);
|
||||
}
|
||||
|
||||
// Implements constant-time comparison to protect against timing attacks
|
||||
// Taken from https://crackstation.net/hashing-security.htm
|
||||
bool misc::slowEquals(const QByteArray &a, const QByteArray &b)
|
||||
{
|
||||
int lengthA = a.length();
|
||||
int lengthB = b.length();
|
||||
|
||||
int diff = lengthA ^ lengthB;
|
||||
for(int i = 0; i < lengthA && i < lengthB; i++)
|
||||
diff |= a[i] ^ b[i];
|
||||
|
||||
return (diff == 0);
|
||||
}
|
||||
|
||||
void misc::loadBencodedFile(const QString &filename, std::vector<char> &buffer, libtorrent::lazy_entry &entry, libtorrent::error_code &ec)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) return;
|
||||
const qint64 content_size = file.bytesAvailable();
|
||||
if (content_size <= 0) return;
|
||||
buffer.resize(content_size);
|
||||
file.read(&buffer[0], content_size);
|
||||
// bdecode
|
||||
lazy_bdecode(&buffer[0], &buffer[0] + buffer.size(), entry, ec);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Trick to get a portable sleep() function
|
||||
class SleeperThread: public QThread {
|
||||
public:
|
||||
static void msleep(unsigned long msecs)
|
||||
{
|
||||
QThread::msleep(msecs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void misc::msleep(unsigned long msecs)
|
||||
{
|
||||
SleeperThread::msleep(msecs);
|
||||
}
|
||||
119
src/core/misc.h
Normal file
119
src/core/misc.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef MISC_H
|
||||
#define MISC_H
|
||||
|
||||
#include <vector>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <ctime>
|
||||
#include <QPoint>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#ifndef DISABLE_GUI
|
||||
#include <QIcon>
|
||||
#endif
|
||||
|
||||
#include <libtorrent/version.hpp>
|
||||
#include <libtorrent/error_code.hpp>
|
||||
|
||||
namespace libtorrent {
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
class big_number;
|
||||
typedef big_number sha1_hash;
|
||||
#else
|
||||
class sha1_hash;
|
||||
#endif
|
||||
struct lazy_entry;
|
||||
}
|
||||
|
||||
const qlonglong MAX_ETA = 8640000;
|
||||
enum shutDownAction { NO_SHUTDOWN, SHUTDOWN_COMPUTER, SUSPEND_COMPUTER, HIBERNATE_COMPUTER };
|
||||
|
||||
/* Miscellaneaous functions that can be useful */
|
||||
namespace misc
|
||||
{
|
||||
QString toQString(const std::string &str);
|
||||
QString toQString(const char* str);
|
||||
QString toQStringU(const std::string &str);
|
||||
QString toQStringU(const char* str);
|
||||
QString toQString(const libtorrent::sha1_hash &hash);
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
void shutdownComputer(shutDownAction action = SHUTDOWN_COMPUTER);
|
||||
#endif
|
||||
|
||||
QString parseHtmlLinks(const QString &raw_text);
|
||||
|
||||
bool isUrl(const QString &s);
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
// Get screen center
|
||||
QPoint screenCenter(QWidget *win);
|
||||
#endif
|
||||
int pythonVersion();
|
||||
// return best userfriendly storage unit (B, KiB, MiB, GiB, TiB)
|
||||
// use Binary prefix standards from IEC 60027-2
|
||||
// see http://en.wikipedia.org/wiki/Kilobyte
|
||||
// value must be given in bytes
|
||||
QString friendlyUnit(qreal val, bool is_speed = false);
|
||||
bool isPreviewable(const QString& extension);
|
||||
QString magnetUriToName(const QString& magnet_uri);
|
||||
QString magnetUriToHash(const QString& magnet_uri);
|
||||
QList<QUrl> magnetUriToTrackers(const QString& magnet_uri);
|
||||
QString bcLinkToMagnet(QString bc_link);
|
||||
// Take a number of seconds and return an user-friendly
|
||||
// time duration like "1d 2h 10m".
|
||||
QString userFriendlyDuration(qlonglong seconds);
|
||||
QString getUserIDString();
|
||||
|
||||
// Convert functions
|
||||
QStringList toStringList(const QList<bool> &l);
|
||||
QList<int> intListfromStringList(const QStringList &l);
|
||||
QList<bool> boolListfromStringList(const QStringList &l);
|
||||
|
||||
QString toQString(time_t t);
|
||||
QString accurateDoubleToString(const double &n, const int &precision);
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
bool naturalSort(QString left, QString right, bool& result);
|
||||
#endif
|
||||
|
||||
// Implements constant-time comparison to protect against timing attacks
|
||||
// Taken from https://crackstation.net/hashing-security.htm
|
||||
bool slowEquals(const QByteArray &a, const QByteArray &b);
|
||||
void loadBencodedFile(const QString &filename, std::vector<char> &buffer, libtorrent::lazy_entry &entry, libtorrent::error_code &ec);
|
||||
|
||||
void msleep(unsigned long msecs);
|
||||
}
|
||||
|
||||
#endif
|
||||
1997
src/core/preferences.cpp
Normal file
1997
src/core/preferences.cpp
Normal file
File diff suppressed because it is too large
Load Diff
489
src/core/preferences.h
Normal file
489
src/core/preferences.h
Normal file
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
* Copyright (C) 2014 sledgehammer999
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
* Contact : hammered999@gmail.com
|
||||
*/
|
||||
|
||||
#ifndef PREFERENCES_H
|
||||
#define PREFERENCES_H
|
||||
|
||||
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
#include <QReadWriteLock>
|
||||
#include <QNetworkCookie>
|
||||
#include <QVariant>
|
||||
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
enum scheduler_days { EVERY_DAY, WEEK_DAYS, WEEK_ENDS, MON, TUE, WED, THU, FRI, SAT, SUN };
|
||||
enum maxRatioAction {PAUSE_ACTION, REMOVE_ACTION};
|
||||
namespace Proxy {
|
||||
enum ProxyType {HTTP=1, SOCKS5=2, HTTP_PW=3, SOCKS5_PW=4, SOCKS4=5};
|
||||
}
|
||||
namespace TrayIcon {
|
||||
enum Style { NORMAL = 0, MONO_DARK, MONO_LIGHT };
|
||||
}
|
||||
namespace DNS {
|
||||
enum Service { DYNDNS, NOIP, NONE = -1 };
|
||||
}
|
||||
|
||||
class Preferences : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Preferences)
|
||||
|
||||
private:
|
||||
explicit Preferences();
|
||||
static Preferences* m_instance;
|
||||
QHash<QString, QVariant> m_data;
|
||||
bool dirty;
|
||||
QTimer timer;
|
||||
mutable QReadWriteLock lock;
|
||||
const QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
void setValue(const QString &key, const QVariant &value);
|
||||
|
||||
public slots:
|
||||
void save();
|
||||
|
||||
public:
|
||||
static Preferences* instance();
|
||||
static void drop();
|
||||
~Preferences();
|
||||
|
||||
// General options
|
||||
QString getLocale() const;
|
||||
void setLocale(const QString &locale);
|
||||
bool useProgramNotification() const;
|
||||
void useProgramNotification(bool use);
|
||||
bool deleteTorrentFilesAsDefault() const;
|
||||
void setDeleteTorrentFilesAsDefault(bool del);
|
||||
bool confirmOnExit() const;
|
||||
void setConfirmOnExit(bool confirm);
|
||||
bool speedInTitleBar() const;
|
||||
void showSpeedInTitleBar(bool show);
|
||||
bool useAlternatingRowColors() const;
|
||||
void setAlternatingRowColors(bool b);
|
||||
bool useRandomPort() const;
|
||||
void setRandomPort(bool b);
|
||||
bool systrayIntegration() const;
|
||||
void setSystrayIntegration(bool enabled);
|
||||
bool isToolbarDisplayed() const;
|
||||
void setToolbarDisplayed(bool displayed);
|
||||
bool minimizeToTray() const;
|
||||
void setMinimizeToTray(bool b);
|
||||
bool closeToTray() const;
|
||||
void setCloseToTray(bool b);
|
||||
bool startMinimized() const;
|
||||
void setStartMinimized(bool b);
|
||||
bool isSplashScreenDisabled() const;
|
||||
void setSplashScreenDisabled(bool b);
|
||||
bool preventFromSuspend() const;
|
||||
void setPreventFromSuspend(bool b);
|
||||
#ifdef Q_OS_WIN
|
||||
bool WinStartup() const;
|
||||
void setWinStartup(bool b);
|
||||
#endif
|
||||
|
||||
// Downloads
|
||||
QString getSavePath() const;
|
||||
void setSavePath(const QString &save_path);
|
||||
bool isTempPathEnabled() const;
|
||||
void setTempPathEnabled(bool enabled);
|
||||
QString getTempPath() const;
|
||||
void setTempPath(const QString &path);
|
||||
bool useIncompleteFilesExtension() const;
|
||||
void useIncompleteFilesExtension(bool enabled);
|
||||
bool appendTorrentLabel() const;
|
||||
void setAppendTorrentLabel(bool b);
|
||||
QString lastLocationPath() const;
|
||||
void setLastLocationPath(const QString &path);
|
||||
bool preAllocateAllFiles() const;
|
||||
void preAllocateAllFiles(bool enabled);
|
||||
bool useAdditionDialog() const;
|
||||
void useAdditionDialog(bool b);
|
||||
bool additionDialogFront() const;
|
||||
void additionDialogFront(bool b);
|
||||
bool addTorrentsInPause() const;
|
||||
void addTorrentsInPause(bool b);
|
||||
QStringList getScanDirs() const;
|
||||
void setScanDirs(const QStringList &dirs);
|
||||
QList<bool> getDownloadInScanDirs() const;
|
||||
void setDownloadInScanDirs(const QList<bool> &list);
|
||||
QString getScanDirsLastPath() const;
|
||||
void setScanDirsLastPath(const QString &path);
|
||||
bool isTorrentExportEnabled() const;
|
||||
QString getTorrentExportDir() const;
|
||||
void setTorrentExportDir(QString path);
|
||||
bool isFinishedTorrentExportEnabled() const;
|
||||
QString getFinishedTorrentExportDir() const;
|
||||
void setFinishedTorrentExportDir(QString path);
|
||||
bool isMailNotificationEnabled() const;
|
||||
void setMailNotificationEnabled(bool enabled);
|
||||
QString getMailNotificationEmail() const;
|
||||
void setMailNotificationEmail(const QString &mail);
|
||||
QString getMailNotificationSMTP() const;
|
||||
void setMailNotificationSMTP(const QString &smtp_server);
|
||||
bool getMailNotificationSMTPSSL() const;
|
||||
void setMailNotificationSMTPSSL(bool use);
|
||||
bool getMailNotificationSMTPAuth() const;
|
||||
void setMailNotificationSMTPAuth(bool use);
|
||||
QString getMailNotificationSMTPUsername() const;
|
||||
void setMailNotificationSMTPUsername(const QString &username);
|
||||
QString getMailNotificationSMTPPassword() const;
|
||||
void setMailNotificationSMTPPassword(const QString &password);
|
||||
int getActionOnDblClOnTorrentDl() const;
|
||||
void setActionOnDblClOnTorrentDl(int act);
|
||||
int getActionOnDblClOnTorrentFn() const;
|
||||
void setActionOnDblClOnTorrentFn(int act);
|
||||
|
||||
// Connection options
|
||||
int getSessionPort() const;
|
||||
void setSessionPort(int port);
|
||||
bool isUPnPEnabled() const;
|
||||
void setUPnPEnabled(bool enabled);
|
||||
int getGlobalDownloadLimit() const;
|
||||
void setGlobalDownloadLimit(int limit);
|
||||
int getGlobalUploadLimit() const;
|
||||
void setGlobalUploadLimit(int limit);
|
||||
int getAltGlobalDownloadLimit() const;
|
||||
void setAltGlobalDownloadLimit(int limit);
|
||||
int getAltGlobalUploadLimit() const;
|
||||
void setAltGlobalUploadLimit(int limit);
|
||||
bool isAltBandwidthEnabled() const;
|
||||
void setAltBandwidthEnabled(bool enabled);
|
||||
bool isSchedulerEnabled() const;
|
||||
void setSchedulerEnabled(bool enabled);
|
||||
QTime getSchedulerStartTime() const;
|
||||
void setSchedulerStartTime(const QTime &time);
|
||||
QTime getSchedulerEndTime() const;
|
||||
void setSchedulerEndTime(const QTime &time);
|
||||
scheduler_days getSchedulerDays() const;
|
||||
void setSchedulerDays(scheduler_days days);
|
||||
|
||||
// Proxy options
|
||||
bool isProxyEnabled() const;
|
||||
bool isProxyAuthEnabled() const;
|
||||
void setProxyAuthEnabled(bool enabled);
|
||||
QString getProxyIp() const;
|
||||
void setProxyIp(const QString &ip);
|
||||
unsigned short getProxyPort() const;
|
||||
void setProxyPort(unsigned short port);
|
||||
QString getProxyUsername() const;
|
||||
void setProxyUsername(const QString &username);
|
||||
QString getProxyPassword() const;
|
||||
void setProxyPassword(const QString &password);
|
||||
int getProxyType() const;
|
||||
void setProxyType(int type);
|
||||
bool proxyPeerConnections() const;
|
||||
void setProxyPeerConnections(bool enabled);
|
||||
#if LIBTORRENT_VERSION_NUM >= 10000
|
||||
bool getForceProxy() const;
|
||||
void setForceProxy(bool enabled);
|
||||
#endif
|
||||
|
||||
// Bittorrent options
|
||||
int getMaxConnecs() const;
|
||||
void setMaxConnecs(int val);
|
||||
int getMaxConnecsPerTorrent() const;
|
||||
void setMaxConnecsPerTorrent(int val);
|
||||
int getMaxUploads() const;
|
||||
void setMaxUploads(int val);
|
||||
int getMaxUploadsPerTorrent() const;
|
||||
void setMaxUploadsPerTorrent(int val);
|
||||
bool isuTPEnabled() const;
|
||||
void setuTPEnabled(bool enabled);
|
||||
bool isuTPRateLimited() const;
|
||||
void setuTPRateLimited(bool enabled);
|
||||
bool isDHTEnabled() const;
|
||||
void setDHTEnabled(bool enabled);
|
||||
bool isPeXEnabled() const;
|
||||
void setPeXEnabled(bool enabled);
|
||||
bool isLSDEnabled() const;
|
||||
void setLSDEnabled(bool enabled);
|
||||
int getEncryptionSetting() const;
|
||||
void setEncryptionSetting(int val);
|
||||
qreal getGlobalMaxRatio() const;
|
||||
void setGlobalMaxRatio(qreal ratio);
|
||||
int getMaxRatioAction() const;
|
||||
void setMaxRatioAction(int act);
|
||||
|
||||
// IP Filter
|
||||
bool isFilteringEnabled() const;
|
||||
void setFilteringEnabled(bool enabled);
|
||||
QString getFilter() const;
|
||||
void setFilter(const QString &path);
|
||||
QStringList bannedIPs() const;
|
||||
void banIP(const QString &ip);
|
||||
|
||||
// Search
|
||||
bool isSearchEnabled() const;
|
||||
void setSearchEnabled(bool enabled);
|
||||
|
||||
// Execution Log
|
||||
bool isExecutionLogEnabled() const;
|
||||
void setExecutionLogEnabled(bool b);
|
||||
|
||||
// Queueing system
|
||||
bool isQueueingSystemEnabled() const;
|
||||
void setQueueingSystemEnabled(bool enabled);
|
||||
int getMaxActiveDownloads() const;
|
||||
void setMaxActiveDownloads(int val);
|
||||
int getMaxActiveUploads() const;
|
||||
void setMaxActiveUploads(int val);
|
||||
int getMaxActiveTorrents() const;
|
||||
void setMaxActiveTorrents(int val);
|
||||
bool ignoreSlowTorrentsForQueueing() const;
|
||||
void setIgnoreSlowTorrentsForQueueing(bool ignore);
|
||||
bool isWebUiEnabled() const;
|
||||
void setWebUiEnabled(bool enabled);
|
||||
bool isWebUiLocalAuthEnabled() const;
|
||||
void setWebUiLocalAuthEnabled(bool enabled);
|
||||
quint16 getWebUiPort() const;
|
||||
void setWebUiPort(quint16 port);
|
||||
bool useUPnPForWebUIPort() const;
|
||||
void setUPnPForWebUIPort(bool enabled);
|
||||
QString getWebUiUsername() const;
|
||||
void setWebUiUsername(const QString &username);
|
||||
QString getWebUiPassword() const;
|
||||
void setWebUiPassword(const QString &new_password);
|
||||
bool isWebUiHttpsEnabled() const;
|
||||
void setWebUiHttpsEnabled(bool enabled);
|
||||
QByteArray getWebUiHttpsCertificate() const;
|
||||
void setWebUiHttpsCertificate(const QByteArray &data);
|
||||
QByteArray getWebUiHttpsKey() const;
|
||||
void setWebUiHttpsKey(const QByteArray &data);
|
||||
bool isDynDNSEnabled() const;
|
||||
void setDynDNSEnabled(bool enabled);
|
||||
DNS::Service getDynDNSService() const;
|
||||
void setDynDNSService(int service);
|
||||
QString getDynDomainName() const;
|
||||
void setDynDomainName(const QString &name);
|
||||
QString getDynDNSUsername() const;
|
||||
void setDynDNSUsername(const QString &username);
|
||||
QString getDynDNSPassword() const;
|
||||
void setDynDNSPassword(const QString &password);
|
||||
|
||||
// Advanced settings
|
||||
void setUILockPassword(const QString &clear_password);
|
||||
void clearUILockPassword();
|
||||
QString getUILockPasswordMD5() const;
|
||||
bool isUILocked() const;
|
||||
void setUILocked(bool locked);
|
||||
bool isAutoRunEnabled() const;
|
||||
void setAutoRunEnabled(bool enabled);
|
||||
QString getAutoRunProgram() const;
|
||||
void setAutoRunProgram(const QString &program);
|
||||
bool shutdownWhenDownloadsComplete() const;
|
||||
void setShutdownWhenDownloadsComplete(bool shutdown);
|
||||
bool suspendWhenDownloadsComplete() const;
|
||||
void setSuspendWhenDownloadsComplete(bool suspend);
|
||||
bool hibernateWhenDownloadsComplete() const;
|
||||
void setHibernateWhenDownloadsComplete(bool hibernate);
|
||||
bool shutdownqBTWhenDownloadsComplete() const;
|
||||
void setShutdownqBTWhenDownloadsComplete(bool shutdown);
|
||||
uint diskCacheSize() const;
|
||||
void setDiskCacheSize(uint size);
|
||||
uint diskCacheTTL() const;
|
||||
void setDiskCacheTTL(uint ttl);
|
||||
bool osCache() const;
|
||||
void setOsCache(bool enable);
|
||||
uint saveResumeDataInterval() const;
|
||||
void setSaveResumeDataInterval(uint m);
|
||||
uint outgoingPortsMin() const;
|
||||
void setOutgoingPortsMin(uint val);
|
||||
uint outgoingPortsMax() const;
|
||||
void setOutgoingPortsMax(uint val);
|
||||
bool ignoreLimitsOnLAN() const;
|
||||
void ignoreLimitsOnLAN(bool ignore);
|
||||
bool includeOverheadInLimits() const;
|
||||
void includeOverheadInLimits(bool include);
|
||||
bool trackerExchangeEnabled() const;
|
||||
void setTrackerExchangeEnabled(bool enable);
|
||||
bool recheckTorrentsOnCompletion() const;
|
||||
void recheckTorrentsOnCompletion(bool recheck);
|
||||
unsigned int getRefreshInterval() const;
|
||||
void setRefreshInterval(uint interval);
|
||||
bool resolvePeerCountries() const;
|
||||
void resolvePeerCountries(bool resolve);
|
||||
bool resolvePeerHostNames() const;
|
||||
void resolvePeerHostNames(bool resolve);
|
||||
int getMaxHalfOpenConnections() const;
|
||||
void setMaxHalfOpenConnections(int value);
|
||||
QString getNetworkInterface() const;
|
||||
void setNetworkInterface(const QString& iface);
|
||||
QString getNetworkInterfaceName() const;
|
||||
void setNetworkInterfaceName(const QString& iface);
|
||||
bool getListenIPv6() const;
|
||||
void setListenIPv6(bool enable);
|
||||
QString getNetworkAddress() const;
|
||||
void setNetworkAddress(const QString& addr);
|
||||
bool isAnonymousModeEnabled() const;
|
||||
void enableAnonymousMode(bool enabled);
|
||||
bool isSuperSeedingEnabled() const;
|
||||
void enableSuperSeeding(bool enabled);
|
||||
bool announceToAllTrackers() const;
|
||||
void setAnnounceToAllTrackers(bool enabled);
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
||||
bool useSystemIconTheme() const;
|
||||
void useSystemIconTheme(bool enabled);
|
||||
#endif
|
||||
QStringList getTorrentLabels() const;
|
||||
void setTorrentLabels(const QStringList& labels);
|
||||
void addTorrentLabel(const QString& label);
|
||||
void removeTorrentLabel(const QString& label);
|
||||
bool recursiveDownloadDisabled() const;
|
||||
void disableRecursiveDownload(bool disable=true);
|
||||
#ifdef Q_OS_WIN
|
||||
static QString getPythonPath();
|
||||
bool neverCheckFileAssoc() const;
|
||||
void setNeverCheckFileAssoc(bool check = true);
|
||||
static bool isTorrentFileAssocSet();
|
||||
static bool isMagnetLinkAssocSet();
|
||||
static void setTorrentFileAssoc(bool set);
|
||||
static void setMagnetLinkAssoc(bool set);
|
||||
#endif
|
||||
bool isTrackerEnabled() const;
|
||||
void setTrackerEnabled(bool enabled);
|
||||
int getTrackerPort() const;
|
||||
void setTrackerPort(int port);
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
bool isUpdateCheckEnabled() const;
|
||||
void setUpdateCheckEnabled(bool enabled);
|
||||
#endif
|
||||
bool confirmTorrentDeletion() const;
|
||||
void setConfirmTorrentDeletion(bool enabled);
|
||||
TrayIcon::Style trayIconStyle() const;
|
||||
void setTrayIconStyle(TrayIcon::Style style);
|
||||
|
||||
|
||||
// Stuff that don't appear in the Options GUI but are saved
|
||||
// in the same file.
|
||||
QByteArray getAddNewTorrentDialogState() const;
|
||||
void setAddNewTorrentDialogState(const QByteArray &state);
|
||||
int getAddNewTorrentDialogPos() const;
|
||||
void setAddNewTorrentDialogPos(const int &pos);
|
||||
int getAddNewTorrentDialogWidth() const;
|
||||
void setAddNewTorrentDialogWidth(const int &width);
|
||||
bool getAddNewTorrentDialogExpanded() const;
|
||||
void setAddNewTorrentDialogExpanded(const bool expanded);
|
||||
QStringList getAddNewTorrentDialogPathHistory() const;
|
||||
void setAddNewTorrentDialogPathHistory(const QStringList &history);
|
||||
QDateTime getDNSLastUpd() const;
|
||||
void setDNSLastUpd(const QDateTime &date);
|
||||
QString getDNSLastIP() const;
|
||||
void setDNSLastIP(const QString &ip);
|
||||
bool getAcceptedLegal() const;
|
||||
void setAcceptedLegal(const bool accepted);
|
||||
QByteArray getMainGeometry() const;
|
||||
void setMainGeometry(const QByteArray &geometry);
|
||||
QByteArray getMainVSplitterState() const;
|
||||
void setMainVSplitterState(const QByteArray &state);
|
||||
QString getMainLastDir() const;
|
||||
void setMainLastDir(const QString &path);
|
||||
#ifndef DISABLE_GUI
|
||||
QSize getPrefSize(const QSize &defaultSize) const;
|
||||
void setPrefSize(const QSize &size);
|
||||
#endif
|
||||
QPoint getPrefPos() const;
|
||||
void setPrefPos(const QPoint &pos);
|
||||
QStringList getPrefHSplitterSizes() const;
|
||||
void setPrefHSplitterSizes(const QStringList &sizes);
|
||||
QByteArray getPeerListState() const;
|
||||
void setPeerListState(const QByteArray &state);
|
||||
QString getPropSplitterSizes() const;
|
||||
void setPropSplitterSizes(const QString &sizes);
|
||||
QByteArray getPropFileListState() const;
|
||||
void setPropFileListState(const QByteArray &state);
|
||||
int getPropCurTab() const;
|
||||
void setPropCurTab(const int &tab);
|
||||
bool getPropVisible() const;
|
||||
void setPropVisible(const bool visible);
|
||||
QByteArray getPropTrackerListState() const;
|
||||
void setPropTrackerListState(const QByteArray &state);
|
||||
QByteArray getRssGeometry() const;
|
||||
void setRssGeometry(const QByteArray &geometry);
|
||||
QByteArray getRssHSplitterSizes() const;
|
||||
void setRssHSplitterSizes(const QByteArray &sizes);
|
||||
QStringList getRssOpenFolders() const;
|
||||
void setRssOpenFolders(const QStringList &folders);
|
||||
QByteArray getRssHSplitterState() const;
|
||||
void setRssHSplitterState(const QByteArray &state);
|
||||
QByteArray getRssVSplitterState() const;
|
||||
void setRssVSplitterState(const QByteArray &state);
|
||||
QString getSearchColsWidth() const;
|
||||
void setSearchColsWidth(const QString &width);
|
||||
QStringList getSearchEngDisabled() const;
|
||||
void setSearchEngDisabled(const QStringList &engines);
|
||||
QString getCreateTorLastAddPath() const;
|
||||
void setCreateTorLastAddPath(const QString &path);
|
||||
QString getCreateTorLastSavePath() const;
|
||||
void setCreateTorLastSavePath(const QString &path);
|
||||
QString getCreateTorTrackers() const;
|
||||
void setCreateTorTrackers(const QString &path);
|
||||
QByteArray getCreateTorGeometry() const;
|
||||
void setCreateTorGeometry(const QByteArray &geometry);
|
||||
bool getCreateTorIgnoreRatio() const;
|
||||
void setCreateTorIgnoreRatio(const bool ignore);
|
||||
QString getTorImportLastContentDir() const;
|
||||
void setTorImportLastContentDir(const QString &path);
|
||||
QByteArray getTorImportGeometry() const;
|
||||
void setTorImportGeometry(const QByteArray &geometry);
|
||||
int getTransSelFilter() const;
|
||||
void setTransSelFilter(const int &index);
|
||||
QByteArray getTransHeaderState() const;
|
||||
void setTransHeaderState(const QByteArray &state);
|
||||
int getToolbarTextPosition() const;
|
||||
void setToolbarTextPosition(const int position);
|
||||
|
||||
// Temp code.
|
||||
// See TorrentStatistics::loadStats() for details.
|
||||
QVariantHash getStats() const;
|
||||
void removeStats();
|
||||
|
||||
//From old RssSettings class
|
||||
bool isRSSEnabled() const;
|
||||
void setRSSEnabled(const bool enabled);
|
||||
uint getRSSRefreshInterval() const;
|
||||
void setRSSRefreshInterval(const uint &interval);
|
||||
int getRSSMaxArticlesPerFeed() const;
|
||||
void setRSSMaxArticlesPerFeed(const int &nb);
|
||||
bool isRssDownloadingEnabled() const;
|
||||
void setRssDownloadingEnabled(const bool b);
|
||||
QStringList getRssFeedsUrls() const;
|
||||
void setRssFeedsUrls(const QStringList &rssFeeds);
|
||||
QStringList getRssFeedsAliases() const;
|
||||
void setRssFeedsAliases(const QStringList &rssAliases);
|
||||
QList<QByteArray> getHostNameCookies(const QString &host_name) const;
|
||||
QList<QNetworkCookie> getHostNameQNetworkCookies(const QString& host_name) const;
|
||||
void setHostNameCookies(const QString &host_name, const QList<QByteArray> &cookies);
|
||||
};
|
||||
|
||||
#endif // PREFERENCES_H
|
||||
73
src/core/qinisettings.h
Normal file
73
src/core/qinisettings.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef QINISETTINGS_H
|
||||
#define QINISETTINGS_H
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
class QIniSettings : public QSettings {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY (QIniSettings)
|
||||
|
||||
public:
|
||||
QIniSettings(const QString &organization = "qBittorrent", const QString &application = "qBittorrent", QObject *parent = 0 ):
|
||||
#ifdef Q_OS_WIN
|
||||
QSettings(QSettings::IniFormat, QSettings::UserScope, organization, application, parent)
|
||||
#else
|
||||
QSettings(organization, application, parent)
|
||||
#endif
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QIniSettings(const QString &fileName, Format format, QObject *parent = 0 ) : QSettings(fileName, format, parent) {
|
||||
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QVariant value(const QString & key, const QVariant &defaultValue = QVariant()) const {
|
||||
QString key_tmp(key);
|
||||
QVariant ret = QSettings::value(key_tmp);
|
||||
if (ret.isNull())
|
||||
return defaultValue;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void setValue(const QString &key, const QVariant &val) {
|
||||
QString key_tmp(key);
|
||||
if (format() == QSettings::NativeFormat) // Using registry, don't touch replace here
|
||||
key_tmp.replace("\\", "/");
|
||||
QSettings::setValue(key_tmp, val);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // QINISETTINGS_H
|
||||
135
src/core/qtlibtorrent/alertdispatcher.cpp
Normal file
135
src/core/qtlibtorrent/alertdispatcher.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2014 Ivan Sorokin
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : vanyacpp@gmail.com
|
||||
*/
|
||||
|
||||
#include "alertdispatcher.h"
|
||||
|
||||
#include <libtorrent/session.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <QMutexLocker>
|
||||
|
||||
const size_t DEFAULT_ALERTS_CAPACITY = 32;
|
||||
|
||||
struct QAlertDispatcher::Tag {
|
||||
Tag(QAlertDispatcher* dispatcher);
|
||||
|
||||
QAlertDispatcher* dispatcher;
|
||||
QMutex alerts_mutex;
|
||||
};
|
||||
|
||||
QAlertDispatcher::Tag::Tag(QAlertDispatcher* dispatcher)
|
||||
: dispatcher(dispatcher)
|
||||
{}
|
||||
|
||||
QAlertDispatcher::QAlertDispatcher(libtorrent::session *session, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_session(session)
|
||||
, current_tag(new Tag(this))
|
||||
, event_posted(false)
|
||||
{
|
||||
alerts.reserve(DEFAULT_ALERTS_CAPACITY);
|
||||
m_session->set_alert_dispatch(boost::bind(&QAlertDispatcher::dispatch, current_tag, _1));
|
||||
}
|
||||
|
||||
QAlertDispatcher::~QAlertDispatcher() {
|
||||
// When QAlertDispatcher is destoyed, libtorrent still can call
|
||||
// QAlertDispatcher::dispatch a few times after destruction. This is
|
||||
// handled by passing a "tag". A tag is a object that references QAlertDispatch.
|
||||
// Tag could be invalidated. So on destruction QAlertDispatcher invalidates a tag
|
||||
// and then unsubscribes from alerts. When QAlertDispatcher::dispatch is called
|
||||
// with invalid tag it simply discard an alert.
|
||||
|
||||
{
|
||||
QMutexLocker lock(¤t_tag->alerts_mutex);
|
||||
current_tag->dispatcher = 0;
|
||||
current_tag.clear();
|
||||
}
|
||||
|
||||
typedef boost::function<void (std::auto_ptr<libtorrent::alert>)> dispatch_function_t;
|
||||
m_session->set_alert_dispatch(dispatch_function_t());
|
||||
}
|
||||
|
||||
void QAlertDispatcher::getPendingAlertsNoWait(std::vector<libtorrent::alert*>& out) {
|
||||
Q_ASSERT(out.empty());
|
||||
out.reserve(DEFAULT_ALERTS_CAPACITY);
|
||||
|
||||
QMutexLocker lock(¤t_tag->alerts_mutex);
|
||||
alerts.swap(out);
|
||||
event_posted = false;
|
||||
}
|
||||
|
||||
void QAlertDispatcher::getPendingAlerts(std::vector<libtorrent::alert*>& out, unsigned long time) {
|
||||
Q_ASSERT(out.empty());
|
||||
out.reserve(DEFAULT_ALERTS_CAPACITY);
|
||||
|
||||
QMutexLocker lock(¤t_tag->alerts_mutex);
|
||||
|
||||
while (alerts.empty())
|
||||
alerts_condvar.wait(¤t_tag->alerts_mutex, time);
|
||||
|
||||
alerts.swap(out);
|
||||
event_posted = false;
|
||||
}
|
||||
|
||||
void QAlertDispatcher::dispatch(QSharedPointer<Tag> tag,
|
||||
std::auto_ptr<libtorrent::alert> alert_ptr) {
|
||||
QMutexLocker lock(&(tag->alerts_mutex));
|
||||
QAlertDispatcher* that = tag->dispatcher;
|
||||
if (!that)
|
||||
return;
|
||||
|
||||
bool was_empty = that->alerts.empty();
|
||||
|
||||
that->alerts.push_back(alert_ptr.get());
|
||||
alert_ptr.release();
|
||||
|
||||
if (was_empty)
|
||||
that->alerts_condvar.wakeAll();
|
||||
|
||||
that->enqueueToMainThread();
|
||||
|
||||
Q_ASSERT(that->current_tag == tag);
|
||||
}
|
||||
|
||||
void QAlertDispatcher::enqueueToMainThread() {
|
||||
if (!event_posted) {
|
||||
event_posted = true;
|
||||
QMetaObject::invokeMethod(this, "deliverSignal", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void QAlertDispatcher::deliverSignal() {
|
||||
emit alertsReceived();
|
||||
|
||||
QMutexLocker lock(¤t_tag->alerts_mutex);
|
||||
event_posted = false;
|
||||
|
||||
if (!alerts.empty())
|
||||
enqueueToMainThread();
|
||||
}
|
||||
80
src/core/qtlibtorrent/alertdispatcher.h
Normal file
80
src/core/qtlibtorrent/alertdispatcher.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2014 Ivan Sorokin
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : vanyacpp@gmail.com
|
||||
*/
|
||||
|
||||
#ifndef ALERTDISPATCHER_H
|
||||
#define ALERTDISPATCHER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QAtomicPointer>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace libtorrent {
|
||||
class session;
|
||||
class alert;
|
||||
}
|
||||
|
||||
class QAlertDispatcher : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(QAlertDispatcher)
|
||||
|
||||
struct Tag;
|
||||
|
||||
public:
|
||||
QAlertDispatcher(libtorrent::session *session, QObject* parent);
|
||||
~QAlertDispatcher();
|
||||
|
||||
void getPendingAlertsNoWait(std::vector<libtorrent::alert*>&);
|
||||
void getPendingAlerts(std::vector<libtorrent::alert*>&, unsigned long time = ULONG_MAX);
|
||||
|
||||
signals:
|
||||
void alertsReceived();
|
||||
|
||||
private:
|
||||
static void dispatch(QSharedPointer<Tag>,
|
||||
std::auto_ptr<libtorrent::alert>);
|
||||
void enqueueToMainThread();
|
||||
|
||||
private slots:
|
||||
void deliverSignal();
|
||||
|
||||
private:
|
||||
libtorrent::session *m_session;
|
||||
QWaitCondition alerts_condvar;
|
||||
std::vector<libtorrent::alert*> alerts;
|
||||
QSharedPointer<Tag> current_tag;
|
||||
bool event_posted;
|
||||
};
|
||||
|
||||
#endif // ALERTDISPATCHER_H
|
||||
79
src/core/qtlibtorrent/bandwidthscheduler.h
Normal file
79
src/core/qtlibtorrent/bandwidthscheduler.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#ifndef BANDWIDTHSCHEDULER_H
|
||||
#define BANDWIDTHSCHEDULER_H
|
||||
|
||||
#include <QTimer>
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include "preferences.h"
|
||||
#include <iostream>
|
||||
|
||||
|
||||
class BandwidthScheduler: public QTimer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BandwidthScheduler(QObject *parent): QTimer(parent) {
|
||||
Q_ASSERT(Preferences::instance()->isSchedulerEnabled());
|
||||
// Signal shot, we call start() again manually
|
||||
setSingleShot(true);
|
||||
// Connect Signals/Slots
|
||||
connect(this, SIGNAL(timeout()), this, SLOT(start()));
|
||||
}
|
||||
|
||||
public slots:
|
||||
void start() {
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
Q_ASSERT(pref->isSchedulerEnabled());
|
||||
bool alt_bw_enabled = pref->isAltBandwidthEnabled();
|
||||
|
||||
QTime start = pref->getSchedulerStartTime();
|
||||
QTime end = pref->getSchedulerEndTime();
|
||||
QTime now = QTime::currentTime();
|
||||
int sched_days = pref->getSchedulerDays();
|
||||
int day = QDateTime::currentDateTime().toLocalTime().date().dayOfWeek();
|
||||
bool new_mode = false;
|
||||
bool reverse = false;
|
||||
|
||||
if (start > end) {
|
||||
QTime temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
reverse = true;
|
||||
}
|
||||
|
||||
if (start <= now && end >= now) {
|
||||
switch(sched_days) {
|
||||
case EVERY_DAY:
|
||||
new_mode = true;
|
||||
break;
|
||||
case WEEK_ENDS:
|
||||
if (day == 6 || day == 7)
|
||||
new_mode = true;
|
||||
break;
|
||||
case WEEK_DAYS:
|
||||
if (day != 6 && day != 7)
|
||||
new_mode = true;
|
||||
break;
|
||||
default:
|
||||
if (day == sched_days - 2)
|
||||
new_mode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (reverse)
|
||||
new_mode = !new_mode;
|
||||
|
||||
if (new_mode != alt_bw_enabled)
|
||||
emit switchToAlternativeMode(new_mode);
|
||||
|
||||
// Timeout regularly to accomodate for external system clock changes
|
||||
// eg from the user or from a timesync utility
|
||||
QTimer::start(1500);
|
||||
}
|
||||
|
||||
signals:
|
||||
void switchToAlternativeMode(bool alternative);
|
||||
};
|
||||
|
||||
#endif // BANDWIDTHSCHEDULER_H
|
||||
394
src/core/qtlibtorrent/filterparserthread.cpp
Normal file
394
src/core/qtlibtorrent/filterparserthread.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "filterparserthread.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <libtorrent/session.hpp>
|
||||
#include <libtorrent/ip_filter.hpp>
|
||||
|
||||
FilterParserThread::FilterParserThread(QObject* parent, libtorrent::session *s) : QThread(parent), s(s), abort(false) {
|
||||
|
||||
}
|
||||
|
||||
FilterParserThread::~FilterParserThread() {
|
||||
abort = true;
|
||||
wait();
|
||||
}
|
||||
|
||||
// Parser for eMule ip filter in DAT format
|
||||
int FilterParserThread::parseDATFilterFile(QString filePath, libtorrent::ip_filter& filter) {
|
||||
int ruleCount = 0;
|
||||
QFile file(filePath);
|
||||
if (file.exists()) {
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
std::cerr << "I/O Error: Could not open ip filer file in read mode." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
unsigned int nbLine = 0;
|
||||
while (!file.atEnd() && !abort) {
|
||||
++nbLine;
|
||||
QByteArray line = file.readLine();
|
||||
// Ignoring empty lines
|
||||
line = line.trimmed();
|
||||
if (line.isEmpty()) continue;
|
||||
// Ignoring commented lines
|
||||
if (line.startsWith('#') || line.startsWith("//")) continue;
|
||||
|
||||
// Line should be splitted by commas
|
||||
QList<QByteArray> partsList = line.split(',');
|
||||
const uint nbElem = partsList.size();
|
||||
|
||||
// IP Range should be splitted by a dash
|
||||
QList<QByteArray> IPs = partsList.first().split('-');
|
||||
if (IPs.size() != 2) {
|
||||
qDebug("Ipfilter.dat: line %d is malformed.", nbLine);
|
||||
qDebug("Line was %s", line.constData());
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
const QString strStartIP = cleanupIPAddress(IPs.at(0));
|
||||
if (strStartIP.isEmpty()) {
|
||||
qDebug("Ipfilter.dat: line %d is malformed.", nbLine);
|
||||
qDebug("Start IP of the range is malformated: %s", qPrintable(strStartIP));
|
||||
continue;
|
||||
}
|
||||
libtorrent::address startAddr = libtorrent::address::from_string(qPrintable(strStartIP), ec);
|
||||
if (ec) {
|
||||
qDebug("Ipfilter.dat: line %d is malformed.", nbLine);
|
||||
qDebug("Start IP of the range is malformated: %s", qPrintable(strStartIP));
|
||||
continue;
|
||||
}
|
||||
const QString strEndIP = cleanupIPAddress(IPs.at(1));
|
||||
if (strEndIP.isEmpty()) {
|
||||
qDebug("Ipfilter.dat: line %d is malformed.", nbLine);
|
||||
qDebug("End IP of the range is malformated: %s", qPrintable(strEndIP));
|
||||
continue;
|
||||
}
|
||||
libtorrent::address endAddr = libtorrent::address::from_string(qPrintable(strEndIP), ec);
|
||||
if (ec) {
|
||||
qDebug("Ipfilter.dat: line %d is malformed.", nbLine);
|
||||
qDebug("End IP of the range is malformated: %s", qPrintable(strEndIP));
|
||||
continue;
|
||||
}
|
||||
if (startAddr.is_v4() != endAddr.is_v4()) {
|
||||
qDebug("Ipfilter.dat: line %d is malformed.", nbLine);
|
||||
qDebug("One IP is IPv4 and the other is IPv6!");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if there is an access value (apparently not mandatory)
|
||||
int nbAccess = 0;
|
||||
if (nbElem > 1) {
|
||||
// There is possibly one
|
||||
nbAccess = partsList.at(1).trimmed().toInt();
|
||||
}
|
||||
|
||||
if (nbAccess > 127) {
|
||||
// Ignoring this rule because access value is too high
|
||||
continue;
|
||||
}
|
||||
// Now Add to the filter
|
||||
try {
|
||||
filter.add_rule(startAddr, endAddr, libtorrent::ip_filter::blocked);
|
||||
++ruleCount;
|
||||
}catch(exception) {
|
||||
qDebug("Bad line in filter file, avoided crash...");
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
return ruleCount;
|
||||
}
|
||||
|
||||
// Parser for PeerGuardian ip filter in p2p format
|
||||
int FilterParserThread::parseP2PFilterFile(QString filePath, libtorrent::ip_filter& filter) {
|
||||
int ruleCount = 0;
|
||||
QFile file(filePath);
|
||||
if (file.exists()) {
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
std::cerr << "I/O Error: Could not open ip filer file in read mode." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
unsigned int nbLine = 0;
|
||||
while (!file.atEnd() && !abort) {
|
||||
++nbLine;
|
||||
QByteArray line = file.readLine().trimmed();
|
||||
if (line.isEmpty()) continue;
|
||||
// Ignoring commented lines
|
||||
if (line.startsWith('#') || line.startsWith("//")) continue;
|
||||
// Line is splitted by :
|
||||
QList<QByteArray> partsList = line.split(':');
|
||||
if (partsList.size() < 2) {
|
||||
qDebug("p2p file: line %d is malformed.", nbLine);
|
||||
continue;
|
||||
}
|
||||
// Get IP range
|
||||
QList<QByteArray> IPs = partsList.last().split('-');
|
||||
if (IPs.size() != 2) {
|
||||
qDebug("p2p file: line %d is malformed.", nbLine);
|
||||
qDebug("line was: %s", line.constData());
|
||||
continue;
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
QString strStartIP = cleanupIPAddress(IPs.at(0));
|
||||
if (strStartIP.isEmpty()) {
|
||||
qDebug("p2p file: line %d is malformed.", nbLine);
|
||||
qDebug("Start IP is invalid: %s", qPrintable(strStartIP));
|
||||
continue;
|
||||
}
|
||||
libtorrent::address startAddr = libtorrent::address::from_string(qPrintable(strStartIP), ec);
|
||||
if (ec) {
|
||||
qDebug("p2p file: line %d is malformed.", nbLine);
|
||||
qDebug("Start IP is invalid: %s", qPrintable(strStartIP));
|
||||
continue;
|
||||
}
|
||||
QString strEndIP = cleanupIPAddress(IPs.at(1));
|
||||
if (strEndIP.isEmpty()) {
|
||||
qDebug("p2p file: line %d is malformed.", nbLine);
|
||||
qDebug("End IP is invalid: %s", qPrintable(strStartIP));
|
||||
continue;
|
||||
}
|
||||
libtorrent::address endAddr = libtorrent::address::from_string(qPrintable(strEndIP), ec);
|
||||
if (ec) {
|
||||
qDebug("p2p file: line %d is malformed.", nbLine);
|
||||
qDebug("End IP is invalid: %s", qPrintable(strStartIP));
|
||||
continue;
|
||||
}
|
||||
if (startAddr.is_v4() != endAddr.is_v4()) {
|
||||
qDebug("p2p file: line %d is malformed.", nbLine);
|
||||
qDebug("Line was: %s", line.constData());
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
filter.add_rule(startAddr, endAddr, libtorrent::ip_filter::blocked);
|
||||
++ruleCount;
|
||||
} catch(std::exception&) {
|
||||
qDebug("p2p file: line %d is malformed.", nbLine);
|
||||
qDebug("Line was: %s", line.constData());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
return ruleCount;
|
||||
}
|
||||
|
||||
int FilterParserThread::getlineInStream(QDataStream& stream, string& name, char delim) {
|
||||
char c;
|
||||
int total_read = 0;
|
||||
int read;
|
||||
do {
|
||||
read = stream.readRawData(&c, 1);
|
||||
total_read += read;
|
||||
if (read > 0) {
|
||||
if (c != delim) {
|
||||
name += c;
|
||||
} else {
|
||||
// Delim found
|
||||
return total_read;
|
||||
}
|
||||
}
|
||||
} while(read > 0);
|
||||
return total_read;
|
||||
}
|
||||
|
||||
// Parser for PeerGuardian ip filter in p2p format
|
||||
int FilterParserThread::parseP2BFilterFile(QString filePath, libtorrent::ip_filter& filter) {
|
||||
int ruleCount = 0;
|
||||
QFile file(filePath);
|
||||
if (file.exists()) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
std::cerr << "I/O Error: Could not open ip filer file in read mode." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
QDataStream stream(&file);
|
||||
// Read header
|
||||
char buf[7];
|
||||
unsigned char version;
|
||||
if (
|
||||
!stream.readRawData(buf, sizeof(buf)) ||
|
||||
memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7) ||
|
||||
!stream.readRawData((char*)&version, sizeof(version))
|
||||
) {
|
||||
std::cerr << "Parsing Error: The filter file is not a valid PeerGuardian P2B file." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
|
||||
if (version==1 || version==2) {
|
||||
qDebug ("p2b version 1 or 2");
|
||||
unsigned int start, end;
|
||||
|
||||
string name;
|
||||
while(getlineInStream(stream, name, '\0') && !abort) {
|
||||
if (
|
||||
!stream.readRawData((char*)&start, sizeof(start)) ||
|
||||
!stream.readRawData((char*)&end, sizeof(end))
|
||||
) {
|
||||
std::cerr << "Parsing Error: The filter file is not a valid PeerGuardian P2B file." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
// Network byte order to Host byte order
|
||||
// asio address_v4 contructor expects it
|
||||
// that way
|
||||
libtorrent::address_v4 first(ntohl(start));
|
||||
libtorrent::address_v4 last(ntohl(end));
|
||||
// Apply to bittorrent session
|
||||
try {
|
||||
filter.add_rule(first, last, libtorrent::ip_filter::blocked);
|
||||
++ruleCount;
|
||||
} catch(std::exception&) {}
|
||||
}
|
||||
}
|
||||
else if (version==3) {
|
||||
qDebug ("p2b version 3");
|
||||
unsigned int namecount;
|
||||
if (!stream.readRawData((char*)&namecount, sizeof(namecount))) {
|
||||
std::cerr << "Parsing Error: The filter file is not a valid PeerGuardian P2B file." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
namecount=ntohl(namecount);
|
||||
// Reading names although, we don't really care about them
|
||||
for (unsigned int i=0; i<namecount; i++) {
|
||||
string name;
|
||||
if (!getlineInStream(stream, name, '\0')) {
|
||||
std::cerr << "Parsing Error: The filter file is not a valid PeerGuardian P2B file." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
if (abort) return ruleCount;
|
||||
}
|
||||
// Reading the ranges
|
||||
unsigned int rangecount;
|
||||
if (!stream.readRawData((char*)&rangecount, sizeof(rangecount))) {
|
||||
std::cerr << "Parsing Error: The filter file is not a valid PeerGuardian P2B file." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
rangecount=ntohl(rangecount);
|
||||
|
||||
unsigned int name, start, end;
|
||||
|
||||
for (unsigned int i=0; i<rangecount; i++) {
|
||||
if (
|
||||
!stream.readRawData((char*)&name, sizeof(name)) ||
|
||||
!stream.readRawData((char*)&start, sizeof(start)) ||
|
||||
!stream.readRawData((char*)&end, sizeof(end))
|
||||
) {
|
||||
std::cerr << "Parsing Error: The filter file is not a valid PeerGuardian P2B file." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
// Network byte order to Host byte order
|
||||
// asio address_v4 contructor expects it
|
||||
// that way
|
||||
libtorrent::address_v4 first(ntohl(start));
|
||||
libtorrent::address_v4 last(ntohl(end));
|
||||
// Apply to bittorrent session
|
||||
try {
|
||||
filter.add_rule(first, last, libtorrent::ip_filter::blocked);
|
||||
++ruleCount;
|
||||
} catch(std::exception&) {}
|
||||
if (abort) return ruleCount;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Parsing Error: The filter file is not a valid PeerGuardian P2B file." << std::endl;
|
||||
return ruleCount;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
return ruleCount;
|
||||
}
|
||||
|
||||
// Process ip filter file
|
||||
// Supported formats:
|
||||
// * eMule IP list (DAT): http://wiki.phoenixlabs.org/wiki/DAT_Format
|
||||
// * PeerGuardian Text (P2P): http://wiki.phoenixlabs.org/wiki/P2P_Format
|
||||
// * PeerGuardian Binary (P2B): http://wiki.phoenixlabs.org/wiki/P2B_Format
|
||||
void FilterParserThread::processFilterFile(QString _filePath) {
|
||||
if (isRunning()) {
|
||||
// Already parsing a filter, abort first
|
||||
abort = true;
|
||||
wait();
|
||||
}
|
||||
abort = false;
|
||||
filePath = _filePath;
|
||||
// Run it
|
||||
start();
|
||||
}
|
||||
|
||||
void FilterParserThread::processFilterList(libtorrent::session *s, const QStringList& IPs) {
|
||||
// First, import current filter
|
||||
libtorrent::ip_filter filter = s->get_ip_filter();
|
||||
foreach (const QString &ip, IPs) {
|
||||
qDebug("Manual ban of peer %s", ip.toLocal8Bit().constData());
|
||||
boost::system::error_code ec;
|
||||
libtorrent::address addr = libtorrent::address::from_string(ip.toLocal8Bit().constData(), ec);
|
||||
Q_ASSERT(!ec);
|
||||
if (!ec)
|
||||
filter.add_rule(addr, addr, libtorrent::ip_filter::blocked);
|
||||
}
|
||||
s->set_ip_filter(filter);
|
||||
}
|
||||
|
||||
QString FilterParserThread::cleanupIPAddress(QString _ip) {
|
||||
QHostAddress ip(_ip.trimmed());
|
||||
if (ip.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
return ip.toString();
|
||||
}
|
||||
|
||||
void FilterParserThread::run() {
|
||||
qDebug("Processing filter file");
|
||||
libtorrent::ip_filter filter = s->get_ip_filter();
|
||||
int ruleCount = 0;
|
||||
if (filePath.endsWith(".p2p", Qt::CaseInsensitive)) {
|
||||
// PeerGuardian p2p file
|
||||
ruleCount = parseP2PFilterFile(filePath, filter);
|
||||
} else {
|
||||
if (filePath.endsWith(".p2b", Qt::CaseInsensitive)) {
|
||||
// PeerGuardian p2b file
|
||||
ruleCount = parseP2BFilterFile(filePath, filter);
|
||||
} else {
|
||||
// Default: eMule DAT format
|
||||
ruleCount = parseDATFilterFile(filePath, filter);
|
||||
}
|
||||
}
|
||||
if (abort)
|
||||
return;
|
||||
try {
|
||||
s->set_ip_filter(filter);
|
||||
emit IPFilterParsed(ruleCount);
|
||||
} catch(std::exception&) {
|
||||
emit IPFilterError();
|
||||
}
|
||||
qDebug("IP Filter thread: finished parsing, filter applied");
|
||||
}
|
||||
82
src/core/qtlibtorrent/filterparserthread.h
Normal file
82
src/core/qtlibtorrent/filterparserthread.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef FILTERPARSERTHREAD_H
|
||||
#define FILTERPARSERTHREAD_H
|
||||
|
||||
#include <QThread>
|
||||
#include <QDataStream>
|
||||
#include <QStringList>
|
||||
|
||||
namespace libtorrent {
|
||||
class session;
|
||||
struct ip_filter;
|
||||
}
|
||||
|
||||
using namespace std;
|
||||
|
||||
// P2B Stuff
|
||||
#include <string.h>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
// End of P2B stuff
|
||||
|
||||
class FilterParserThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FilterParserThread(QObject* parent, libtorrent::session *s);
|
||||
~FilterParserThread();
|
||||
|
||||
int parseDATFilterFile(QString filePath, libtorrent::ip_filter& filter);
|
||||
int parseP2PFilterFile(QString filePath, libtorrent::ip_filter& filter);
|
||||
int getlineInStream(QDataStream& stream, string& name, char delim);
|
||||
int parseP2BFilterFile(QString filePath, libtorrent::ip_filter& filter);
|
||||
void processFilterFile(QString _filePath);
|
||||
static void processFilterList(libtorrent::session *s, const QStringList& IPs);
|
||||
|
||||
signals:
|
||||
void IPFilterParsed(int ruleCount);
|
||||
void IPFilterError();
|
||||
|
||||
protected:
|
||||
QString cleanupIPAddress(QString _ip);
|
||||
void run();
|
||||
|
||||
private:
|
||||
libtorrent::session *s;
|
||||
bool abort;
|
||||
QString filePath;
|
||||
};
|
||||
|
||||
#endif
|
||||
3210
src/core/qtlibtorrent/qbtsession.cpp
Normal file
3210
src/core/qtlibtorrent/qbtsession.cpp
Normal file
File diff suppressed because it is too large
Load Diff
353
src/core/qtlibtorrent/qbtsession.h
Normal file
353
src/core/qtlibtorrent/qbtsession.h
Normal file
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
#ifndef __BITTORRENT_H__
|
||||
#define __BITTORRENT_H__
|
||||
|
||||
#include <QMap>
|
||||
#include <QHash>
|
||||
#include <QUrl>
|
||||
#include <QStringList>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
#include <QNetworkCookie>
|
||||
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include "qtorrenthandle.h"
|
||||
#include "trackerinfos.h"
|
||||
#include "misc.h"
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#include "rssdownloadrule.h"
|
||||
#endif
|
||||
|
||||
namespace libtorrent {
|
||||
struct add_torrent_params;
|
||||
struct pe_settings;
|
||||
struct proxy_settings;
|
||||
class session;
|
||||
struct session_status;
|
||||
|
||||
class alert;
|
||||
struct torrent_finished_alert;
|
||||
struct save_resume_data_alert;
|
||||
struct file_renamed_alert;
|
||||
struct torrent_deleted_alert;
|
||||
struct storage_moved_alert;
|
||||
struct storage_moved_failed_alert;
|
||||
struct metadata_received_alert;
|
||||
struct file_error_alert;
|
||||
struct file_completed_alert;
|
||||
struct torrent_paused_alert;
|
||||
struct tracker_error_alert;
|
||||
struct tracker_reply_alert;
|
||||
struct tracker_warning_alert;
|
||||
struct portmap_error_alert;
|
||||
struct portmap_alert;
|
||||
struct peer_blocked_alert;
|
||||
struct peer_ban_alert;
|
||||
struct fastresume_rejected_alert;
|
||||
struct url_seed_alert;
|
||||
struct listen_succeeded_alert;
|
||||
struct listen_failed_alert;
|
||||
struct torrent_checked_alert;
|
||||
struct external_ip_alert;
|
||||
struct state_update_alert;
|
||||
struct stats_alert;
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
class upnp;
|
||||
class natpmp;
|
||||
#endif
|
||||
}
|
||||
|
||||
class DownloadThread;
|
||||
class FilterParserThread;
|
||||
class HttpServer;
|
||||
class BandwidthScheduler;
|
||||
class ScanFoldersModel;
|
||||
class TorrentSpeedMonitor;
|
||||
class TorrentStatistics;
|
||||
class DNSUpdater;
|
||||
class QAlertDispatcher;
|
||||
|
||||
enum TorrentExportFolder {
|
||||
RegularTorrentExportFolder,
|
||||
FinishedTorrentExportFolder
|
||||
};
|
||||
|
||||
class QTracker;
|
||||
|
||||
class QBtSession : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(QBtSession)
|
||||
|
||||
public:
|
||||
static const qreal MAX_RATIO;
|
||||
|
||||
private:
|
||||
explicit QBtSession();
|
||||
static QBtSession* m_instance;
|
||||
|
||||
public:
|
||||
static QBtSession* instance();
|
||||
static void drop();
|
||||
~QBtSession();
|
||||
QTorrentHandle getTorrentHandle(const QString &hash) const;
|
||||
std::vector<libtorrent::torrent_handle> getTorrents() const;
|
||||
bool isFilePreviewPossible(const QString& hash) const;
|
||||
qreal getPayloadDownloadRate() const;
|
||||
qreal getPayloadUploadRate() const;
|
||||
libtorrent::session_status getSessionStatus() const;
|
||||
int getListenPort() const;
|
||||
qreal getRealRatio(const libtorrent::torrent_status &status) const;
|
||||
QHash<QString, TrackerInfos> getTrackersInfo(const QString &hash) const;
|
||||
bool hasActiveTorrents() const;
|
||||
bool hasDownloadingTorrents() const;
|
||||
//int getMaximumActiveDownloads() const;
|
||||
//int getMaximumActiveTorrents() const;
|
||||
inline libtorrent::session* getSession() const { return s; }
|
||||
inline bool useTemporaryFolder() const { return !defaultTempPath.isEmpty(); }
|
||||
inline QString getDefaultSavePath() const { return defaultSavePath; }
|
||||
inline ScanFoldersModel* getScanFoldersModel() const { return m_scanFolders; }
|
||||
inline bool isDHTEnabled() const { return DHTEnabled; }
|
||||
inline bool isLSDEnabled() const { return LSDEnabled; }
|
||||
inline bool isPexEnabled() const { return PeXEnabled; }
|
||||
inline bool isQueueingEnabled() const { return queueingEnabled; }
|
||||
quint64 getAlltimeDL() const;
|
||||
quint64 getAlltimeUL() const;
|
||||
void postTorrentUpdate();
|
||||
|
||||
public slots:
|
||||
QTorrentHandle addTorrent(QString path, bool fromScanDir = false, QString from_url = QString(), bool resumed = false, bool imported = false);
|
||||
QTorrentHandle addMagnetUri(QString magnet_uri, bool resumed=false, bool fromScanDir=false, const QString &filePath=QString());
|
||||
void loadSessionState();
|
||||
void saveSessionState();
|
||||
void downloadFromUrl(const QString &url, const QList<QNetworkCookie>& cookies = QList<QNetworkCookie>());
|
||||
void deleteTorrent(const QString &hash, bool delete_local_files = false);
|
||||
void startUpTorrents();
|
||||
void recheckTorrent(const QString &hash);
|
||||
void useAlternativeSpeedsLimit(bool alternative);
|
||||
qlonglong getETA(const QString& hash, const libtorrent::torrent_status &status) const;
|
||||
/* Needed by Web UI */
|
||||
void pauseAllTorrents();
|
||||
void pauseTorrent(const QString &hash);
|
||||
void resumeTorrent(const QString &hash);
|
||||
void resumeAllTorrents();
|
||||
/* End Web UI */
|
||||
void preAllocateAllFiles(bool b);
|
||||
void saveFastResumeData();
|
||||
void enableIPFilter(const QString &filter_path, bool force=false);
|
||||
void disableIPFilter();
|
||||
void setQueueingEnabled(bool enable);
|
||||
void handleDownloadFailure(QString url, QString reason);
|
||||
void handleMagnetRedirect(const QString &url_new, const QString &url_old);
|
||||
#ifndef DISABLE_GUI
|
||||
void downloadUrlAndSkipDialog(QString url, QString save_path=QString(), QString label=QString(),
|
||||
const QList<QNetworkCookie>& cookies = QList<QNetworkCookie>(),
|
||||
const RssDownloadRule::AddPausedState &aps = RssDownloadRule::USE_GLOBAL);
|
||||
#else
|
||||
void downloadUrlAndSkipDialog(QString url, QString save_path=QString(), QString label=QString(),
|
||||
const QList<QNetworkCookie>& cookies = QList<QNetworkCookie>());
|
||||
#endif
|
||||
// Session configuration - Setters
|
||||
void setListeningPort(int port);
|
||||
void setMaxConnectionsPerTorrent(int max);
|
||||
void setMaxUploadsPerTorrent(int max);
|
||||
void setDownloadRateLimit(long rate);
|
||||
void setUploadRateLimit(long rate);
|
||||
void setGlobalMaxRatio(qreal ratio);
|
||||
qreal getGlobalMaxRatio() const { return global_ratio_limit; }
|
||||
void setMaxRatioPerTorrent(const QString &hash, qreal ratio);
|
||||
qreal getMaxRatioPerTorrent(const QString &hash, bool *usesGlobalRatio) const;
|
||||
void removeRatioPerTorrent(const QString &hash);
|
||||
void setDefaultSavePath(const QString &savepath);
|
||||
void setDefaultTempPath(const QString &temppath);
|
||||
void setAppendLabelToSavePath(bool append);
|
||||
void appendLabelToTorrentSavePath(const QTorrentHandle &h);
|
||||
void changeLabelInTorrentSavePath(const QTorrentHandle &h, QString old_label, QString new_label);
|
||||
void appendqBextensionToTorrent(const QTorrentHandle &h, bool append);
|
||||
void setAppendqBExtension(bool append);
|
||||
void setDownloadLimit(QString hash, long val);
|
||||
void setUploadLimit(QString hash, long val);
|
||||
void enableUPnP(bool b);
|
||||
void enableLSD(bool b);
|
||||
void enableDHT(bool b);
|
||||
void processDownloadedFile(QString, QString);
|
||||
#ifndef DISABLE_GUI
|
||||
void addMagnetSkipAddDlg(const QString& uri, const QString& save_path = QString(), const QString& label = QString(),
|
||||
const RssDownloadRule::AddPausedState &aps = RssDownloadRule::USE_GLOBAL, const QString &uri_old = QString());
|
||||
#else
|
||||
void addMagnetSkipAddDlg(const QString& uri, const QString& save_path = QString(), const QString& label = QString(), const QString &uri_old = QString());
|
||||
#endif
|
||||
void addMagnetInteractive(const QString& uri);
|
||||
void downloadFromURLList(const QStringList& urls);
|
||||
void configureSession();
|
||||
void banIP(QString ip);
|
||||
void recursiveTorrentDownload(const QTorrentHandle &h);
|
||||
void unhideMagnet(const QString &hash);
|
||||
|
||||
private:
|
||||
void applyEncryptionSettings(libtorrent::pe_settings se);
|
||||
void setProxySettings(libtorrent::proxy_settings proxySettings);
|
||||
void setSessionSettings(const libtorrent::session_settings &sessionSettings);
|
||||
QString getSavePath(const QString &hash, bool fromScanDir = false, QString filePath = QString::null, bool imported = false);
|
||||
bool loadFastResumeData(const QString &hash, std::vector<char> &buf);
|
||||
void loadTorrentSettings(QTorrentHandle &h);
|
||||
void loadTorrentTempData(QTorrentHandle &h, QString savePath, bool magnet);
|
||||
void initializeAddTorrentParams(const QString &hash, libtorrent::add_torrent_params &p);
|
||||
void updateRatioTimer();
|
||||
void recoverPersistentData(const QString &hash, const std::vector<char> &buf);
|
||||
void backupPersistentData(const QString &hash, boost::shared_ptr<libtorrent::entry> data);
|
||||
void handleAlert(libtorrent::alert* a);
|
||||
void handleTorrentFinishedAlert(libtorrent::torrent_finished_alert* p);
|
||||
void handleSaveResumeDataAlert(libtorrent::save_resume_data_alert* p);
|
||||
void handleFileRenamedAlert(libtorrent::file_renamed_alert* p);
|
||||
void handleTorrentDeletedAlert(libtorrent::torrent_deleted_alert* p);
|
||||
void handleStorageMovedAlert(libtorrent::storage_moved_alert* p);
|
||||
void handleStorageMovedFailedAlert(libtorrent::storage_moved_failed_alert* p);
|
||||
void handleMetadataReceivedAlert(libtorrent::metadata_received_alert* p);
|
||||
void handleFileErrorAlert(libtorrent::file_error_alert* p);
|
||||
void handleFileCompletedAlert(libtorrent::file_completed_alert* p);
|
||||
void handleTorrentPausedAlert(libtorrent::torrent_paused_alert* p);
|
||||
void handleTrackerErrorAlert(libtorrent::tracker_error_alert* p);
|
||||
void handleTrackerReplyAlert(libtorrent::tracker_reply_alert* p);
|
||||
void handleTrackerWarningAlert(libtorrent::tracker_warning_alert* p);
|
||||
void handlePortmapWarningAlert(libtorrent::portmap_error_alert* p);
|
||||
void handlePortmapAlert(libtorrent::portmap_alert* p);
|
||||
void handlePeerBlockedAlert(libtorrent::peer_blocked_alert* p);
|
||||
void handlePeerBanAlert(libtorrent::peer_ban_alert* p);
|
||||
void handleFastResumeRejectedAlert(libtorrent::fastresume_rejected_alert* p);
|
||||
void handleUrlSeedAlert(libtorrent::url_seed_alert* p);
|
||||
void handleListenSucceededAlert(libtorrent::listen_succeeded_alert *p);
|
||||
void handleListenFailedAlert(libtorrent::listen_failed_alert *p);
|
||||
void handleTorrentCheckedAlert(libtorrent::torrent_checked_alert* p);
|
||||
void handleExternalIPAlert(libtorrent::external_ip_alert *p);
|
||||
void handleStateUpdateAlert(libtorrent::state_update_alert *p);
|
||||
void handleStatsAlert(libtorrent::stats_alert *p);
|
||||
|
||||
private slots:
|
||||
void addTorrentsFromScanFolder(QStringList&);
|
||||
void readAlerts();
|
||||
void processBigRatios();
|
||||
void exportTorrentFiles(QString path);
|
||||
void saveTempFastResumeData();
|
||||
void sendNotificationEmail(const QTorrentHandle &h);
|
||||
void autoRunExternalProgram(const QTorrentHandle &h);
|
||||
void mergeTorrents(QTorrentHandle& h_ex, boost::intrusive_ptr<libtorrent::torrent_info> t);
|
||||
void mergeTorrents(QTorrentHandle& h_ex, const QString& magnet_uri);
|
||||
void exportTorrentFile(const QTorrentHandle &h, TorrentExportFolder folder = RegularTorrentExportFolder);
|
||||
void initWebUi();
|
||||
void handleIPFilterParsed(int ruleCount);
|
||||
void handleIPFilterError();
|
||||
|
||||
signals:
|
||||
void addedTorrent(const QTorrentHandle& h);
|
||||
void torrentAboutToBeRemoved(const QTorrentHandle &h);
|
||||
void pausedTorrent(const QTorrentHandle& h);
|
||||
void resumedTorrent(const QTorrentHandle& h);
|
||||
void finishedTorrent(const QTorrentHandle& h);
|
||||
void fullDiskError(const QTorrentHandle& h, QString msg);
|
||||
void trackerError(const QString &hash, QString time, QString msg);
|
||||
void trackerAuthenticationRequired(const QTorrentHandle& h);
|
||||
void newDownloadedTorrent(QString path, QString url);
|
||||
void newDownloadedTorrentFromRss(QString url);
|
||||
void newMagnetLink(const QString& link);
|
||||
void updateFileSize(const QString &hash);
|
||||
void downloadFromUrlFailure(QString url, QString reason);
|
||||
void torrentFinishedChecking(const QTorrentHandle& h);
|
||||
void metadataReceived(const QTorrentHandle &h);
|
||||
void savePathChanged(const QTorrentHandle &h);
|
||||
void alternativeSpeedsModeChanged(bool alternative);
|
||||
void recursiveTorrentDownloadPossible(const QTorrentHandle &h);
|
||||
void ipFilterParsed(bool error, int ruleCount);
|
||||
void metadataReceivedHidden(const QTorrentHandle &h);
|
||||
void stateUpdate(const std::vector<libtorrent::torrent_status> &statuses);
|
||||
void statsReceived(const libtorrent::stats_alert&);
|
||||
|
||||
private:
|
||||
// Bittorrent
|
||||
libtorrent::session *s;
|
||||
QPointer<BandwidthScheduler> bd_scheduler;
|
||||
QMap<QUrl, QPair<QString, QString> > savepathLabel_fromurl; // Use QMap for compatibility with Qt < 4.7: qHash(QUrl)
|
||||
#ifndef DISABLE_GUI
|
||||
QMap<QUrl, RssDownloadRule::AddPausedState> addpaused_fromurl;
|
||||
#endif
|
||||
QHash<QString, QHash<QString, TrackerInfos> > trackersInfos;
|
||||
QHash<QString, QString> savePathsToRemove;
|
||||
QStringList torrentsToPausedAfterChecking;
|
||||
QTimer resumeDataTimer;
|
||||
// Ratio
|
||||
QPointer<QTimer> BigRatioTimer;
|
||||
// HTTP
|
||||
DownloadThread* downloader;
|
||||
// File System
|
||||
ScanFoldersModel *m_scanFolders;
|
||||
// Settings
|
||||
bool preAllocateAll;
|
||||
qreal global_ratio_limit;
|
||||
int high_ratio_action;
|
||||
bool LSDEnabled;
|
||||
bool DHTEnabled;
|
||||
bool PeXEnabled;
|
||||
bool queueingEnabled;
|
||||
bool appendLabelToSavePath;
|
||||
bool m_torrentExportEnabled;
|
||||
bool m_finishedTorrentExportEnabled;
|
||||
bool appendqBExtension;
|
||||
QString defaultSavePath;
|
||||
QString defaultTempPath;
|
||||
// IP filtering
|
||||
QPointer<FilterParserThread> filterParser;
|
||||
QString filterPath;
|
||||
// Web UI
|
||||
QPointer<HttpServer> httpServer;
|
||||
QList<QUrl> url_skippingDlg;
|
||||
// GeoIP
|
||||
#ifndef DISABLE_GUI
|
||||
bool geoipDBLoaded;
|
||||
bool resolve_countries;
|
||||
#endif
|
||||
// Tracker
|
||||
QPointer<QTracker> m_tracker;
|
||||
TorrentSpeedMonitor *m_speedMonitor;
|
||||
shutDownAction m_shutdownAct;
|
||||
// Port forwarding
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
libtorrent::upnp *m_upnp;
|
||||
libtorrent::natpmp *m_natpmp;
|
||||
#endif
|
||||
// DynDNS
|
||||
DNSUpdater *m_dynDNSUpdater;
|
||||
QAlertDispatcher* m_alertDispatcher;
|
||||
TorrentStatistics* m_torrentStatistics;
|
||||
};
|
||||
|
||||
#endif
|
||||
25
src/core/qtlibtorrent/qtlibtorrent.pri
Normal file
25
src/core/qtlibtorrent/qtlibtorrent.pri
Normal file
@@ -0,0 +1,25 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/qbtsession.h \
|
||||
$$PWD/qtorrenthandle.h \
|
||||
$$PWD/bandwidthscheduler.h \
|
||||
$$PWD/trackerinfos.h \
|
||||
$$PWD/torrentspeedmonitor.h \
|
||||
$$PWD/filterparserthread.h \
|
||||
$$PWD/alertdispatcher.h \
|
||||
$$PWD/torrentstatistics.h
|
||||
|
||||
SOURCES += $$PWD/qbtsession.cpp \
|
||||
$$PWD/qtorrenthandle.cpp \
|
||||
$$PWD/torrentspeedmonitor.cpp \
|
||||
$$PWD/alertdispatcher.cpp \
|
||||
$$PWD/torrentstatistics.cpp \
|
||||
$$PWD/filterparserthread.cpp
|
||||
|
||||
!contains(DEFINES, DISABLE_GUI) {
|
||||
HEADERS += $$PWD/torrentmodel.h \
|
||||
$$PWD/shutdownconfirm.h
|
||||
|
||||
SOURCES += $$PWD/torrentmodel.cpp \
|
||||
$$PWD/shutdownconfirm.cpp
|
||||
}
|
||||
843
src/core/qtlibtorrent/qtorrenthandle.cpp
Normal file
843
src/core/qtlibtorrent/qtorrenthandle.cpp
Normal file
@@ -0,0 +1,843 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QByteArray>
|
||||
#include <math.h>
|
||||
#include "fs_utils.h"
|
||||
#include "misc.h"
|
||||
#include "preferences.h"
|
||||
#include "qtorrenthandle.h"
|
||||
#include "torrentpersistentdata.h"
|
||||
#include "qbtsession.h"
|
||||
#include <libtorrent/version.hpp>
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
using namespace libtorrent;
|
||||
using namespace std;
|
||||
|
||||
static QPair<int, int> get_file_extremity_pieces(const torrent_info& t, int file_index)
|
||||
{
|
||||
const int num_pieces = t.num_pieces();
|
||||
const int piece_size = t.piece_length();
|
||||
const file_entry& file = t.file_at(file_index);
|
||||
|
||||
// Determine the first and last piece of the file
|
||||
int first_piece = floor((file.offset + 1) / (float) piece_size);
|
||||
Q_ASSERT(first_piece >= 0 && first_piece < num_pieces);
|
||||
|
||||
int num_pieces_in_file = ceil(file.size / (float) piece_size);
|
||||
int last_piece = first_piece + num_pieces_in_file - 1;
|
||||
Q_ASSERT(last_piece >= 0 && last_piece < num_pieces);
|
||||
|
||||
return qMakePair(first_piece, last_piece);
|
||||
}
|
||||
|
||||
QTorrentHandle::QTorrentHandle(const torrent_handle& h): torrent_handle(h)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Getters
|
||||
//
|
||||
|
||||
QString QTorrentHandle::hash() const
|
||||
{
|
||||
return misc::toQString(torrent_handle::info_hash());
|
||||
}
|
||||
|
||||
QString QTorrentHandle::name() const
|
||||
{
|
||||
QString name = TorrentPersistentData::instance()->getName(hash());
|
||||
if (name.isEmpty()) {
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
name = misc::toQStringU(torrent_handle::name());
|
||||
#else
|
||||
name = misc::toQStringU(status(query_name).name);
|
||||
#endif
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
QString QTorrentHandle::creation_date() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
boost::optional<time_t> t = torrent_handle::get_torrent_info().creation_date();
|
||||
#else
|
||||
boost::optional<time_t> t = torrent_handle::torrent_file()->creation_date();
|
||||
#endif
|
||||
return t ? misc::toQString(*t) : "";
|
||||
}
|
||||
|
||||
qlonglong QTorrentHandle::creation_date_unix() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
boost::optional<time_t> t = torrent_handle::get_torrent_info().creation_date();
|
||||
#else
|
||||
boost::optional<time_t> t = torrent_handle::torrent_file()->creation_date();
|
||||
#endif
|
||||
return t ? *t : -1;
|
||||
}
|
||||
|
||||
QString QTorrentHandle::current_tracker() const
|
||||
{
|
||||
return misc::toQString(status(0x0).current_tracker);
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_paused() const
|
||||
{
|
||||
return is_paused(status(0x0));
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_queued() const
|
||||
{
|
||||
return is_queued(status(0x0));
|
||||
}
|
||||
|
||||
size_type QTorrentHandle::total_size() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return torrent_handle::get_torrent_info().total_size();
|
||||
#else
|
||||
return torrent_handle::torrent_file()->total_size();
|
||||
#endif
|
||||
}
|
||||
|
||||
size_type QTorrentHandle::piece_length() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return torrent_handle::get_torrent_info().piece_length();
|
||||
#else
|
||||
return torrent_handle::torrent_file()->piece_length();
|
||||
#endif
|
||||
}
|
||||
|
||||
int QTorrentHandle::num_pieces() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return torrent_handle::get_torrent_info().num_pieces();
|
||||
#else
|
||||
return torrent_handle::torrent_file()->num_pieces();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool QTorrentHandle::first_last_piece_first() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
torrent_info const* t = &get_torrent_info();
|
||||
#else
|
||||
boost::intrusive_ptr<torrent_info const> t = torrent_file();
|
||||
#endif
|
||||
|
||||
// Get int first media file
|
||||
int index = 0;
|
||||
for (index = 0; index < t->num_files(); ++index) {
|
||||
QString path = misc::toQStringU(t->file_at(index).path);
|
||||
const QString ext = fsutils::fileExtension(path);
|
||||
if (misc::isPreviewable(ext) && torrent_handle::file_priority(index) > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (index >= t->num_files()) // No media file
|
||||
return false;
|
||||
|
||||
QPair<int, int> extremities = get_file_extremity_pieces(*t, index);
|
||||
|
||||
return (torrent_handle::piece_priority(extremities.first) == 7)
|
||||
&& (torrent_handle::piece_priority(extremities.second) == 7);
|
||||
}
|
||||
|
||||
QString QTorrentHandle::save_path() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return fsutils::fromNativePath(misc::toQStringU(torrent_handle::save_path()));
|
||||
#else
|
||||
return fsutils::fromNativePath(misc::toQStringU(status(torrent_handle::query_save_path).save_path));
|
||||
#endif
|
||||
}
|
||||
|
||||
QString QTorrentHandle::save_path_parsed() const
|
||||
{
|
||||
QString p;
|
||||
if (has_metadata() && num_files() == 1) {
|
||||
p = firstFileSavePath();
|
||||
}
|
||||
else {
|
||||
p = fsutils::fromNativePath(TorrentPersistentData::instance()->getSavePath(hash()));
|
||||
if (p.isEmpty())
|
||||
p = save_path();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
QStringList QTorrentHandle::url_seeds() const
|
||||
{
|
||||
QStringList res;
|
||||
try {
|
||||
const std::set<std::string> existing_seeds = torrent_handle::url_seeds();
|
||||
|
||||
std::set<std::string>::const_iterator it = existing_seeds.begin();
|
||||
std::set<std::string>::const_iterator itend = existing_seeds.end();
|
||||
for (; it != itend; ++it) {
|
||||
qDebug("URL Seed: %s", it->c_str());
|
||||
res << misc::toQString(*it);
|
||||
}
|
||||
} catch(std::exception &e) {
|
||||
std::cout << "ERROR: Failed to convert the URL seed" << std::endl;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// get the size of the torrent without the filtered files
|
||||
size_type QTorrentHandle::actual_size() const
|
||||
{
|
||||
return status(query_accurate_download_counters).total_wanted;
|
||||
}
|
||||
|
||||
bool QTorrentHandle::has_filtered_pieces() const
|
||||
{
|
||||
const std::vector<int> piece_priorities = torrent_handle::piece_priorities();
|
||||
foreach (const int priority, piece_priorities)
|
||||
if (priority == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int QTorrentHandle::num_files() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return torrent_handle::get_torrent_info().num_files();
|
||||
#else
|
||||
return torrent_handle::torrent_file()->num_files();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString QTorrentHandle::filename_at(unsigned int index) const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
Q_ASSERT(index < (unsigned int)torrent_handle::get_torrent_info().num_files());
|
||||
#else
|
||||
Q_ASSERT(index < (unsigned int)torrent_handle::torrent_file()->num_files());
|
||||
#endif
|
||||
return fsutils::fileName(filepath_at(index));
|
||||
}
|
||||
|
||||
size_type QTorrentHandle::filesize_at(unsigned int index) const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
Q_ASSERT(index < (unsigned int)torrent_handle::get_torrent_info().num_files());
|
||||
return torrent_handle::get_torrent_info().files().file_size(index);
|
||||
#else
|
||||
Q_ASSERT(index < (unsigned int)torrent_handle::torrent_file()->num_files());
|
||||
return torrent_handle::torrent_file()->files().file_size(index);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString QTorrentHandle::filepath_at(unsigned int index) const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return filepath_at(torrent_handle::get_torrent_info(), index);
|
||||
#else
|
||||
return filepath_at(*torrent_handle::torrent_file(), index);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString QTorrentHandle::orig_filepath_at(unsigned int index) const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return fsutils::fromNativePath(misc::toQStringU(torrent_handle::get_torrent_info().orig_files().file_path(index)));
|
||||
#else
|
||||
return fsutils::fromNativePath(misc::toQStringU(torrent_handle::torrent_file()->orig_files().file_path(index)));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
torrent_status::state_t QTorrentHandle::state() const
|
||||
{
|
||||
return status(0x0).state;
|
||||
}
|
||||
|
||||
QString QTorrentHandle::creator() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return misc::toQStringU(torrent_handle::get_torrent_info().creator());
|
||||
#else
|
||||
return misc::toQStringU(torrent_handle::torrent_file()->creator());
|
||||
#endif
|
||||
}
|
||||
|
||||
QString QTorrentHandle::comment() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return misc::toQStringU(torrent_handle::get_torrent_info().comment());
|
||||
#else
|
||||
return misc::toQStringU(torrent_handle::torrent_file()->comment());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_checking() const
|
||||
{
|
||||
return is_checking(status(0x0));
|
||||
}
|
||||
|
||||
// Return a list of absolute paths corresponding
|
||||
// to all files in a torrent
|
||||
QStringList QTorrentHandle::absolute_files_path() const
|
||||
{
|
||||
QDir saveDir(save_path());
|
||||
QStringList res;
|
||||
for (int i = 0; i<num_files(); ++i)
|
||||
res << fsutils::expandPathAbs(saveDir.absoluteFilePath(filepath_at(i)));
|
||||
return res;
|
||||
}
|
||||
|
||||
QStringList QTorrentHandle::absolute_files_path_uneeded() const
|
||||
{
|
||||
QDir saveDir(save_path());
|
||||
QStringList res;
|
||||
std::vector<int> fp = torrent_handle::file_priorities();
|
||||
for (uint i = 0; i < fp.size(); ++i) {
|
||||
if (fp[i] == 0) {
|
||||
const QString file_path = fsutils::expandPathAbs(saveDir.absoluteFilePath(filepath_at(i)));
|
||||
if (file_path.contains(".unwanted"))
|
||||
res << file_path;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool QTorrentHandle::has_missing_files() const
|
||||
{
|
||||
const QStringList paths = absolute_files_path();
|
||||
foreach (const QString &path, paths)
|
||||
if (!QFile::exists(path)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int QTorrentHandle::queue_position() const
|
||||
{
|
||||
return queue_position(status(0x0));
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_seed() const
|
||||
{
|
||||
// Affected by bug http://code.rasterbar.com/libtorrent/ticket/402
|
||||
//return torrent_handle::is_seed();
|
||||
// May suffer from approximation problems
|
||||
//return (progress() == 1.);
|
||||
// This looks safe
|
||||
return is_seed(status(0x0));
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_sequential_download() const
|
||||
{
|
||||
return status(0x0).sequential_download;
|
||||
}
|
||||
|
||||
bool QTorrentHandle::priv() const
|
||||
{
|
||||
if (!has_metadata())
|
||||
return false;
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
return torrent_handle::get_torrent_info().priv();
|
||||
#else
|
||||
return torrent_handle::torrent_file()->priv();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString QTorrentHandle::firstFileSavePath() const
|
||||
{
|
||||
Q_ASSERT(has_metadata());
|
||||
QString fsave_path = fsutils::fromNativePath(TorrentPersistentData::instance()->getSavePath(hash()));
|
||||
if (fsave_path.isEmpty())
|
||||
fsave_path = save_path();
|
||||
if (!fsave_path.endsWith("/"))
|
||||
fsave_path += "/";
|
||||
fsave_path += filepath_at(0);
|
||||
// Remove .!qB extension
|
||||
if (fsave_path.endsWith(".!qB", Qt::CaseInsensitive))
|
||||
fsave_path.chop(4);
|
||||
return fsave_path;
|
||||
}
|
||||
|
||||
QString QTorrentHandle::root_path() const
|
||||
{
|
||||
if (num_files() < 2)
|
||||
return save_path();
|
||||
QString first_filepath = filepath_at(0);
|
||||
const int slashIndex = first_filepath.indexOf("/");
|
||||
if (slashIndex >= 0)
|
||||
return QDir(save_path()).absoluteFilePath(first_filepath.left(slashIndex));
|
||||
return save_path();
|
||||
}
|
||||
|
||||
bool QTorrentHandle::has_error() const
|
||||
{
|
||||
return has_error(status(0x0));
|
||||
}
|
||||
|
||||
QString QTorrentHandle::error() const
|
||||
{
|
||||
return misc::toQString(status(0x0).error);
|
||||
}
|
||||
|
||||
void QTorrentHandle::downloading_pieces(bitfield &bf) const
|
||||
{
|
||||
std::vector<partial_piece_info> queue;
|
||||
torrent_handle::get_download_queue(queue);
|
||||
|
||||
std::vector<partial_piece_info>::const_iterator it = queue.begin();
|
||||
std::vector<partial_piece_info>::const_iterator itend = queue.end();
|
||||
for (; it!= itend; ++it)
|
||||
bf.set_bit(it->piece_index);
|
||||
return;
|
||||
}
|
||||
|
||||
bool QTorrentHandle::has_metadata() const
|
||||
{
|
||||
return status(0x0).has_metadata;
|
||||
}
|
||||
|
||||
void QTorrentHandle::file_progress(std::vector<size_type>& fp) const
|
||||
{
|
||||
torrent_handle::file_progress(fp, torrent_handle::piece_granularity);
|
||||
}
|
||||
|
||||
QTorrentState QTorrentHandle::torrentState() const
|
||||
{
|
||||
QTorrentState state = QTorrentState::Unknown;
|
||||
libtorrent::torrent_status s = status(torrent_handle::query_accurate_download_counters);
|
||||
|
||||
if (is_paused(s)) {
|
||||
if (has_error(s))
|
||||
state = QTorrentState::Error;
|
||||
else
|
||||
state = is_seed(s) ? QTorrentState::PausedUploading : QTorrentState::PausedDownloading;
|
||||
}
|
||||
else {
|
||||
if (QBtSession::instance()->isQueueingEnabled() && is_queued(s)) {
|
||||
state = is_seed(s) ? QTorrentState::QueuedUploading : QTorrentState::QueuedDownloading;
|
||||
}
|
||||
else {
|
||||
switch (s.state) {
|
||||
case torrent_status::finished:
|
||||
case torrent_status::seeding:
|
||||
state = s.upload_payload_rate > 0 ? QTorrentState::Uploading : QTorrentState::StalledUploading;
|
||||
break;
|
||||
case torrent_status::allocating:
|
||||
case torrent_status::checking_files:
|
||||
case torrent_status::queued_for_checking:
|
||||
case torrent_status::checking_resume_data:
|
||||
state = is_seed(s) ? QTorrentState::CheckingUploading : QTorrentState::CheckingDownloading;
|
||||
break;
|
||||
case torrent_status::downloading:
|
||||
case torrent_status::downloading_metadata:
|
||||
state = s.download_payload_rate > 0 ? QTorrentState::Downloading : QTorrentState::StalledDownloading;
|
||||
break;
|
||||
default:
|
||||
qWarning("Unrecognized torrent status, should not happen!!! status was %d", this->state());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
qulonglong QTorrentHandle::eta() const
|
||||
{
|
||||
libtorrent::torrent_status s = status(torrent_handle::query_accurate_download_counters);
|
||||
return QBtSession::instance()->getETA(hash(), s);
|
||||
}
|
||||
|
||||
void QTorrentHandle::toggleSequentialDownload()
|
||||
{
|
||||
if (is_valid() && has_metadata()) {
|
||||
bool was_sequential = is_sequential_download();
|
||||
set_sequential_download(!was_sequential);
|
||||
if (!was_sequential)
|
||||
prioritize_first_last_piece(true);
|
||||
}
|
||||
}
|
||||
|
||||
void QTorrentHandle::toggleFirstLastPiecePrio()
|
||||
{
|
||||
if (is_valid() && has_metadata())
|
||||
prioritize_first_last_piece(!first_last_piece_first());
|
||||
}
|
||||
|
||||
//
|
||||
// Setters
|
||||
//
|
||||
|
||||
void QTorrentHandle::pause() const
|
||||
{
|
||||
torrent_handle::auto_managed(false);
|
||||
torrent_handle::pause();
|
||||
if (!TorrentPersistentData::instance()->getHasMissingFiles(this->hash()))
|
||||
torrent_handle::save_resume_data();
|
||||
}
|
||||
|
||||
void QTorrentHandle::resume() const
|
||||
{
|
||||
if (has_error())
|
||||
torrent_handle::clear_error();
|
||||
|
||||
const QString torrent_hash = hash();
|
||||
TorrentPersistentData* const TorPersistent = TorrentPersistentData::instance();
|
||||
bool has_persistant_error = TorPersistent->hasError(torrent_hash);
|
||||
TorPersistent->setErrorState(torrent_hash, false);
|
||||
bool temp_path_enabled = Preferences::instance()->isTempPathEnabled();
|
||||
if (has_persistant_error && temp_path_enabled) {
|
||||
// Torrent was supposed to be seeding, checking again in final destination
|
||||
qDebug("Resuming a torrent with error...");
|
||||
const QString final_save_path = TorPersistent->getSavePath(torrent_hash);
|
||||
qDebug("Torrent final path is: %s", qPrintable(final_save_path));
|
||||
if (!final_save_path.isEmpty())
|
||||
move_storage(final_save_path);
|
||||
}
|
||||
torrent_handle::auto_managed(true);
|
||||
torrent_handle::resume();
|
||||
if (has_persistant_error && temp_path_enabled)
|
||||
// Force recheck
|
||||
torrent_handle::force_recheck();
|
||||
}
|
||||
|
||||
void QTorrentHandle::remove_url_seed(const QString& seed) const
|
||||
{
|
||||
torrent_handle::remove_url_seed(seed.toStdString());
|
||||
}
|
||||
|
||||
void QTorrentHandle::add_url_seed(const QString& seed) const
|
||||
{
|
||||
const std::string str_seed = seed.toStdString();
|
||||
qDebug("calling torrent_handle::add_url_seed(%s)", str_seed.c_str());
|
||||
torrent_handle::add_url_seed(str_seed);
|
||||
}
|
||||
|
||||
void QTorrentHandle::set_tracker_login(const QString& username, const QString& password) const
|
||||
{
|
||||
torrent_handle::set_tracker_login(std::string(username.toLocal8Bit().constData()), std::string(password.toLocal8Bit().constData()));
|
||||
}
|
||||
|
||||
void QTorrentHandle::move_storage(const QString& new_path) const
|
||||
{
|
||||
QString hashstr = hash();
|
||||
|
||||
if (TorrentTempData::isMoveInProgress(hashstr)) {
|
||||
qDebug("enqueue move storage to %s", qPrintable(new_path));
|
||||
TorrentTempData::enqueueMove(hashstr, new_path);
|
||||
}
|
||||
else {
|
||||
QString old_path = save_path();
|
||||
|
||||
qDebug("move storage: %s to %s", qPrintable(old_path), qPrintable(new_path));
|
||||
|
||||
if (QDir(old_path) == QDir(new_path))
|
||||
return;
|
||||
|
||||
TorrentTempData::startMove(hashstr, old_path, new_path);
|
||||
|
||||
// Create destination directory if necessary
|
||||
// or move_storage() will fail...
|
||||
QDir().mkpath(new_path);
|
||||
// Actually move the storage
|
||||
torrent_handle::move_storage(fsutils::toNativePath(new_path).toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
bool QTorrentHandle::save_torrent_file(const QString& path) const
|
||||
{
|
||||
if (!has_metadata()) return false;
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
torrent_info const* t = &get_torrent_info();
|
||||
#else
|
||||
boost::intrusive_ptr<torrent_info const> t = torrent_file();
|
||||
#endif
|
||||
|
||||
entry meta = bdecode(t->metadata().get(),
|
||||
t->metadata().get() + t->metadata_size());
|
||||
entry torrent_entry(entry::dictionary_t);
|
||||
torrent_entry["info"] = meta;
|
||||
if (!torrent_handle::trackers().empty())
|
||||
torrent_entry["announce"] = torrent_handle::trackers().front().url;
|
||||
|
||||
vector<char> out;
|
||||
bencode(back_inserter(out), torrent_entry);
|
||||
QFile torrent_file(path);
|
||||
if (!out.empty() && torrent_file.open(QIODevice::WriteOnly)) {
|
||||
torrent_file.write(&out[0], out.size());
|
||||
torrent_file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void QTorrentHandle::file_priority(int index, int priority) const
|
||||
{
|
||||
vector<int> priorities = torrent_handle::file_priorities();
|
||||
if (priorities[index] != priority) {
|
||||
priorities[index] = priority;
|
||||
prioritize_files(priorities);
|
||||
}
|
||||
}
|
||||
|
||||
void QTorrentHandle::prioritize_files(const vector<int> &files) const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
torrent_info const& info = torrent_handle::get_torrent_info();
|
||||
#else
|
||||
boost::intrusive_ptr<torrent_info const> info_ptr = torrent_handle::torrent_file();
|
||||
torrent_info const& info = *info_ptr;
|
||||
#endif
|
||||
if ((int)files.size() != info.num_files()) return;
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
bool was_seed = is_seed();
|
||||
qDebug() << Q_FUNC_INFO << "Changing files priorities...";
|
||||
torrent_handle::prioritize_files(files);
|
||||
qDebug() << Q_FUNC_INFO << "Moving unwanted files to .unwanted folder and conversely...";
|
||||
|
||||
QString spath = save_path();
|
||||
|
||||
for (uint i = 0; i < files.size(); ++i) {
|
||||
QString filepath = filepath_at(info, i);
|
||||
// Move unwanted files to a .unwanted subfolder
|
||||
if (files[i] == 0) {
|
||||
QString old_abspath = QDir(spath).absoluteFilePath(filepath);
|
||||
QString parent_abspath = fsutils::branchPath(old_abspath);
|
||||
// Make sure the file does not already exists
|
||||
if (QDir(parent_abspath).dirName() != ".unwanted") {
|
||||
QString unwanted_abspath = parent_abspath + "/.unwanted";
|
||||
QString new_abspath = unwanted_abspath + "/" + fsutils::fileName(filepath);
|
||||
qDebug() << "Unwanted path is" << unwanted_abspath;
|
||||
if (QFile::exists(new_abspath)) {
|
||||
qWarning() << "File" << new_abspath << "already exists at destination.";
|
||||
continue;
|
||||
}
|
||||
bool created = QDir().mkpath(unwanted_abspath);
|
||||
#ifdef Q_OS_WIN
|
||||
qDebug() << "unwanted folder was created:" << created;
|
||||
if (created) {
|
||||
// Hide the folder on Windows
|
||||
qDebug() << "Hiding folder (Windows)";
|
||||
wstring win_path = fsutils::toNativePath(unwanted_abspath).toStdWString();
|
||||
DWORD dwAttrs = GetFileAttributesW(win_path.c_str());
|
||||
bool ret = SetFileAttributesW(win_path.c_str(), dwAttrs | FILE_ATTRIBUTE_HIDDEN);
|
||||
Q_ASSERT(ret != 0); Q_UNUSED(ret);
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(created);
|
||||
#endif
|
||||
QString parent_path = fsutils::branchPath(filepath);
|
||||
if (!parent_path.isEmpty() && !parent_path.endsWith("/"))
|
||||
parent_path += "/";
|
||||
rename_file(i, parent_path + ".unwanted/" + fsutils::fileName(filepath));
|
||||
}
|
||||
}
|
||||
// Move wanted files back to their original folder
|
||||
if (files[i] > 0) {
|
||||
QString parent_relpath = fsutils::branchPath(filepath);
|
||||
if (QDir(parent_relpath).dirName() == ".unwanted") {
|
||||
QString old_name = fsutils::fileName(filepath);
|
||||
QString new_relpath = fsutils::branchPath(parent_relpath);
|
||||
if (new_relpath.isEmpty())
|
||||
rename_file(i, old_name);
|
||||
else
|
||||
rename_file(i, QDir(new_relpath).filePath(old_name));
|
||||
// Remove .unwanted directory if empty
|
||||
qDebug() << "Attempting to remove .unwanted folder at " << QDir(spath + "/" + new_relpath).absoluteFilePath(".unwanted");
|
||||
QDir(spath + "/" + new_relpath).rmdir(".unwanted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (was_seed && !is_seed()) {
|
||||
qDebug() << "Torrent is no longer SEEDING";
|
||||
// Save seed status
|
||||
TorrentPersistentData::instance()->saveSeedStatus(*this);
|
||||
// Move to temp folder if necessary
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
if (pref->isTempPathEnabled()) {
|
||||
QString tmp_path = pref->getTempPath();
|
||||
qDebug() << "tmp folder is enabled, move torrent to " << tmp_path << " from " << spath;
|
||||
move_storage(tmp_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QTorrentHandle::prioritize_first_last_piece(int file_index, bool b) const
|
||||
{
|
||||
// Determine the priority to set
|
||||
int prio = b ? 7 : torrent_handle::file_priority(file_index);
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10000
|
||||
torrent_info const* tf = &get_torrent_info();
|
||||
#else
|
||||
boost::intrusive_ptr<torrent_info const> tf = torrent_file();
|
||||
#endif
|
||||
|
||||
QPair<int, int> extremities = get_file_extremity_pieces(*tf, file_index);
|
||||
piece_priority(extremities.first, prio);
|
||||
piece_priority(extremities.second, prio);
|
||||
}
|
||||
|
||||
void QTorrentHandle::prioritize_first_last_piece(bool b) const
|
||||
{
|
||||
if (!has_metadata()) return;
|
||||
// Download first and last pieces first for all media files in the torrent
|
||||
const uint nbfiles = num_files();
|
||||
for (uint index = 0; index < nbfiles; ++index) {
|
||||
const QString path = filepath_at(index);
|
||||
const QString ext = fsutils::fileExtension(path);
|
||||
if (misc::isPreviewable(ext) && torrent_handle::file_priority(index) > 0) {
|
||||
qDebug() << "File" << path << "is previewable, toggle downloading of first/last pieces first";
|
||||
prioritize_first_last_piece(index, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QTorrentHandle::rename_file(int index, const QString& name) const
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << index << name;
|
||||
torrent_handle::rename_file(index, std::string(fsutils::toNativePath(name).toUtf8().constData()));
|
||||
}
|
||||
|
||||
//
|
||||
// Operators
|
||||
//
|
||||
|
||||
bool QTorrentHandle::operator ==(const QTorrentHandle& new_h) const
|
||||
{
|
||||
return info_hash() == new_h.info_hash();
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_paused(const libtorrent::torrent_status &status)
|
||||
{
|
||||
return status.paused && !status.auto_managed;
|
||||
}
|
||||
|
||||
int QTorrentHandle::queue_position(const libtorrent::torrent_status &status)
|
||||
{
|
||||
if (status.queue_position < 0)
|
||||
return -1;
|
||||
return status.queue_position + 1;
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_queued(const libtorrent::torrent_status &status)
|
||||
{
|
||||
return status.paused && status.auto_managed;
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_seed(const libtorrent::torrent_status &status)
|
||||
{
|
||||
return status.state == torrent_status::finished
|
||||
|| status.state == torrent_status::seeding;
|
||||
}
|
||||
|
||||
bool QTorrentHandle::is_checking(const libtorrent::torrent_status &status)
|
||||
{
|
||||
return status.state == torrent_status::checking_files
|
||||
|| status.state == torrent_status::checking_resume_data;
|
||||
}
|
||||
|
||||
bool QTorrentHandle::has_error(const libtorrent::torrent_status &status)
|
||||
{
|
||||
return status.paused && !status.error.empty();
|
||||
}
|
||||
|
||||
float QTorrentHandle::progress(const libtorrent::torrent_status &status)
|
||||
{
|
||||
if (!status.total_wanted)
|
||||
return 0.;
|
||||
if (status.total_wanted_done == status.total_wanted)
|
||||
return 1.;
|
||||
float progress = (float) status.total_wanted_done / (float) status.total_wanted;
|
||||
Q_ASSERT(progress >= 0.f && progress <= 1.f);
|
||||
return progress;
|
||||
}
|
||||
|
||||
QString QTorrentHandle::filepath_at(const libtorrent::torrent_info &info, unsigned int index)
|
||||
{
|
||||
return fsutils::fromNativePath(misc::toQStringU(info.files().file_path(index)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
QTorrentState::QTorrentState(int value)
|
||||
: m_value(value)
|
||||
{
|
||||
}
|
||||
|
||||
QString QTorrentState::toString() const
|
||||
{
|
||||
switch (m_value) {
|
||||
case Error:
|
||||
return "error";
|
||||
case Uploading:
|
||||
return "uploading";
|
||||
case PausedUploading:
|
||||
return "pausedUP";
|
||||
case QueuedUploading:
|
||||
return "queuedUP";
|
||||
case StalledUploading:
|
||||
return "stalledUP";
|
||||
case CheckingUploading:
|
||||
return "checkingUP";
|
||||
case Downloading:
|
||||
return "downloading";
|
||||
case PausedDownloading:
|
||||
return "pausedDL";
|
||||
case QueuedDownloading:
|
||||
return "queuedDL";
|
||||
case StalledDownloading:
|
||||
return "stalledDL";
|
||||
case CheckingDownloading:
|
||||
return "checkingDL";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
QTorrentState::operator int() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
168
src/core/qtlibtorrent/qtorrenthandle.h
Normal file
168
src/core/qtlibtorrent/qtorrenthandle.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef QTORRENTHANDLE_H
|
||||
#define QTORRENTHANDLE_H
|
||||
|
||||
#include <libtorrent/torrent_handle.hpp>
|
||||
|
||||
#include <QString>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QStringList;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class QTorrentState
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
Unknown = -1,
|
||||
|
||||
Error,
|
||||
|
||||
Uploading,
|
||||
PausedUploading,
|
||||
QueuedUploading,
|
||||
StalledUploading,
|
||||
CheckingUploading,
|
||||
|
||||
Downloading,
|
||||
PausedDownloading,
|
||||
QueuedDownloading,
|
||||
StalledDownloading,
|
||||
CheckingDownloading
|
||||
};
|
||||
|
||||
QTorrentState(int value);
|
||||
|
||||
operator int() const;
|
||||
QString toString() const;
|
||||
|
||||
private:
|
||||
int m_value;
|
||||
};
|
||||
|
||||
// A wrapper for torrent_handle in libtorrent
|
||||
// to interact well with Qt types
|
||||
class QTorrentHandle: public libtorrent::torrent_handle
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
|
||||
QTorrentHandle() {}
|
||||
explicit QTorrentHandle(const libtorrent::torrent_handle& h);
|
||||
|
||||
//
|
||||
// Getters
|
||||
//
|
||||
QString hash() const;
|
||||
QString name() const;
|
||||
QString current_tracker() const;
|
||||
bool is_paused() const;
|
||||
bool has_filtered_pieces() const;
|
||||
libtorrent::size_type total_size() const;
|
||||
libtorrent::size_type piece_length() const;
|
||||
int num_pieces() const;
|
||||
QString save_path() const;
|
||||
QString save_path_parsed() const;
|
||||
QStringList url_seeds() const;
|
||||
libtorrent::size_type actual_size() const;
|
||||
int num_files() const;
|
||||
int queue_position() const;
|
||||
bool is_queued() const;
|
||||
QString filename_at(unsigned int index) const;
|
||||
libtorrent::size_type filesize_at(unsigned int index) const;
|
||||
QString filepath_at(unsigned int index) const;
|
||||
QString orig_filepath_at(unsigned int index) const;
|
||||
libtorrent::torrent_status::state_t state() const;
|
||||
QString creator() const;
|
||||
QString comment() const;
|
||||
QStringList absolute_files_path() const;
|
||||
QStringList absolute_files_path_uneeded() const;
|
||||
bool has_missing_files() const;
|
||||
bool is_seed() const;
|
||||
bool is_checking() const;
|
||||
bool is_sequential_download() const;
|
||||
QString creation_date() const;
|
||||
qlonglong creation_date_unix() const;
|
||||
bool priv() const;
|
||||
bool first_last_piece_first() const;
|
||||
QString root_path() const;
|
||||
QString firstFileSavePath() const;
|
||||
bool has_error() const;
|
||||
QString error() const;
|
||||
void downloading_pieces(libtorrent::bitfield& bf) const;
|
||||
bool has_metadata() const;
|
||||
void file_progress(std::vector<libtorrent::size_type>& fp) const;
|
||||
QTorrentState torrentState() const;
|
||||
qulonglong eta() const;
|
||||
void toggleSequentialDownload();
|
||||
void toggleFirstLastPiecePrio();
|
||||
|
||||
//
|
||||
// Setters
|
||||
//
|
||||
void pause() const;
|
||||
void resume() const;
|
||||
void remove_url_seed(const QString& seed) const;
|
||||
void add_url_seed(const QString& seed) const;
|
||||
void set_tracker_login(const QString& username, const QString& password) const;
|
||||
void move_storage(const QString& path) const;
|
||||
void prioritize_first_last_piece(bool b) const;
|
||||
void rename_file(int index, const QString& name) const;
|
||||
bool save_torrent_file(const QString& path) const;
|
||||
void prioritize_files(const std::vector<int>& files) const;
|
||||
void file_priority(int index, int priority) const;
|
||||
|
||||
//
|
||||
// Operators
|
||||
//
|
||||
bool operator ==(const QTorrentHandle& new_h) const;
|
||||
|
||||
static bool is_paused(const libtorrent::torrent_status &status);
|
||||
static int queue_position(const libtorrent::torrent_status &status);
|
||||
static bool is_queued(const libtorrent::torrent_status &status);
|
||||
static bool is_seed(const libtorrent::torrent_status &status);
|
||||
static bool is_checking(const libtorrent::torrent_status &status);
|
||||
static bool has_error(const libtorrent::torrent_status &status);
|
||||
static float progress(const libtorrent::torrent_status &status);
|
||||
static QString filepath_at(const libtorrent::torrent_info &info, unsigned int index);
|
||||
|
||||
private:
|
||||
void prioritize_first_last_piece(int file_index, bool b) const;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
114
src/core/qtlibtorrent/shutdownconfirm.cpp
Normal file
114
src/core/qtlibtorrent/shutdownconfirm.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
* Copyright (C) 2014 sledgehammer999
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
* Contact : hammered999@gmail.com
|
||||
*/
|
||||
|
||||
#include "shutdownconfirm.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
ShutdownConfirmDlg::ShutdownConfirmDlg(const shutDownAction &action): exit_now(NULL), timeout(15), action0(action) {
|
||||
// Title and button
|
||||
if (action0 == NO_SHUTDOWN) {
|
||||
setWindowTitle(tr("Exit confirmation"));
|
||||
exit_now = addButton(tr("Exit now"), QMessageBox::AcceptRole);
|
||||
}
|
||||
else {
|
||||
setWindowTitle(tr("Shutdown confirmation"));
|
||||
exit_now = addButton(tr("Shutdown now"), QMessageBox::AcceptRole);
|
||||
}
|
||||
// Cancel Button
|
||||
addButton(QMessageBox::Cancel);
|
||||
// Text
|
||||
updateText();
|
||||
// Icon
|
||||
setIcon(QMessageBox::Warning);
|
||||
// Always on top
|
||||
setWindowFlags(windowFlags()|Qt::WindowStaysOnTopHint);
|
||||
// Set 'Cancel' as default button.
|
||||
setDefaultButton(QMessageBox::Cancel);
|
||||
timer.setInterval(1000); // 1sec
|
||||
connect(&timer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
|
||||
show();
|
||||
// Move to center
|
||||
move(misc::screenCenter(this));
|
||||
}
|
||||
|
||||
void ShutdownConfirmDlg::showEvent(QShowEvent *event) {
|
||||
QMessageBox::showEvent(event);
|
||||
timer.start();
|
||||
}
|
||||
|
||||
bool ShutdownConfirmDlg::askForConfirmation(const shutDownAction &action) {
|
||||
ShutdownConfirmDlg dlg(action);
|
||||
dlg.exec();
|
||||
return dlg.shutdown();
|
||||
}
|
||||
|
||||
void ShutdownConfirmDlg::updateSeconds() {
|
||||
--timeout;
|
||||
updateText();
|
||||
if (timeout == 0) {
|
||||
timer.stop();
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
bool ShutdownConfirmDlg::shutdown() const {
|
||||
// This is necessary because result() in the case of QMessageBox
|
||||
// returns a type of StandardButton, but since we use a custom button
|
||||
// it will return 0 instead, even though we set the 'accept' role on it.
|
||||
if (result() != QDialog::Accepted)
|
||||
return (clickedButton() == exit_now);
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShutdownConfirmDlg::updateText() {
|
||||
QString text;
|
||||
|
||||
switch (action0) {
|
||||
case NO_SHUTDOWN:
|
||||
text = tr("qBittorrent will now exit unless you cancel within the next %1 seconds.").arg(QString::number(timeout));
|
||||
break;
|
||||
case SHUTDOWN_COMPUTER:
|
||||
text = tr("The computer will now be switched off unless you cancel within the next %1 seconds.").arg(QString::number(timeout));
|
||||
break;
|
||||
case SUSPEND_COMPUTER:
|
||||
text = tr("The computer will now go to sleep mode unless you cancel within the next %1 seconds.").arg(QString::number(timeout));
|
||||
break;
|
||||
case HIBERNATE_COMPUTER:
|
||||
text = tr("The computer will now go to hibernation mode unless you cancel within the next %1 seconds.").arg(QString::number(timeout));
|
||||
break;
|
||||
}
|
||||
|
||||
setText(text);
|
||||
}
|
||||
|
||||
64
src/core/qtlibtorrent/shutdownconfirm.h
Normal file
64
src/core/qtlibtorrent/shutdownconfirm.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef SHUTDOWNCONFIRM_H
|
||||
#define SHUTDOWNCONFIRM_H
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include "misc.h"
|
||||
|
||||
class ShutdownConfirmDlg : public QMessageBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ShutdownConfirmDlg(const shutDownAction &action);
|
||||
bool shutdown() const;
|
||||
|
||||
static bool askForConfirmation(const shutDownAction &action);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event);
|
||||
|
||||
private slots:
|
||||
void updateSeconds();
|
||||
|
||||
private:
|
||||
// Methods
|
||||
void updateText();
|
||||
|
||||
// Vars
|
||||
QAbstractButton *exit_now;
|
||||
QTimer timer;
|
||||
int timeout;
|
||||
shutDownAction action0;
|
||||
};
|
||||
|
||||
#endif // SHUTDOWNCONFIRM_H
|
||||
643
src/core/qtlibtorrent/torrentmodel.cpp
Normal file
643
src/core/qtlibtorrent/torrentmodel.cpp
Normal file
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "torrentmodel.h"
|
||||
#include "torrentpersistentdata.h"
|
||||
#include "qbtsession.h"
|
||||
#include "fs_utils.h"
|
||||
|
||||
#include <libtorrent/session.hpp>
|
||||
|
||||
using namespace libtorrent;
|
||||
|
||||
namespace {
|
||||
QIcon get_paused_icon() {
|
||||
static QIcon cached = QIcon(":/icons/skin/paused.png");
|
||||
return cached;
|
||||
}
|
||||
|
||||
QIcon get_queued_icon() {
|
||||
static QIcon cached = QIcon(":/icons/skin/queued.png");
|
||||
return cached;
|
||||
}
|
||||
|
||||
QIcon get_downloading_icon() {
|
||||
static QIcon cached = QIcon(":/icons/skin/downloading.png");
|
||||
return cached;
|
||||
}
|
||||
|
||||
QIcon get_stalled_downloading_icon() {
|
||||
static QIcon cached = QIcon(":/icons/skin/stalledDL.png");
|
||||
return cached;
|
||||
}
|
||||
|
||||
QIcon get_uploading_icon() {
|
||||
static QIcon cached = QIcon(":/icons/skin/uploading.png");
|
||||
return cached;
|
||||
}
|
||||
|
||||
QIcon get_stalled_uploading_icon() {
|
||||
static QIcon cached = QIcon(":/icons/skin/stalledUP.png");
|
||||
return cached;
|
||||
}
|
||||
|
||||
QIcon get_checking_icon() {
|
||||
static QIcon cached = QIcon(":/icons/skin/checking.png");
|
||||
return cached;
|
||||
}
|
||||
|
||||
QIcon get_error_icon() {
|
||||
static QIcon cached = QIcon(":/icons/skin/error.png");
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
TorrentModelItem::TorrentModelItem(const QTorrentHandle &h)
|
||||
: m_torrent(h)
|
||||
, m_lastStatus(h.status(torrent_handle::query_accurate_download_counters))
|
||||
, m_addedTime(TorrentPersistentData::instance()->getAddedDate(h.hash()))
|
||||
, m_label(TorrentPersistentData::instance()->getLabel(h.hash()))
|
||||
, m_name(TorrentPersistentData::instance()->getName(h.hash()))
|
||||
, m_hash(h.hash())
|
||||
{
|
||||
if (m_name.isEmpty())
|
||||
m_name = h.name();
|
||||
}
|
||||
|
||||
void TorrentModelItem::refreshStatus(libtorrent::torrent_status const& status) {
|
||||
m_lastStatus = status;
|
||||
}
|
||||
|
||||
TorrentModelItem::State TorrentModelItem::state() const {
|
||||
try {
|
||||
// Pause or Queued
|
||||
if (m_torrent.is_paused(m_lastStatus)) {
|
||||
if (TorrentPersistentData::instance()->getHasMissingFiles(misc::toQString(m_lastStatus.info_hash)))
|
||||
return STATE_PAUSED_MISSING;
|
||||
else
|
||||
return m_torrent.is_seed(m_lastStatus) ? STATE_PAUSED_UP : STATE_PAUSED_DL;
|
||||
}
|
||||
|
||||
if (m_torrent.is_queued(m_lastStatus)
|
||||
&& m_lastStatus.state != torrent_status::queued_for_checking
|
||||
&& m_lastStatus.state != torrent_status::checking_resume_data
|
||||
&& m_lastStatus.state != torrent_status::checking_files)
|
||||
return m_torrent.is_seed(m_lastStatus) ? STATE_QUEUED_UP : STATE_QUEUED_DL;
|
||||
|
||||
// Other states
|
||||
switch(m_lastStatus.state) {
|
||||
case torrent_status::allocating:
|
||||
return STATE_ALLOCATING;
|
||||
case torrent_status::downloading_metadata:
|
||||
return STATE_DOWNLOADING_META;
|
||||
case torrent_status::downloading:
|
||||
return m_lastStatus.download_payload_rate > 0 ? STATE_DOWNLOADING : STATE_STALLED_DL;
|
||||
case torrent_status::finished:
|
||||
case torrent_status::seeding:
|
||||
return m_lastStatus.upload_payload_rate > 0 ? STATE_SEEDING : STATE_STALLED_UP;
|
||||
case torrent_status::queued_for_checking:
|
||||
return STATE_QUEUED_CHECK;
|
||||
case torrent_status::checking_resume_data:
|
||||
return STATE_QUEUED_FASTCHECK;
|
||||
case torrent_status::checking_files:
|
||||
return m_torrent.is_seed(m_lastStatus) ? STATE_CHECKING_UP : STATE_CHECKING_DL;
|
||||
default:
|
||||
return STATE_INVALID;
|
||||
}
|
||||
} catch(invalid_handle&) {
|
||||
return STATE_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
QIcon TorrentModelItem::getIconByState(State state) {
|
||||
switch (state) {
|
||||
case STATE_DOWNLOADING:
|
||||
case STATE_DOWNLOADING_META:
|
||||
return get_downloading_icon();
|
||||
case STATE_ALLOCATING:
|
||||
case STATE_STALLED_DL:
|
||||
return get_stalled_downloading_icon();
|
||||
case STATE_STALLED_UP:
|
||||
return get_stalled_uploading_icon();
|
||||
case STATE_SEEDING:
|
||||
return get_uploading_icon();
|
||||
case STATE_PAUSED_DL:
|
||||
case STATE_PAUSED_UP:
|
||||
return get_paused_icon();
|
||||
case STATE_QUEUED_DL:
|
||||
case STATE_QUEUED_UP:
|
||||
return get_queued_icon();
|
||||
case STATE_CHECKING_UP:
|
||||
case STATE_CHECKING_DL:
|
||||
case STATE_QUEUED_CHECK:
|
||||
case STATE_QUEUED_FASTCHECK:
|
||||
return get_checking_icon();
|
||||
case STATE_INVALID:
|
||||
case STATE_PAUSED_MISSING:
|
||||
return get_error_icon();
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return get_error_icon();
|
||||
}
|
||||
}
|
||||
|
||||
QColor TorrentModelItem::getColorByState(State state) {
|
||||
switch (state) {
|
||||
case STATE_DOWNLOADING:
|
||||
case STATE_DOWNLOADING_META:
|
||||
return QColor(0, 128, 0); // green
|
||||
case STATE_ALLOCATING:
|
||||
case STATE_STALLED_DL:
|
||||
case STATE_STALLED_UP:
|
||||
return QColor(128, 128, 128); // grey
|
||||
case STATE_SEEDING:
|
||||
return QColor(255, 165, 0); // orange
|
||||
case STATE_PAUSED_DL:
|
||||
case STATE_PAUSED_UP:
|
||||
case STATE_PAUSED_MISSING:
|
||||
return QColor(255, 0, 0); // red
|
||||
case STATE_QUEUED_DL:
|
||||
case STATE_QUEUED_UP:
|
||||
case STATE_CHECKING_UP:
|
||||
case STATE_CHECKING_DL:
|
||||
case STATE_QUEUED_CHECK:
|
||||
case STATE_QUEUED_FASTCHECK:
|
||||
return QColor(128, 128, 128); // grey
|
||||
case STATE_INVALID:
|
||||
return QColor(255, 0, 0); // red
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return QColor(255, 0, 0); // red
|
||||
}
|
||||
}
|
||||
|
||||
bool TorrentModelItem::setData(int column, const QVariant &value, int role)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << column << value;
|
||||
if (role != Qt::DisplayRole) return false;
|
||||
// Label, seed date and Name columns can be edited
|
||||
switch(column) {
|
||||
case TR_NAME:
|
||||
m_name = value.toString();
|
||||
TorrentPersistentData::instance()->saveName(m_torrent.hash(), m_name);
|
||||
return true;
|
||||
case TR_LABEL: {
|
||||
QString new_label = value.toString();
|
||||
if (m_label != new_label) {
|
||||
QString old_label = m_label;
|
||||
m_label = new_label;
|
||||
TorrentPersistentData::instance()->saveLabel(m_torrent.hash(), new_label);
|
||||
emit labelChanged(old_label, new_label);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant TorrentModelItem::data(int column, int role) const
|
||||
{
|
||||
if (role == Qt::DecorationRole && column == TR_NAME) {
|
||||
return getIconByState(state());
|
||||
}
|
||||
if (role == Qt::ForegroundRole) {
|
||||
return getColorByState(state());
|
||||
}
|
||||
if (role != Qt::DisplayRole && role != Qt::UserRole) return QVariant();
|
||||
switch(column) {
|
||||
case TR_NAME:
|
||||
return m_name.isEmpty() ? m_torrent.name() : m_name;
|
||||
case TR_PRIORITY: {
|
||||
int pos = m_torrent.queue_position(m_lastStatus);
|
||||
if (pos > -1)
|
||||
return pos - HiddenData::getSize();
|
||||
else
|
||||
return pos;
|
||||
}
|
||||
case TR_SIZE:
|
||||
return m_lastStatus.has_metadata ? static_cast<qlonglong>(m_lastStatus.total_wanted) : -1;
|
||||
case TR_PROGRESS:
|
||||
return m_torrent.progress(m_lastStatus);
|
||||
case TR_STATUS:
|
||||
return state();
|
||||
case TR_SEEDS: {
|
||||
return (role == Qt::DisplayRole) ? m_lastStatus.num_seeds : m_lastStatus.num_complete;
|
||||
}
|
||||
case TR_PEERS: {
|
||||
return (role == Qt::DisplayRole) ? (m_lastStatus.num_peers-m_lastStatus.num_seeds) : m_lastStatus.num_incomplete;
|
||||
}
|
||||
case TR_DLSPEED:
|
||||
return m_lastStatus.download_payload_rate;
|
||||
case TR_UPSPEED:
|
||||
return m_lastStatus.upload_payload_rate;
|
||||
case TR_ETA: {
|
||||
// XXX: Is this correct?
|
||||
if (m_torrent.is_paused(m_lastStatus) || m_torrent.is_queued(m_lastStatus)) return MAX_ETA;
|
||||
return QBtSession::instance()->getETA(m_hash, m_lastStatus);
|
||||
}
|
||||
case TR_RATIO:
|
||||
return QBtSession::instance()->getRealRatio(m_lastStatus);
|
||||
case TR_LABEL:
|
||||
return m_label;
|
||||
case TR_ADD_DATE:
|
||||
return m_addedTime;
|
||||
case TR_SEED_DATE:
|
||||
return m_lastStatus.completed_time ? QDateTime::fromTime_t(m_lastStatus.completed_time) : QDateTime();
|
||||
case TR_TRACKER:
|
||||
return misc::toQString(m_lastStatus.current_tracker);
|
||||
case TR_DLLIMIT:
|
||||
return m_torrent.download_limit();
|
||||
case TR_UPLIMIT:
|
||||
return m_torrent.upload_limit();
|
||||
case TR_AMOUNT_DOWNLOADED:
|
||||
return static_cast<qlonglong>(m_lastStatus.all_time_download);
|
||||
case TR_AMOUNT_UPLOADED:
|
||||
return static_cast<qlonglong>(m_lastStatus.all_time_upload);
|
||||
case TR_AMOUNT_LEFT:
|
||||
return static_cast<qlonglong>(m_lastStatus.total_wanted - m_lastStatus.total_wanted_done);
|
||||
case TR_TIME_ELAPSED:
|
||||
return (role == Qt::DisplayRole) ? m_lastStatus.active_time : m_lastStatus.seeding_time;
|
||||
case TR_SAVE_PATH:
|
||||
return fsutils::toNativePath(m_torrent.save_path_parsed());
|
||||
case TR_COMPLETED:
|
||||
return static_cast<qlonglong>(m_lastStatus.total_wanted_done);
|
||||
case TR_RATIO_LIMIT: {
|
||||
QString hash = misc::toQString(m_lastStatus.info_hash);
|
||||
return QBtSession::instance()->getMaxRatioPerTorrent(hash, NULL);
|
||||
}
|
||||
case TR_SEEN_COMPLETE_DATE:
|
||||
return m_lastStatus.last_seen_complete ? QDateTime::fromTime_t(m_lastStatus.last_seen_complete) : QDateTime();
|
||||
case TR_LAST_ACTIVITY:
|
||||
if (m_torrent.is_paused(m_lastStatus) || m_torrent.is_checking(m_lastStatus))
|
||||
return -1;
|
||||
if (m_lastStatus.time_since_upload < m_lastStatus.time_since_download)
|
||||
return m_lastStatus.time_since_upload;
|
||||
else
|
||||
return m_lastStatus.time_since_download;
|
||||
case TR_TOTAL_SIZE:
|
||||
return m_lastStatus.has_metadata ? static_cast<qlonglong>(m_torrent.total_size()) : -1;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
// TORRENT MODEL
|
||||
|
||||
TorrentModel::TorrentModel(QObject *parent) :
|
||||
QAbstractListModel(parent), m_refreshInterval(2000)
|
||||
{
|
||||
}
|
||||
|
||||
void TorrentModel::populate() {
|
||||
// Load the torrents
|
||||
std::vector<torrent_handle> torrents = QBtSession::instance()->getSession()->get_torrents();
|
||||
|
||||
std::vector<torrent_handle>::const_iterator it = torrents.begin();
|
||||
std::vector<torrent_handle>::const_iterator itend = torrents.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
const QTorrentHandle h(*it);
|
||||
if (HiddenData::hasData(h.hash()))
|
||||
continue;
|
||||
addTorrent(h);
|
||||
}
|
||||
// Refresh timer
|
||||
connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(forceModelRefresh()));
|
||||
m_refreshTimer.start(m_refreshInterval);
|
||||
// Listen for torrent changes
|
||||
connect(QBtSession::instance(), SIGNAL(addedTorrent(QTorrentHandle)), SLOT(addTorrent(QTorrentHandle)));
|
||||
connect(QBtSession::instance(), SIGNAL(torrentAboutToBeRemoved(QTorrentHandle)), SLOT(handleTorrentAboutToBeRemoved(QTorrentHandle)));
|
||||
connect(QBtSession::instance(), SIGNAL(finishedTorrent(QTorrentHandle)), SLOT(handleFinishedTorrent(QTorrentHandle)));
|
||||
connect(QBtSession::instance(), SIGNAL(metadataReceived(QTorrentHandle)), SLOT(handleTorrentUpdate(QTorrentHandle)));
|
||||
connect(QBtSession::instance(), SIGNAL(resumedTorrent(QTorrentHandle)), SLOT(handleTorrentUpdate(QTorrentHandle)));
|
||||
connect(QBtSession::instance(), SIGNAL(pausedTorrent(QTorrentHandle)), SLOT(handleTorrentUpdate(QTorrentHandle)));
|
||||
connect(QBtSession::instance(), SIGNAL(torrentFinishedChecking(QTorrentHandle)), SLOT(handleTorrentUpdate(QTorrentHandle)));
|
||||
connect(QBtSession::instance(), SIGNAL(stateUpdate(std::vector<libtorrent::torrent_status>)), SLOT(stateUpdated(std::vector<libtorrent::torrent_status>)));
|
||||
}
|
||||
|
||||
TorrentModel::~TorrentModel() {
|
||||
qDebug() << Q_FUNC_INFO << "ENTER";
|
||||
qDeleteAll(m_torrents);
|
||||
m_torrents.clear();
|
||||
qDebug() << Q_FUNC_INFO << "EXIT";
|
||||
}
|
||||
|
||||
QVariant TorrentModel::headerData(int section, Qt::Orientation orientation,
|
||||
int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal) {
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch(section) {
|
||||
case TorrentModelItem::TR_NAME: return tr("Name", "i.e: torrent name");
|
||||
case TorrentModelItem::TR_PRIORITY: return "#";
|
||||
case TorrentModelItem::TR_SIZE: return tr("Size", "i.e: torrent size");
|
||||
case TorrentModelItem::TR_PROGRESS: return tr("Done", "% Done");
|
||||
case TorrentModelItem::TR_STATUS: return tr("Status", "Torrent status (e.g. downloading, seeding, paused)");
|
||||
case TorrentModelItem::TR_SEEDS: return tr("Seeds", "i.e. full sources (often untranslated)");
|
||||
case TorrentModelItem::TR_PEERS: return tr("Peers", "i.e. partial sources (often untranslated)");
|
||||
case TorrentModelItem::TR_DLSPEED: return tr("Down Speed", "i.e: Download speed");
|
||||
case TorrentModelItem::TR_UPSPEED: return tr("Up Speed", "i.e: Upload speed");
|
||||
case TorrentModelItem::TR_RATIO: return tr("Ratio", "Share ratio");
|
||||
case TorrentModelItem::TR_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left");
|
||||
case TorrentModelItem::TR_LABEL: return tr("Label");
|
||||
case TorrentModelItem::TR_ADD_DATE: return tr("Added On", "Torrent was added to transfer list on 01/01/2010 08:00");
|
||||
case TorrentModelItem::TR_SEED_DATE: return tr("Completed On", "Torrent was completed on 01/01/2010 08:00");
|
||||
case TorrentModelItem::TR_TRACKER: return tr("Tracker");
|
||||
case TorrentModelItem::TR_DLLIMIT: return tr("Down Limit", "i.e: Download limit");
|
||||
case TorrentModelItem::TR_UPLIMIT: return tr("Up Limit", "i.e: Upload limit");
|
||||
case TorrentModelItem::TR_AMOUNT_DOWNLOADED: return tr("Downloaded", "Amount of data downloaded (e.g. in MB)");
|
||||
case TorrentModelItem::TR_AMOUNT_UPLOADED: return tr("Uploaded", "Amount of data uploaded (e.g. in MB)");
|
||||
case TorrentModelItem::TR_AMOUNT_LEFT: return tr("Remaining", "Amount of data left to download (e.g. in MB)");
|
||||
case TorrentModelItem::TR_TIME_ELAPSED: return tr("Time Active", "Time (duration) the torrent is active (not paused)");
|
||||
case TorrentModelItem::TR_SAVE_PATH: return tr("Save path", "Torrent save path");
|
||||
case TorrentModelItem::TR_COMPLETED: return tr("Completed", "Amount of data completed (e.g. in MB)");
|
||||
case TorrentModelItem::TR_RATIO_LIMIT: return tr("Ratio Limit", "Upload share ratio limit");
|
||||
case TorrentModelItem::TR_SEEN_COMPLETE_DATE: return tr("Last Seen Complete", "Indicates the time when the torrent was last seen complete/whole");
|
||||
case TorrentModelItem::TR_LAST_ACTIVITY: return tr("Last Activity", "Time passed since a chunk was downloaded/uploaded");
|
||||
case TorrentModelItem::TR_TOTAL_SIZE: return tr("Total Size", "i.e. Size including unwanted data");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
else if (role == Qt::TextAlignmentRole) {
|
||||
switch(section) {
|
||||
case TorrentModelItem::TR_AMOUNT_DOWNLOADED:
|
||||
case TorrentModelItem::TR_AMOUNT_UPLOADED:
|
||||
case TorrentModelItem::TR_AMOUNT_LEFT:
|
||||
case TorrentModelItem::TR_COMPLETED:
|
||||
case TorrentModelItem::TR_SIZE:
|
||||
case TorrentModelItem::TR_TOTAL_SIZE:
|
||||
case TorrentModelItem::TR_ETA:
|
||||
case TorrentModelItem::TR_SEEDS:
|
||||
case TorrentModelItem::TR_PEERS:
|
||||
case TorrentModelItem::TR_UPSPEED:
|
||||
case TorrentModelItem::TR_DLSPEED:
|
||||
case TorrentModelItem::TR_UPLIMIT:
|
||||
case TorrentModelItem::TR_DLLIMIT:
|
||||
case TorrentModelItem::TR_RATIO_LIMIT:
|
||||
case TorrentModelItem::TR_RATIO:
|
||||
case TorrentModelItem::TR_PRIORITY:
|
||||
case TorrentModelItem::TR_LAST_ACTIVITY:
|
||||
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
|
||||
default:
|
||||
return QAbstractListModel::headerData(section, orientation, role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant TorrentModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) return QVariant();
|
||||
try {
|
||||
if (index.row() >= 0 && index.row() < rowCount() && index.column() >= 0 && index.column() < columnCount())
|
||||
return m_torrents[index.row()]->data(index.column(), role);
|
||||
} catch(invalid_handle&) {}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool TorrentModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << value;
|
||||
if (!index.isValid() || role != Qt::DisplayRole) return false;
|
||||
qDebug("Index is valid and role is DisplayRole");
|
||||
try {
|
||||
if (index.row() >= 0 && index.row() < rowCount() && index.column() >= 0 && index.column() < columnCount()) {
|
||||
bool change = m_torrents[index.row()]->setData(index.column(), value, role);
|
||||
if (change)
|
||||
notifyTorrentChanged(index.row());
|
||||
return change;
|
||||
}
|
||||
} catch(invalid_handle&) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
int TorrentModel::torrentRow(const QString &hash) const
|
||||
{
|
||||
int row = 0;
|
||||
|
||||
QList<TorrentModelItem*>::const_iterator it = m_torrents.constBegin();
|
||||
QList<TorrentModelItem*>::const_iterator itend = m_torrents.constEnd();
|
||||
for ( ; it != itend; ++it) {
|
||||
if ((*it)->hash() == hash) return row;
|
||||
++row;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TorrentModel::addTorrent(const QTorrentHandle &h)
|
||||
{
|
||||
if (torrentRow(h.hash()) < 0) {
|
||||
beginInsertTorrent(m_torrents.size());
|
||||
TorrentModelItem *item = new TorrentModelItem(h);
|
||||
connect(item, SIGNAL(labelChanged(QString,QString)), SLOT(handleTorrentLabelChange(QString,QString)));
|
||||
m_torrents << item;
|
||||
emit torrentAdded(item);
|
||||
endInsertTorrent();
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentModel::beginInsertTorrent(int row)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
}
|
||||
|
||||
void TorrentModel::endInsertTorrent()
|
||||
{
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void TorrentModel::beginRemoveTorrent(int row)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
}
|
||||
|
||||
void TorrentModel::endRemoveTorrent()
|
||||
{
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void TorrentModel::handleTorrentUpdate(const QTorrentHandle &h)
|
||||
{
|
||||
const int row = torrentRow(h.hash());
|
||||
if (row >= 0) {
|
||||
m_torrents[row]->refreshStatus(h.status(torrent_handle::query_accurate_download_counters));
|
||||
notifyTorrentChanged(row);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentModel::handleFinishedTorrent(const QTorrentHandle& h)
|
||||
{
|
||||
const int row = torrentRow(h.hash());
|
||||
if (row < 0)
|
||||
return;
|
||||
|
||||
// Update completion date
|
||||
m_torrents[row]->refreshStatus(h.status(torrent_handle::query_accurate_download_counters));
|
||||
notifyTorrentChanged(row);
|
||||
}
|
||||
|
||||
void TorrentModel::notifyTorrentChanged(int row)
|
||||
{
|
||||
emit dataChanged(index(row, 0), index(row, columnCount()-1));
|
||||
}
|
||||
|
||||
void TorrentModel::setRefreshInterval(int refreshInterval)
|
||||
{
|
||||
if (m_refreshInterval != refreshInterval) {
|
||||
m_refreshInterval = refreshInterval;
|
||||
m_refreshTimer.stop();
|
||||
m_refreshTimer.start(m_refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentModel::forceModelRefresh()
|
||||
{
|
||||
QBtSession::instance()->postTorrentUpdate();
|
||||
}
|
||||
|
||||
TorrentStatusReport TorrentModel::getTorrentStatusReport() const
|
||||
{
|
||||
TorrentStatusReport report;
|
||||
|
||||
QList<TorrentModelItem*>::const_iterator it = m_torrents.constBegin();
|
||||
QList<TorrentModelItem*>::const_iterator itend = m_torrents.constEnd();
|
||||
for ( ; it != itend; ++it) {
|
||||
switch((*it)->state()) {
|
||||
case TorrentModelItem::STATE_DOWNLOADING:
|
||||
++report.nb_active;
|
||||
++report.nb_downloading;
|
||||
break;
|
||||
case TorrentModelItem::STATE_DOWNLOADING_META:
|
||||
++report.nb_downloading;
|
||||
break;
|
||||
case TorrentModelItem::STATE_PAUSED_DL:
|
||||
++report.nb_paused;
|
||||
case TorrentModelItem::STATE_STALLED_DL:
|
||||
case TorrentModelItem::STATE_CHECKING_DL:
|
||||
case TorrentModelItem::STATE_QUEUED_DL: {
|
||||
++report.nb_inactive;
|
||||
++report.nb_downloading;
|
||||
break;
|
||||
}
|
||||
case TorrentModelItem::STATE_SEEDING:
|
||||
++report.nb_active;
|
||||
++report.nb_seeding;
|
||||
break;
|
||||
case TorrentModelItem::STATE_PAUSED_UP:
|
||||
case TorrentModelItem::STATE_PAUSED_MISSING:
|
||||
++report.nb_paused;
|
||||
case TorrentModelItem::STATE_STALLED_UP:
|
||||
case TorrentModelItem::STATE_CHECKING_UP:
|
||||
case TorrentModelItem::STATE_QUEUED_UP: {
|
||||
++report.nb_seeding;
|
||||
++report.nb_inactive;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
Qt::ItemFlags TorrentModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return 0;
|
||||
// Explicitely mark as editable
|
||||
return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
void TorrentModel::handleTorrentLabelChange(QString previous, QString current)
|
||||
{
|
||||
emit torrentChangedLabel(static_cast<TorrentModelItem*>(sender()), previous, current);
|
||||
}
|
||||
|
||||
QString TorrentModel::torrentHash(int row) const
|
||||
{
|
||||
if (row >= 0 && row < rowCount())
|
||||
return m_torrents.at(row)->hash();
|
||||
return QString();
|
||||
}
|
||||
|
||||
void TorrentModel::handleTorrentAboutToBeRemoved(const QTorrentHandle &h)
|
||||
{
|
||||
const int row = torrentRow(h.hash());
|
||||
qDebug() << Q_FUNC_INFO << row;
|
||||
if (row >= 0) {
|
||||
emit torrentAboutToBeRemoved(m_torrents.at(row));
|
||||
|
||||
beginRemoveTorrent(row);
|
||||
delete m_torrents[row];
|
||||
m_torrents.removeAt(row);
|
||||
endRemoveTorrent();
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentModel::stateUpdated(const std::vector<libtorrent::torrent_status> &statuses) {
|
||||
typedef std::vector<libtorrent::torrent_status> statuses_t;
|
||||
|
||||
for (statuses_t::const_iterator i = statuses.begin(), end = statuses.end(); i != end; ++i) {
|
||||
libtorrent::torrent_status const& status = *i;
|
||||
|
||||
const int row = torrentRow(misc::toQString(status.info_hash));
|
||||
if (row >= 0) {
|
||||
m_torrents[row]->refreshStatus(status);
|
||||
notifyTorrentChanged(row);
|
||||
}
|
||||
}
|
||||
|
||||
emit modelRefreshed();
|
||||
}
|
||||
|
||||
bool TorrentModel::inhibitSystem()
|
||||
{
|
||||
QList<TorrentModelItem*>::const_iterator it = m_torrents.constBegin();
|
||||
QList<TorrentModelItem*>::const_iterator itend = m_torrents.constEnd();
|
||||
for ( ; it != itend; ++it) {
|
||||
switch((*it)->data(TorrentModelItem::TR_STATUS).toInt()) {
|
||||
case TorrentModelItem::STATE_DOWNLOADING:
|
||||
case TorrentModelItem::STATE_DOWNLOADING_META:
|
||||
case TorrentModelItem::STATE_STALLED_DL:
|
||||
case TorrentModelItem::STATE_SEEDING:
|
||||
case TorrentModelItem::STATE_STALLED_UP:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
128
src/core/qtlibtorrent/torrentmodel.h
Normal file
128
src/core/qtlibtorrent/torrentmodel.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef TORRENTMODEL_H
|
||||
#define TORRENTMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QDateTime>
|
||||
#include <QIcon>
|
||||
#include <QTimer>
|
||||
|
||||
#include "qtorrenthandle.h"
|
||||
|
||||
struct TorrentStatusReport {
|
||||
TorrentStatusReport(): nb_downloading(0), nb_seeding(0), nb_active(0), nb_inactive(0), nb_paused(0) {}
|
||||
uint nb_downloading; uint nb_seeding; uint nb_active; uint nb_inactive; uint nb_paused;
|
||||
};
|
||||
|
||||
class TorrentModelItem : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum State {STATE_DOWNLOADING, STATE_DOWNLOADING_META, STATE_ALLOCATING, STATE_STALLED_DL, STATE_STALLED_UP, STATE_SEEDING, STATE_PAUSED_DL, STATE_PAUSED_UP, STATE_PAUSED_MISSING, STATE_QUEUED_DL, STATE_QUEUED_UP, STATE_CHECKING_UP, STATE_CHECKING_DL, STATE_QUEUED_CHECK, STATE_QUEUED_FASTCHECK, STATE_INVALID};
|
||||
enum Column {TR_NAME, TR_PRIORITY, TR_SIZE, TR_TOTAL_SIZE, TR_PROGRESS, TR_STATUS, TR_SEEDS, TR_PEERS, TR_DLSPEED, TR_UPSPEED, TR_ETA, TR_RATIO, TR_LABEL, TR_ADD_DATE, TR_SEED_DATE, TR_TRACKER, TR_DLLIMIT, TR_UPLIMIT, TR_AMOUNT_DOWNLOADED, TR_AMOUNT_UPLOADED, TR_AMOUNT_LEFT, TR_TIME_ELAPSED, TR_SAVE_PATH, TR_COMPLETED, TR_RATIO_LIMIT, TR_SEEN_COMPLETE_DATE, TR_LAST_ACTIVITY, NB_COLUMNS};
|
||||
|
||||
public:
|
||||
TorrentModelItem(const QTorrentHandle& h);
|
||||
void refreshStatus(libtorrent::torrent_status const& status);
|
||||
inline int columnCount() const { return NB_COLUMNS; }
|
||||
QVariant data(int column, int role = Qt::DisplayRole) const;
|
||||
bool setData(int column, const QVariant &value, int role = Qt::DisplayRole);
|
||||
inline QString const& hash() const { return m_hash; }
|
||||
State state() const;
|
||||
|
||||
signals:
|
||||
void labelChanged(QString previous, QString current);
|
||||
|
||||
private:
|
||||
static QIcon getIconByState(State state);
|
||||
static QColor getColorByState(State state);
|
||||
|
||||
private:
|
||||
QTorrentHandle m_torrent;
|
||||
libtorrent::torrent_status m_lastStatus;
|
||||
QDateTime m_addedTime;
|
||||
QString m_label;
|
||||
QString m_name;
|
||||
QString m_hash; // Cached for safety reasons
|
||||
};
|
||||
|
||||
class TorrentModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TorrentModel)
|
||||
|
||||
public:
|
||||
explicit TorrentModel(QObject *parent = 0);
|
||||
~TorrentModel();
|
||||
inline int rowCount(const QModelIndex& index = QModelIndex()) const { Q_UNUSED(index); return m_torrents.size(); }
|
||||
int columnCount(const QModelIndex &parent=QModelIndex()) const { Q_UNUSED(parent); return TorrentModelItem::NB_COLUMNS; }
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole);
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
int torrentRow(const QString &hash) const;
|
||||
QString torrentHash(int row) const;
|
||||
void setRefreshInterval(int refreshInterval);
|
||||
TorrentStatusReport getTorrentStatusReport() const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
void populate();
|
||||
bool inhibitSystem();
|
||||
|
||||
signals:
|
||||
void torrentAdded(TorrentModelItem *torrentItem);
|
||||
void torrentAboutToBeRemoved(TorrentModelItem *torrentItem);
|
||||
void torrentChangedLabel(TorrentModelItem *torrentItem, QString previous, QString current);
|
||||
void modelRefreshed();
|
||||
|
||||
private slots:
|
||||
void addTorrent(const QTorrentHandle& h);
|
||||
void handleTorrentUpdate(const QTorrentHandle &h);
|
||||
void handleFinishedTorrent(const QTorrentHandle& h);
|
||||
void notifyTorrentChanged(int row);
|
||||
void forceModelRefresh();
|
||||
void handleTorrentLabelChange(QString previous, QString current);
|
||||
void handleTorrentAboutToBeRemoved(const QTorrentHandle & h);
|
||||
void stateUpdated(const std::vector<libtorrent::torrent_status> &statuses);
|
||||
|
||||
private:
|
||||
void beginInsertTorrent(int row);
|
||||
void endInsertTorrent();
|
||||
void beginRemoveTorrent(int row);
|
||||
void endRemoveTorrent();
|
||||
|
||||
private:
|
||||
QList<TorrentModelItem*> m_torrents;
|
||||
int m_refreshInterval;
|
||||
QTimer m_refreshTimer;
|
||||
};
|
||||
|
||||
#endif // TORRENTMODEL_H
|
||||
195
src/core/qtlibtorrent/torrentspeedmonitor.cpp
Normal file
195
src/core/qtlibtorrent/torrentspeedmonitor.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QList>
|
||||
|
||||
#include "qbtsession.h"
|
||||
#include "misc.h"
|
||||
#include "torrentspeedmonitor.h"
|
||||
|
||||
using namespace libtorrent;
|
||||
|
||||
namespace {
|
||||
|
||||
template<class T> struct Sample {
|
||||
Sample()
|
||||
: download()
|
||||
, upload()
|
||||
{}
|
||||
|
||||
Sample(T download, T upload)
|
||||
: download(download)
|
||||
, upload(upload)
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
explicit Sample(Sample<U> other)
|
||||
: download(static_cast<U>(other.download))
|
||||
, upload(static_cast<U>(other.upload))
|
||||
{}
|
||||
|
||||
T download;
|
||||
T upload;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Sample<T>& operator+=(Sample<T>& lhs, Sample<T> const& rhs) {
|
||||
lhs.download += rhs.download;
|
||||
lhs.upload += rhs.upload;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Sample<T>& operator-=(Sample<T>& lhs, Sample<T> const& rhs) {
|
||||
lhs.download -= rhs.download;
|
||||
lhs.upload -= rhs.upload;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Sample<T> operator+(Sample<T> const& lhs, Sample<T> const& rhs) {
|
||||
return Sample<T>(lhs.download + rhs.download, lhs.upload + rhs.upload);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Sample<T> operator-(Sample<T> const& lhs, Sample<T> const& rhs) {
|
||||
return Sample<T>(lhs.download - rhs.download, lhs.upload - rhs.upload);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Sample<T> operator*(Sample<T> const& lhs, T rhs) {
|
||||
return Sample<T>(lhs.download * rhs, lhs.upload * rhs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Sample<T> operator*(T lhs,Sample<T> const& rhs) {
|
||||
return Sample<T>(lhs * rhs.download, lhs * rhs.upload);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Sample<T> operator/(Sample<T> const& lhs, T rhs) {
|
||||
return Sample<T>(lhs.download / rhs, lhs.upload / rhs);
|
||||
}
|
||||
}
|
||||
|
||||
class SpeedSample {
|
||||
|
||||
public:
|
||||
SpeedSample() {}
|
||||
void addSample(Sample<int> const& item);
|
||||
Sample<qreal> average() const;
|
||||
|
||||
private:
|
||||
static const int max_samples = 30;
|
||||
|
||||
private:
|
||||
QList<Sample<int> > m_speedSamples;
|
||||
Sample<long long> m_sum;
|
||||
};
|
||||
|
||||
TorrentSpeedMonitor::TorrentSpeedMonitor(QBtSession* session)
|
||||
: m_session(session)
|
||||
{
|
||||
connect(m_session, SIGNAL(torrentAboutToBeRemoved(QTorrentHandle)), SLOT(removeSamples(QTorrentHandle)));
|
||||
connect(m_session, SIGNAL(pausedTorrent(QTorrentHandle)), SLOT(removeSamples(QTorrentHandle)));
|
||||
connect(m_session, SIGNAL(statsReceived(libtorrent::stats_alert)), SLOT(statsReceived(libtorrent::stats_alert)));
|
||||
}
|
||||
|
||||
TorrentSpeedMonitor::~TorrentSpeedMonitor()
|
||||
{}
|
||||
|
||||
void SpeedSample::addSample(Sample<int> const& item)
|
||||
{
|
||||
m_speedSamples.push_back(item);
|
||||
m_sum += Sample<long long>(item);
|
||||
if (m_speedSamples.size() > max_samples) {
|
||||
m_sum -= Sample<long long>(m_speedSamples.front());
|
||||
m_speedSamples.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
Sample<qreal> SpeedSample::average() const
|
||||
{
|
||||
if (m_speedSamples.empty())
|
||||
return Sample<qreal>();
|
||||
|
||||
return Sample<qreal>(m_sum) * (qreal(1.) / m_speedSamples.size());
|
||||
}
|
||||
|
||||
void TorrentSpeedMonitor::removeSamples(const QTorrentHandle& h) {
|
||||
try {
|
||||
m_samples.remove(h.hash());
|
||||
} catch(invalid_handle&) {}
|
||||
}
|
||||
|
||||
qlonglong TorrentSpeedMonitor::getETA(const QString &hash, const libtorrent::torrent_status &status) const
|
||||
{
|
||||
if (QTorrentHandle::is_paused(status))
|
||||
return MAX_ETA;
|
||||
|
||||
QHash<QString, SpeedSample>::const_iterator i = m_samples.find(hash);
|
||||
if (i == m_samples.end())
|
||||
return MAX_ETA;
|
||||
|
||||
const Sample<qreal> speed_average = i->average();
|
||||
|
||||
if (QTorrentHandle::is_seed(status)) {
|
||||
if (!speed_average.upload)
|
||||
return MAX_ETA;
|
||||
|
||||
bool _unused;
|
||||
qreal max_ratio = m_session->getMaxRatioPerTorrent(hash, &_unused);
|
||||
if (max_ratio < 0)
|
||||
return MAX_ETA;
|
||||
|
||||
libtorrent::size_type realDL = status.all_time_download;
|
||||
if (realDL <= 0)
|
||||
realDL = status.total_wanted;
|
||||
|
||||
return (realDL * max_ratio - status.all_time_upload) / speed_average.upload;
|
||||
}
|
||||
|
||||
if (!speed_average.download)
|
||||
return MAX_ETA;
|
||||
|
||||
return (status.total_wanted - status.total_wanted_done) / speed_average.download;
|
||||
}
|
||||
|
||||
void TorrentSpeedMonitor::statsReceived(const stats_alert &stats)
|
||||
{
|
||||
Q_ASSERT(stats.interval >= 1000);
|
||||
|
||||
Sample<int> transferred(stats.transferred[stats_alert::download_payload],
|
||||
stats.transferred[stats_alert::upload_payload]);
|
||||
|
||||
Sample<int> normalized = Sample<int>(Sample<long long>(transferred) * 1000LL / static_cast<long long>(stats.interval));
|
||||
|
||||
m_samples[misc::toQString(stats.handle.info_hash())].addSample(normalized);
|
||||
}
|
||||
62
src/core/qtlibtorrent/torrentspeedmonitor.h
Normal file
62
src/core/qtlibtorrent/torrentspeedmonitor.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef TORRENTSPEEDMONITOR_H
|
||||
#define TORRENTSPEEDMONITOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
#include "qtorrenthandle.h"
|
||||
#include <libtorrent/alert_types.hpp>
|
||||
|
||||
class QBtSession;
|
||||
class SpeedSample;
|
||||
|
||||
class TorrentSpeedMonitor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TorrentSpeedMonitor)
|
||||
|
||||
public:
|
||||
explicit TorrentSpeedMonitor(QBtSession* session);
|
||||
~TorrentSpeedMonitor();
|
||||
qlonglong getETA(const QString &hash, const libtorrent::torrent_status &status) const;
|
||||
|
||||
private slots:
|
||||
void statsReceived(const libtorrent::stats_alert& stats);
|
||||
void removeSamples(const QTorrentHandle& h);
|
||||
|
||||
private:
|
||||
QHash<QString, SpeedSample> m_samples;
|
||||
QBtSession *m_session;
|
||||
};
|
||||
|
||||
#endif // TORRENTSPEEDMONITOR_H
|
||||
100
src/core/qtlibtorrent/torrentstatistics.cpp
Normal file
100
src/core/qtlibtorrent/torrentstatistics.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "torrentstatistics.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include <libtorrent/session.hpp>
|
||||
|
||||
#include "qbtsession.h"
|
||||
#include "qinisettings.h"
|
||||
#include "preferences.h"
|
||||
|
||||
TorrentStatistics::TorrentStatistics(QBtSession* session, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_session(session)
|
||||
, m_sessionUL(0)
|
||||
, m_sessionDL(0)
|
||||
, m_lastWrite(0)
|
||||
, m_dirty(false)
|
||||
{
|
||||
loadStats();
|
||||
connect(&m_timer, SIGNAL(timeout()), this, SLOT(gatherStats()));
|
||||
m_timer.start(60 * 1000);
|
||||
}
|
||||
|
||||
TorrentStatistics::~TorrentStatistics() {
|
||||
if (m_dirty)
|
||||
m_lastWrite = 0;
|
||||
saveStats();
|
||||
}
|
||||
|
||||
quint64 TorrentStatistics::getAlltimeDL() const {
|
||||
return m_alltimeDL + m_sessionDL;
|
||||
}
|
||||
|
||||
quint64 TorrentStatistics::getAlltimeUL() const {
|
||||
return m_alltimeUL + m_sessionUL;
|
||||
}
|
||||
|
||||
void TorrentStatistics::gatherStats() {
|
||||
libtorrent::session_status ss = m_session->getSessionStatus();
|
||||
if (ss.total_download > m_sessionDL) {
|
||||
m_sessionDL = ss.total_download;
|
||||
m_dirty = true;
|
||||
}
|
||||
if (ss.total_upload > m_sessionUL) {
|
||||
m_sessionUL = ss.total_upload;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
saveStats();
|
||||
}
|
||||
|
||||
void TorrentStatistics::saveStats() const {
|
||||
if (!(m_dirty && (QDateTime::currentMSecsSinceEpoch() - m_lastWrite >= 15*60*1000) ))
|
||||
return;
|
||||
QIniSettings s("qBittorrent", "qBittorrent-data");
|
||||
QVariantHash v;
|
||||
v.insert("AlltimeDL", m_alltimeDL + m_sessionDL);
|
||||
v.insert("AlltimeUL", m_alltimeUL + m_sessionUL);
|
||||
s.setValue("Stats/AllStats", v);
|
||||
m_dirty = false;
|
||||
m_lastWrite = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
void TorrentStatistics::loadStats() {
|
||||
// Temp code. Versions v3.1.4 and v3.1.5 saved the data in the qbittorrent.ini file.
|
||||
// This code reads the data from there, writes it to the new file, and removes the keys
|
||||
// from the old file. This code should be removed after some time has passed.
|
||||
// e.g. When we reach v3.3.0
|
||||
// Don't forget to remove:
|
||||
// 1. Preferences::getStats()
|
||||
// 2. Preferences::removeStats()
|
||||
// 3. #include "preferences.h"
|
||||
Preferences* const pref = Preferences::instance();
|
||||
QIniSettings s("qBittorrent", "qBittorrent-data");
|
||||
QVariantHash v = pref->getStats();
|
||||
|
||||
// Let's test if the qbittorrent.ini holds the key
|
||||
if (!v.isEmpty()) {
|
||||
m_dirty = true;
|
||||
|
||||
// If the user has used qbt > 3.1.5 and then reinstalled/used
|
||||
// qbt < 3.1.6, there will be stats in qbittorrent-data.ini too
|
||||
// so we need to merge those 2.
|
||||
if (s.contains("Stats/AllStats")) {
|
||||
QVariantHash tmp = s.value("Stats/AllStats").toHash();
|
||||
v["AlltimeDL"] = v["AlltimeDL"].toULongLong() + tmp["AlltimeDL"].toULongLong();
|
||||
v["AlltimeUL"] = v["AlltimeUL"].toULongLong() + tmp["AlltimeUL"].toULongLong();
|
||||
}
|
||||
}
|
||||
else
|
||||
v = s.value("Stats/AllStats").toHash();
|
||||
|
||||
m_alltimeDL = v["AlltimeDL"].toULongLong();
|
||||
m_alltimeUL = v["AlltimeUL"].toULongLong();
|
||||
|
||||
if (m_dirty) {
|
||||
saveStats();
|
||||
pref->removeStats();
|
||||
}
|
||||
}
|
||||
41
src/core/qtlibtorrent/torrentstatistics.h
Normal file
41
src/core/qtlibtorrent/torrentstatistics.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef TORRENTSTATISTICS_H
|
||||
#define TORRENTSTATISTICS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class QBtSession;
|
||||
|
||||
class TorrentStatistics : QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TorrentStatistics)
|
||||
|
||||
public:
|
||||
TorrentStatistics(QBtSession* session, QObject* parent = 0);
|
||||
~TorrentStatistics();
|
||||
|
||||
quint64 getAlltimeDL() const;
|
||||
quint64 getAlltimeUL() const;
|
||||
|
||||
private slots:
|
||||
void gatherStats();
|
||||
|
||||
private:
|
||||
void saveStats() const;
|
||||
void loadStats();
|
||||
|
||||
private:
|
||||
QBtSession* m_session;
|
||||
// Will overflow at 15.9 EiB
|
||||
quint64 m_alltimeUL;
|
||||
quint64 m_alltimeDL;
|
||||
qint64 m_sessionUL;
|
||||
qint64 m_sessionDL;
|
||||
mutable qint64 m_lastWrite;
|
||||
mutable bool m_dirty;
|
||||
|
||||
QTimer m_timer;
|
||||
};
|
||||
|
||||
#endif // TORRENTSTATISTICS_H
|
||||
59
src/core/qtlibtorrent/trackerinfos.h
Normal file
59
src/core/qtlibtorrent/trackerinfos.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef TRACKERINFOS_H
|
||||
#define TRACKERINFOS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class TrackerInfos {
|
||||
public:
|
||||
QString name_or_url;
|
||||
QString last_message;
|
||||
unsigned long num_peers;
|
||||
|
||||
//TrackerInfos() {}
|
||||
TrackerInfos(const TrackerInfos &b)
|
||||
: name_or_url(b.name_or_url)
|
||||
, last_message(b.last_message)
|
||||
, num_peers(b.num_peers)
|
||||
{
|
||||
Q_ASSERT(!name_or_url.isEmpty());
|
||||
}
|
||||
|
||||
TrackerInfos(QString name_or_url)
|
||||
: name_or_url(name_or_url)
|
||||
, last_message("")
|
||||
, num_peers(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif // TRACKERINFOS_H
|
||||
26
src/core/qtnotify/notifications.cpp
Normal file
26
src/core/qtnotify/notifications.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This file was generated by qdbusxml2cpp version 0.7
|
||||
* Command line was: qdbusxml2cpp -p notifications.h:notifications.cpp notifications.xml
|
||||
*
|
||||
* qdbusxml2cpp is Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
*
|
||||
* This is an auto-generated file.
|
||||
* This file may have been hand-edited. Look for HAND-EDIT comments
|
||||
* before re-generating it.
|
||||
*/
|
||||
|
||||
#include "notifications.h"
|
||||
|
||||
/*
|
||||
* Implementation of interface class OrgFreedesktopNotificationsInterface
|
||||
*/
|
||||
|
||||
OrgFreedesktopNotificationsInterface::OrgFreedesktopNotificationsInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)
|
||||
: QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)
|
||||
{
|
||||
}
|
||||
|
||||
OrgFreedesktopNotificationsInterface::~OrgFreedesktopNotificationsInterface()
|
||||
{
|
||||
}
|
||||
|
||||
84
src/core/qtnotify/notifications.h
Normal file
84
src/core/qtnotify/notifications.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file was generated by qdbusxml2cpp version 0.7
|
||||
* Command line was: qdbusxml2cpp -p notifications.h:notifications.cpp notifications.xml
|
||||
*
|
||||
* qdbusxml2cpp is Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
*
|
||||
* This is an auto-generated file.
|
||||
* Do not edit! All changes made to it will be lost.
|
||||
*/
|
||||
|
||||
#ifndef NOTIFICATIONS_H_1301681398
|
||||
#define NOTIFICATIONS_H_1301681398
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtDBus/QtDBus>
|
||||
|
||||
/*
|
||||
* Proxy class for interface org.freedesktop.Notifications
|
||||
*/
|
||||
class OrgFreedesktopNotificationsInterface: public QDBusAbstractInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static inline const char *staticInterfaceName()
|
||||
{ return "org.freedesktop.Notifications"; }
|
||||
|
||||
public:
|
||||
OrgFreedesktopNotificationsInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0);
|
||||
|
||||
~OrgFreedesktopNotificationsInterface();
|
||||
|
||||
public Q_SLOTS: // METHODS
|
||||
inline QDBusPendingReply<> CloseNotification(uint id)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << qVariantFromValue(id);
|
||||
return asyncCallWithArgumentList(QLatin1String("CloseNotification"), argumentList);
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<QStringList> GetCapabilities()
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
return asyncCallWithArgumentList(QLatin1String("GetCapabilities"), argumentList);
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<QString, QString, QString, QString> GetServerInformation()
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
return asyncCallWithArgumentList(QLatin1String("GetServerInformation"), argumentList);
|
||||
}
|
||||
inline QDBusReply<QString> GetServerInformation(QString &return_vendor, QString &return_version, QString &return_spec_version)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
QDBusMessage reply = callWithArgumentList(QDBus::Block, QLatin1String("GetServerInformation"), argumentList);
|
||||
if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == 4) {
|
||||
return_vendor = qdbus_cast<QString>(reply.arguments().at(1));
|
||||
return_version = qdbus_cast<QString>(reply.arguments().at(2));
|
||||
return_spec_version = qdbus_cast<QString>(reply.arguments().at(3));
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<uint> Notify(const QString &app_name, uint id, const QString &icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << qVariantFromValue(app_name) << qVariantFromValue(id) << qVariantFromValue(icon) << qVariantFromValue(summary) << qVariantFromValue(body) << qVariantFromValue(actions) << qVariantFromValue(hints) << qVariantFromValue(timeout);
|
||||
return asyncCallWithArgumentList(QLatin1String("Notify"), argumentList);
|
||||
}
|
||||
|
||||
Q_SIGNALS: // SIGNALS
|
||||
};
|
||||
|
||||
namespace org {
|
||||
namespace freedesktop {
|
||||
typedef ::OrgFreedesktopNotificationsInterface Notifications;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
31
src/core/qtnotify/notifications.xml
Normal file
31
src/core/qtnotify/notifications.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.freedesktop.Notifications">
|
||||
<method name="GetServerInformation">
|
||||
<arg name="return_name" type="s" direction="out"/>
|
||||
<arg name="return_vendor" type="s" direction="out"/>
|
||||
<arg name="return_version" type="s" direction="out"/>
|
||||
<arg name="return_spec_version" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="GetCapabilities">
|
||||
<arg name="return_caps" type="as" direction="out"/>
|
||||
</method>
|
||||
<method name="CloseNotification">
|
||||
<arg name="id" type="u" direction="in"/>
|
||||
</method>
|
||||
<method name="Notify">
|
||||
<arg name="app_name" type="s" direction="in"/>
|
||||
<arg name="id" type="u" direction="in"/>
|
||||
<arg name="icon" type="s" direction="in"/>
|
||||
<arg name="summary" type="s" direction="in"/>
|
||||
<arg name="body" type="s" direction="in"/>
|
||||
<arg name="actions" type="as" direction="in"/>
|
||||
<arg name="hints" type="a{sv}" direction="in"/>
|
||||
<annotation name="com.trolltech.QtDBus.QtTypeName.In6" value="QVariantMap"/>
|
||||
<arg name="timeout" type="i" direction="in"/>
|
||||
<arg name="return_id" type="u" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
|
||||
5
src/core/qtnotify/qtnotify.pri
Normal file
5
src/core/qtnotify/qtnotify.pri
Normal file
@@ -0,0 +1,5 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += $$PWD/notifications.h
|
||||
|
||||
SOURCES += $$PWD/notifications.cpp
|
||||
198
src/core/scannedfoldersmodel.cpp
Normal file
198
src/core/scannedfoldersmodel.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christian Kandeler, Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "scannedfoldersmodel.h"
|
||||
#include "preferences.h"
|
||||
#include "filesystemwatcher.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
#include <QTemporaryFile>
|
||||
#include "misc.h"
|
||||
|
||||
namespace {
|
||||
const int PathColumn = 0;
|
||||
const int DownloadAtTorrentColumn = 1;
|
||||
}
|
||||
|
||||
class ScanFoldersModel::PathData {
|
||||
public:
|
||||
PathData(const QString &path) : path(path), downloadAtPath(false) {}
|
||||
PathData(const QString &path, bool download_at_path) : path(path), downloadAtPath(download_at_path) {}
|
||||
const QString path;
|
||||
bool downloadAtPath;
|
||||
};
|
||||
|
||||
ScanFoldersModel *ScanFoldersModel::instance(QObject *parent) {
|
||||
//Q_ASSERT(!parent != !m_instance);
|
||||
if (!m_instance)
|
||||
m_instance = new ScanFoldersModel(parent);
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
ScanFoldersModel::ScanFoldersModel(QObject *parent) :
|
||||
QAbstractTableModel(parent), m_fsWatcher(0)
|
||||
{ }
|
||||
|
||||
ScanFoldersModel::~ScanFoldersModel() {
|
||||
qDeleteAll(m_pathList);
|
||||
}
|
||||
|
||||
int ScanFoldersModel::rowCount(const QModelIndex &parent) const {
|
||||
return parent.isValid() ? 0 : m_pathList.count();
|
||||
}
|
||||
|
||||
int ScanFoldersModel::columnCount(const QModelIndex &parent) const {
|
||||
Q_UNUSED(parent);
|
||||
return 2;
|
||||
}
|
||||
|
||||
QVariant ScanFoldersModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid() || index.row() >= rowCount())
|
||||
return QVariant();
|
||||
|
||||
const PathData* pathData = m_pathList.at(index.row());
|
||||
if (index.column() == PathColumn && role == Qt::DisplayRole) {
|
||||
|
||||
return fsutils::toNativePath(pathData->path);
|
||||
}
|
||||
if (index.column() == DownloadAtTorrentColumn && role == Qt::CheckStateRole)
|
||||
return pathData->downloadAtPath ? Qt::Checked : Qt::Unchecked;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant ScanFoldersModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section < 0 || section >= columnCount())
|
||||
return QVariant();
|
||||
|
||||
if (section == PathColumn)
|
||||
return tr("Watched Folder");
|
||||
return tr("Download here");
|
||||
}
|
||||
|
||||
Qt::ItemFlags ScanFoldersModel::flags(const QModelIndex &index) const {
|
||||
if (!index.isValid() || index.row() >= rowCount() || index.column() != DownloadAtTorrentColumn)
|
||||
return QAbstractTableModel::flags(index);
|
||||
return QAbstractTableModel::flags(index) | Qt::ItemIsUserCheckable;
|
||||
}
|
||||
|
||||
bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value, int role) {
|
||||
if (!index.isValid() || index.row() >= rowCount() || index.column() > DownloadAtTorrentColumn || role != Qt::CheckStateRole)
|
||||
return false;
|
||||
Q_ASSERT(index.column() == DownloadAtTorrentColumn);
|
||||
m_pathList[index.row()]->downloadAtPath = (value.toInt() == Qt::Checked);
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &path, bool download_at_path) {
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
return DoesNotExist;
|
||||
if (!dir.isReadable())
|
||||
return CannotRead;
|
||||
const QString &canonicalPath = dir.canonicalPath();
|
||||
if (findPathData(canonicalPath) != -1)
|
||||
return AlreadyInList;
|
||||
if (!m_fsWatcher) {
|
||||
m_fsWatcher = new FileSystemWatcher(this);
|
||||
connect(m_fsWatcher, SIGNAL(torrentsAdded(QStringList&)), this, SIGNAL(torrentsAdded(QStringList&)));
|
||||
}
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_pathList << new PathData(canonicalPath, download_at_path);
|
||||
endInsertRows();
|
||||
// Start scanning
|
||||
m_fsWatcher->addPath(canonicalPath);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
void ScanFoldersModel::removePath(int row) {
|
||||
Q_ASSERT(row >= 0 && row < rowCount());
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_fsWatcher->removePath(m_pathList.at(row)->path);
|
||||
m_pathList.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
bool ScanFoldersModel::removePath(const QString &path) {
|
||||
const int row = findPathData(path);
|
||||
if (row == -1)
|
||||
return false;
|
||||
removePath(row);
|
||||
return true;
|
||||
}
|
||||
|
||||
ScanFoldersModel::PathStatus ScanFoldersModel::setDownloadAtPath(int row, bool downloadAtPath) {
|
||||
Q_ASSERT(row >= 0 && row < rowCount());
|
||||
|
||||
bool &oldValue = m_pathList[row]->downloadAtPath;
|
||||
if (oldValue != downloadAtPath) {
|
||||
if (downloadAtPath) {
|
||||
QTemporaryFile testFile(m_pathList[row]->path + "/tmpFile");
|
||||
if (!testFile.open())
|
||||
return CannotWrite;
|
||||
}
|
||||
oldValue = downloadAtPath;
|
||||
const QModelIndex changedIndex = index(row, DownloadAtTorrentColumn);
|
||||
emit dataChanged(changedIndex, changedIndex);
|
||||
}
|
||||
return Ok;
|
||||
}
|
||||
|
||||
bool ScanFoldersModel::downloadInTorrentFolder(const QString &filePath) const {
|
||||
const int row = findPathData(QFileInfo(filePath).dir().path());
|
||||
Q_ASSERT(row != -1);
|
||||
return m_pathList.at(row)->downloadAtPath;
|
||||
}
|
||||
|
||||
int ScanFoldersModel::findPathData(const QString &path) const {
|
||||
for (int i = 0; i < m_pathList.count(); ++i) {
|
||||
const PathData* pathData = m_pathList.at(i);
|
||||
if (pathData->path == fsutils::fromNativePath(path))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ScanFoldersModel::makePersistent() {
|
||||
Preferences* const pref = Preferences::instance();
|
||||
QStringList paths;
|
||||
QList<bool> downloadInFolderInfo;
|
||||
foreach (const PathData* pathData, m_pathList) {
|
||||
paths << pathData->path;
|
||||
downloadInFolderInfo << pathData->downloadAtPath;
|
||||
}
|
||||
pref->setScanDirs(paths);
|
||||
pref->setDownloadInScanDirs(downloadInFolderInfo);
|
||||
}
|
||||
|
||||
ScanFoldersModel *ScanFoldersModel::m_instance = 0;
|
||||
80
src/core/scannedfoldersmodel.h
Normal file
80
src/core/scannedfoldersmodel.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christian Kandeler, Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef SCANNEDFOLDERSMODEL_H
|
||||
#define SCANNEDFOLDERSMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QList>
|
||||
#include <QStringList>
|
||||
|
||||
class FileSystemWatcher;
|
||||
|
||||
class ScanFoldersModel : public QAbstractTableModel {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ScanFoldersModel)
|
||||
|
||||
public:
|
||||
enum PathStatus { Ok, DoesNotExist, CannotRead, CannotWrite, AlreadyInList };
|
||||
static ScanFoldersModel *instance(QObject *parent = 0);
|
||||
virtual ~ScanFoldersModel();
|
||||
|
||||
virtual int rowCount(const QModelIndex & parent = QModelIndex()) const;
|
||||
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
|
||||
// TODO: removePaths(); singular version becomes private helper functions;
|
||||
// also: remove functions should take modelindexes
|
||||
PathStatus addPath(const QString &path, bool download_at_path);
|
||||
void removePath(int row);
|
||||
bool removePath(const QString &path);
|
||||
PathStatus setDownloadAtPath(int row, bool downloadAtPath);
|
||||
|
||||
bool downloadInTorrentFolder(const QString &filePath) const;
|
||||
void makePersistent();
|
||||
|
||||
signals:
|
||||
// The absolute paths of new torrent files in the scanned directories.
|
||||
void torrentsAdded(QStringList &pathList);
|
||||
|
||||
private:
|
||||
explicit ScanFoldersModel(QObject *parent);
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
|
||||
static ScanFoldersModel *m_instance;
|
||||
class PathData;
|
||||
int findPathData(const QString &path) const;
|
||||
|
||||
QList<PathData*> m_pathList;
|
||||
FileSystemWatcher *m_fsWatcher;
|
||||
};
|
||||
|
||||
#endif // SCANNEDFOLDERSMODEL_H
|
||||
480
src/core/smtp.cpp
Normal file
480
src/core/smtp.cpp
Normal file
@@ -0,0 +1,480 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on QxtSmtp from libqxt (http://libqxt.org)
|
||||
*/
|
||||
|
||||
#include "smtp.h"
|
||||
#include "preferences.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QTextStream>
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslSocket>
|
||||
#else
|
||||
#include <QTcpSocket>
|
||||
#endif
|
||||
#include <QTextCodec>
|
||||
#include <QDebug>
|
||||
#include <QHostAddress>
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QCryptographicHash>
|
||||
#include <QStringList>
|
||||
|
||||
namespace {
|
||||
const short DEFAULT_PORT = 25;
|
||||
const short DEFAULT_PORT_SSL = 465;
|
||||
|
||||
QByteArray hmacMD5(QByteArray key, const QByteArray &msg)
|
||||
{
|
||||
const int blockSize = 64; // HMAC-MD5 block size
|
||||
if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with MD5 compression
|
||||
key = QCryptographicHash::hash(key, QCryptographicHash::Md5);
|
||||
}
|
||||
|
||||
QByteArray innerPadding(blockSize, char(0x36)); // initialize inner padding with char "6"
|
||||
QByteArray outerPadding(blockSize, char(0x5c)); // initialize outer padding with char "\"
|
||||
// ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
|
||||
// Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)
|
||||
|
||||
for (int i = 0; i < key.length(); i++) {
|
||||
innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
|
||||
outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
|
||||
}
|
||||
|
||||
// result = hash ( outerPadding CONCAT hash ( innerPadding CONCAT baseString ) ).toBase64
|
||||
QByteArray total = outerPadding;
|
||||
QByteArray part = innerPadding;
|
||||
part.append(msg);
|
||||
total.append(QCryptographicHash::hash(part, QCryptographicHash::Md5));
|
||||
return QCryptographicHash::hash(total, QCryptographicHash::Md5);
|
||||
}
|
||||
|
||||
QByteArray determineFQDN()
|
||||
{
|
||||
QString hostname = QHostInfo::localHostName();
|
||||
if (hostname.isEmpty())
|
||||
hostname = "localhost";
|
||||
|
||||
return hostname.toLocal8Bit();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Smtp::Smtp(QObject *parent): QObject(parent),
|
||||
state(Init), use_ssl(false) {
|
||||
#ifndef QT_NO_OPENSSL
|
||||
socket = new QSslSocket(this);
|
||||
#else
|
||||
socket = new QTcpSocket(this);
|
||||
#endif
|
||||
|
||||
connect(socket, SIGNAL(readyRead()), SLOT(readyRead()));
|
||||
connect(socket, SIGNAL(disconnected()), SLOT(deleteLater()));
|
||||
|
||||
// Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html)
|
||||
Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex()
|
||||
== "750c783e6ab0b503eaa86e310a5db738");
|
||||
Q_ASSERT(hmacMD5(QByteArray::fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
"Hi There").toHex()
|
||||
== "9294727a3638bb1c13f48ef8158bfc9d");
|
||||
}
|
||||
|
||||
Smtp::~Smtp() {
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
}
|
||||
|
||||
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body) {
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
QTextCodec* latin1 = QTextCodec::codecForName("latin1");
|
||||
message = "";
|
||||
message += encode_mime_header("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1);
|
||||
message += encode_mime_header("From", from, latin1);
|
||||
message += encode_mime_header("Subject", subject, latin1);
|
||||
message += encode_mime_header("To", to, latin1);
|
||||
message += "MIME-Version: 1.0\r\n";
|
||||
message += "Content-Type: text/plain; charset=UTF-8\r\n";
|
||||
message += "Content-Transfer-Encoding: base64\r\n";
|
||||
message += "\r\n";
|
||||
// Encode the body in base64
|
||||
QString crlf_body = body;
|
||||
QByteArray b = crlf_body.replace("\n","\r\n").toUtf8().toBase64();
|
||||
int ct = b.length();
|
||||
for (int i = 0; i < ct; i += 78)
|
||||
{
|
||||
message += b.mid(i, 78);
|
||||
}
|
||||
this->from = from;
|
||||
rcpt = to;
|
||||
// Authentication
|
||||
if (pref->getMailNotificationSMTPAuth()) {
|
||||
username = pref->getMailNotificationSMTPUsername();
|
||||
password = pref->getMailNotificationSMTPPassword();
|
||||
}
|
||||
|
||||
// Connect to SMTP server
|
||||
#ifndef QT_NO_OPENSSL
|
||||
if (pref->getMailNotificationSMTPSSL()) {
|
||||
socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL);
|
||||
use_ssl = true;
|
||||
} else {
|
||||
#endif
|
||||
socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
|
||||
use_ssl = false;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Smtp::readyRead()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
// SMTP is line-oriented
|
||||
buffer += socket->readAll();
|
||||
while (true)
|
||||
{
|
||||
int pos = buffer.indexOf("\r\n");
|
||||
if (pos < 0) return; // Loop exit condition
|
||||
QByteArray line = buffer.left(pos);
|
||||
buffer = buffer.mid(pos + 2);
|
||||
qDebug() << "Response line:" << line;
|
||||
// Extract reponse code
|
||||
QByteArray code = line.left(3);
|
||||
|
||||
switch(state) {
|
||||
case Init: {
|
||||
if (code[0] == '2') {
|
||||
// The server may send a multiline greeting/INIT/220 response.
|
||||
// We wait until it finishes.
|
||||
if (line[3] != ' ')
|
||||
break;
|
||||
// Connection was successful
|
||||
ehlo();
|
||||
} else {
|
||||
logError("Connection failed, unrecognized reply: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EhloSent:
|
||||
case HeloSent:
|
||||
case EhloGreetReceived:
|
||||
parseEhloResponse(code, line[3] != ' ', line.mid(4));
|
||||
break;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
case StartTLSSent:
|
||||
if (code == "220") {
|
||||
socket->startClientEncryption();
|
||||
ehlo();
|
||||
} else {
|
||||
authenticate();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case AuthRequestSent:
|
||||
case AuthUsernameSent:
|
||||
if (authType == AuthPlain) authPlain();
|
||||
else if (authType == AuthLogin) authLogin();
|
||||
else authCramMD5(line.mid(4));
|
||||
break;
|
||||
case AuthSent:
|
||||
case Authenticated:
|
||||
if (code[0] == '2') {
|
||||
qDebug() << "Sending <mail from>...";
|
||||
socket->write("mail from:<" + from.toLatin1() + ">\r\n");
|
||||
socket->flush();
|
||||
state = Rcpt;
|
||||
} else {
|
||||
// Authentication failed!
|
||||
logError("Authentication failed, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
case Rcpt:
|
||||
if (code[0] == '2') {
|
||||
socket->write("rcpt to:<" + rcpt.toLatin1() + ">\r\n");
|
||||
socket->flush();
|
||||
state = Data;
|
||||
} else {
|
||||
logError("<mail from> was rejected by server, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
case Data:
|
||||
if (code[0] == '2') {
|
||||
socket->write("data\r\n");
|
||||
socket->flush();
|
||||
state = Body;
|
||||
} else {
|
||||
logError("<Rcpt to> was rejected by server, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
case Body:
|
||||
if (code[0] == '3') {
|
||||
socket->write(message + "\r\n.\r\n");
|
||||
socket->flush();
|
||||
state = Quit;
|
||||
} else {
|
||||
logError("<data> was rejected by server, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
case Quit:
|
||||
if (code[0] == '2') {
|
||||
socket->write("QUIT\r\n");
|
||||
socket->flush();
|
||||
// here, we just close.
|
||||
state = Close;
|
||||
} else {
|
||||
logError("Message was rejected by the server, error: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
qDebug() << "Disconnecting from host";
|
||||
socket->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Smtp::encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix)
|
||||
{
|
||||
QByteArray rv = "";
|
||||
QByteArray line = key.toLatin1() + ": ";
|
||||
if (!prefix.isEmpty()) line += prefix;
|
||||
if (!value.contains("=?") && latin1->canEncode(value)) {
|
||||
bool firstWord = true;
|
||||
foreach (const QByteArray& word, value.toLatin1().split(' ')) {
|
||||
if (line.size() > 78) {
|
||||
rv = rv + line + "\r\n";
|
||||
line.clear();
|
||||
}
|
||||
if (firstWord)
|
||||
line += word;
|
||||
else
|
||||
line += " " + word;
|
||||
firstWord = false;
|
||||
}
|
||||
} else {
|
||||
// The text cannot be losslessly encoded as Latin-1. Therefore, we
|
||||
// must use base64 encoding.
|
||||
QByteArray utf8 = value.toUtf8();
|
||||
// Use base64 encoding
|
||||
QByteArray base64 = utf8.toBase64();
|
||||
int ct = base64.length();
|
||||
line += "=?utf-8?b?";
|
||||
for (int i = 0; i < ct; i += 4) {
|
||||
/*if (line.length() > 72) {
|
||||
rv += line + "?\n\r";
|
||||
line = " =?utf-8?b?";
|
||||
}*/
|
||||
line = line + base64.mid(i, 4);
|
||||
}
|
||||
line += "?="; // end encoded-word atom
|
||||
}
|
||||
return rv + line + "\r\n";
|
||||
}
|
||||
|
||||
void Smtp::ehlo()
|
||||
{
|
||||
QByteArray address = determineFQDN();
|
||||
socket->write("ehlo " + address + "\r\n");
|
||||
socket->flush();
|
||||
state = EhloSent;
|
||||
}
|
||||
|
||||
void Smtp::helo()
|
||||
{
|
||||
QByteArray address = determineFQDN();
|
||||
socket->write("helo " + address + "\r\n");
|
||||
socket->flush();
|
||||
state = HeloSent;
|
||||
}
|
||||
|
||||
void Smtp::parseEhloResponse(const QByteArray& code, bool continued, const QString& line)
|
||||
{
|
||||
if (code != "250") {
|
||||
// Error
|
||||
if (state == EhloSent) {
|
||||
// try to send HELO instead of EHLO
|
||||
qDebug() << "EHLO failed, trying HELO instead...";
|
||||
helo();
|
||||
} else {
|
||||
// Both EHLO and HELO failed, chances are this is NOT
|
||||
// a SMTP server
|
||||
logError("Both EHLO and HELO failed, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state != EhloGreetReceived) {
|
||||
if (!continued) {
|
||||
// greeting only, no extensions
|
||||
qDebug() << "No extension";
|
||||
state = EhloDone;
|
||||
} else {
|
||||
// greeting followed by extensions
|
||||
state = EhloGreetReceived;
|
||||
qDebug () << "EHLO greet received";
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qDebug() << Q_FUNC_INFO << "Supported extension: " << line.section(' ', 0, 0).toUpper()
|
||||
<< line.section(' ', 1);
|
||||
extensions[line.section(' ', 0, 0).toUpper()] = line.section(' ', 1);
|
||||
if (!continued)
|
||||
state = EhloDone;
|
||||
}
|
||||
if (state != EhloDone) return;
|
||||
if (extensions.contains("STARTTLS") && use_ssl) {
|
||||
qDebug() << "STARTTLS";
|
||||
startTLS();
|
||||
} else {
|
||||
authenticate();
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::authenticate()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
if (!extensions.contains("AUTH") ||
|
||||
username.isEmpty() || password.isEmpty()) {
|
||||
// Skip authentication
|
||||
qDebug() << "Skipping authentication...";
|
||||
state = Authenticated;
|
||||
// At this point the server will not send any response
|
||||
// So fill the buffer with a fake one to pass the tests
|
||||
// in readyRead()
|
||||
buffer.push_front("250 QBT FAKE RESPONSE\r\n");
|
||||
return;
|
||||
}
|
||||
// AUTH extension is supported, check which
|
||||
// authentication modes are supported by
|
||||
// the server
|
||||
QStringList auth = extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts);
|
||||
if (auth.contains("CRAM-MD5")) {
|
||||
qDebug() << "Using CRAM-MD5 authentication...";
|
||||
authCramMD5();
|
||||
}
|
||||
else if (auth.contains("PLAIN")) {
|
||||
qDebug() << "Using PLAIN authentication...";
|
||||
authPlain();
|
||||
}
|
||||
else if (auth.contains("LOGIN")) {
|
||||
qDebug() << "Using LOGIN authentication...";
|
||||
authLogin();
|
||||
} else {
|
||||
// Skip authentication
|
||||
logError("The SMTP server does not seem to support any of the authentications modes "
|
||||
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
|
||||
"knowing it is likely to fail... Server Auth Modes: "+auth.join("|"));
|
||||
state = Authenticated;
|
||||
// At this point the server will not send any response
|
||||
// So fill the buffer with a fake one to pass the tests
|
||||
// in readyRead()
|
||||
buffer.push_front("250 QBT FAKE RESPONSE\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::startTLS()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
socket->write("starttls\r\n");
|
||||
socket->flush();
|
||||
state = StartTLSSent;
|
||||
#else
|
||||
authenticate();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Smtp::authCramMD5(const QByteArray& challenge)
|
||||
{
|
||||
if (state != AuthRequestSent) {
|
||||
socket->write("auth cram-md5\r\n");
|
||||
socket->flush();
|
||||
authType = AuthCramMD5;
|
||||
state = AuthRequestSent;
|
||||
} else {
|
||||
QByteArray response = username.toLatin1() + ' '
|
||||
+ hmacMD5(password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
|
||||
socket->write(response.toBase64() + "\r\n");
|
||||
socket->flush();
|
||||
state = AuthSent;
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::authPlain()
|
||||
{
|
||||
if (state != AuthRequestSent) {
|
||||
authType = AuthPlain;
|
||||
// Prepare Auth string
|
||||
QByteArray auth;
|
||||
auth += '\0';
|
||||
auth += username.toLatin1();
|
||||
qDebug() << "username: " << username.toLatin1();
|
||||
auth += '\0';
|
||||
auth += password.toLatin1();
|
||||
qDebug() << "password: " << password.toLatin1();
|
||||
// Send it
|
||||
socket->write("auth plain "+ auth.toBase64() + "\r\n");
|
||||
socket->flush();
|
||||
state = AuthSent;
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::authLogin()
|
||||
{
|
||||
if (state != AuthRequestSent && state != AuthUsernameSent) {
|
||||
socket->write("auth login\r\n");
|
||||
socket->flush();
|
||||
authType = AuthLogin;
|
||||
state = AuthRequestSent;
|
||||
}
|
||||
else if (state == AuthRequestSent) {
|
||||
socket->write(username.toLatin1().toBase64() + "\r\n");
|
||||
socket->flush();
|
||||
state = AuthUsernameSent;
|
||||
}
|
||||
else {
|
||||
socket->write(password.toLatin1().toBase64() + "\r\n");
|
||||
socket->flush();
|
||||
state = AuthSent;
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::logError(const QString &msg)
|
||||
{
|
||||
qDebug() << "Email Notification Error:" << msg;
|
||||
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
|
||||
}
|
||||
99
src/core/smtp.h
Normal file
99
src/core/smtp.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on QxtSmtp from libqxt (http://libqxt.org)
|
||||
*/
|
||||
|
||||
#ifndef SMTP_H
|
||||
#define SMTP_H
|
||||
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextStream;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
class QSslSocket;
|
||||
#else
|
||||
class QTcpSocket;
|
||||
#endif
|
||||
class QTextCodec;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Smtp : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Smtp(QObject *parent = 0);
|
||||
~Smtp();
|
||||
void sendMail(const QString &from, const QString &to, const QString &subject, const QString &body);
|
||||
|
||||
private slots:
|
||||
void readyRead();
|
||||
|
||||
private:
|
||||
QByteArray encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix=QByteArray());
|
||||
void ehlo();
|
||||
void helo();
|
||||
void parseEhloResponse(const QByteArray& code, bool continued, const QString& line);
|
||||
void authenticate();
|
||||
void startTLS();
|
||||
void authCramMD5(const QByteArray& challenge = QByteArray());
|
||||
void authPlain();
|
||||
void authLogin();
|
||||
void logError(const QString &msg);
|
||||
|
||||
private:
|
||||
enum states { Rcpt, EhloSent, HeloSent, EhloDone, EhloGreetReceived, AuthRequestSent, AuthSent,
|
||||
AuthUsernameSent, Authenticated, StartTLSSent, Data, Init, Body, Quit, Close };
|
||||
enum AuthType { AuthPlain, AuthLogin, AuthCramMD5 };
|
||||
|
||||
private:
|
||||
QByteArray message;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
QSslSocket *socket;
|
||||
#else
|
||||
QTcpSocket *socket;
|
||||
#endif
|
||||
QString from;
|
||||
QString rcpt;
|
||||
QString response;
|
||||
int state;
|
||||
QHash<QString, QString> extensions;
|
||||
QByteArray buffer;
|
||||
bool use_ssl;
|
||||
AuthType authType;
|
||||
QString username;
|
||||
QString password;
|
||||
};
|
||||
#endif
|
||||
527
src/core/torrentpersistentdata.cpp
Normal file
527
src/core/torrentpersistentdata.cpp
Normal file
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "torrentpersistentdata.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QVariant>
|
||||
|
||||
#include "qinisettings.h"
|
||||
#include "misc.h"
|
||||
#include "qtorrenthandle.h"
|
||||
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
|
||||
QHash<QString, TorrentTempData::TorrentData> TorrentTempData::data = QHash<QString, TorrentTempData::TorrentData>();
|
||||
QHash<QString, TorrentTempData::TorrentMoveState> TorrentTempData::torrentMoveStates = QHash<QString, TorrentTempData::TorrentMoveState>();
|
||||
QHash<QString, bool> HiddenData::data = QHash<QString, bool>();
|
||||
unsigned int HiddenData::metadata_counter = 0;
|
||||
TorrentPersistentData* TorrentPersistentData::m_instance = 0;
|
||||
|
||||
TorrentTempData::TorrentData::TorrentData()
|
||||
: sequential(false)
|
||||
, seed(false)
|
||||
, add_paused(Preferences::instance()->addTorrentsInPause())
|
||||
{
|
||||
}
|
||||
|
||||
TorrentTempData::TorrentMoveState::TorrentMoveState(QString oldPath, QString newPath)
|
||||
: oldPath(oldPath)
|
||||
, newPath(newPath)
|
||||
{
|
||||
}
|
||||
|
||||
bool TorrentTempData::hasTempData(const QString &hash)
|
||||
{
|
||||
return data.contains(hash);
|
||||
}
|
||||
|
||||
void TorrentTempData::deleteTempData(const QString &hash)
|
||||
{
|
||||
data.remove(hash);
|
||||
}
|
||||
|
||||
void TorrentTempData::setFilesPriority(const QString &hash, const std::vector<int> &pp)
|
||||
{
|
||||
data[hash].files_priority = pp;
|
||||
}
|
||||
|
||||
void TorrentTempData::setFilesPath(const QString &hash, const QStringList &path_list)
|
||||
{
|
||||
data[hash].path_list = path_list;
|
||||
}
|
||||
|
||||
void TorrentTempData::setSavePath(const QString &hash, const QString &save_path)
|
||||
{
|
||||
data[hash].save_path = save_path;
|
||||
}
|
||||
|
||||
void TorrentTempData::setLabel(const QString &hash, const QString &label)
|
||||
{
|
||||
data[hash].label = label;
|
||||
}
|
||||
|
||||
void TorrentTempData::setSequential(const QString &hash, const bool &sequential)
|
||||
{
|
||||
data[hash].sequential = sequential;
|
||||
}
|
||||
|
||||
bool TorrentTempData::isSequential(const QString &hash)
|
||||
{
|
||||
return data.value(hash).sequential;
|
||||
}
|
||||
|
||||
void TorrentTempData::setSeedingMode(const QString &hash, const bool &seed)
|
||||
{
|
||||
data[hash].seed = seed;
|
||||
}
|
||||
|
||||
bool TorrentTempData::isSeedingMode(const QString &hash)
|
||||
{
|
||||
return data.value(hash).seed;
|
||||
}
|
||||
|
||||
QString TorrentTempData::getSavePath(const QString &hash)
|
||||
{
|
||||
return data.value(hash).save_path;
|
||||
}
|
||||
|
||||
QStringList TorrentTempData::getFilesPath(const QString &hash)
|
||||
{
|
||||
return data.value(hash).path_list;
|
||||
}
|
||||
|
||||
QString TorrentTempData::getLabel(const QString &hash)
|
||||
{
|
||||
return data.value(hash).label;
|
||||
}
|
||||
|
||||
void TorrentTempData::getFilesPriority(const QString &hash, std::vector<int> &fp)
|
||||
{
|
||||
fp = data.value(hash).files_priority;
|
||||
}
|
||||
|
||||
bool TorrentTempData::isMoveInProgress(const QString &hash)
|
||||
{
|
||||
return torrentMoveStates.find(hash) != torrentMoveStates.end();
|
||||
}
|
||||
|
||||
void TorrentTempData::enqueueMove(const QString &hash, const QString &queuedPath)
|
||||
{
|
||||
QHash<QString, TorrentMoveState>::iterator i = torrentMoveStates.find(hash);
|
||||
if (i == torrentMoveStates.end()) {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
i->queuedPath = queuedPath;
|
||||
}
|
||||
|
||||
void TorrentTempData::startMove(const QString &hash, const QString &oldPath, const QString& newPath)
|
||||
{
|
||||
QHash<QString, TorrentMoveState>::iterator i = torrentMoveStates.find(hash);
|
||||
if (i != torrentMoveStates.end()) {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
torrentMoveStates.insert(hash, TorrentMoveState(oldPath, newPath));
|
||||
}
|
||||
|
||||
void TorrentTempData::finishMove(const QString &hash)
|
||||
{
|
||||
QHash<QString, TorrentMoveState>::iterator i = torrentMoveStates.find(hash);
|
||||
if (i == torrentMoveStates.end()) {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
torrentMoveStates.erase(i);
|
||||
}
|
||||
|
||||
QString TorrentTempData::getOldPath(const QString &hash)
|
||||
{
|
||||
QHash<QString, TorrentMoveState>::iterator i = torrentMoveStates.find(hash);
|
||||
if (i == torrentMoveStates.end()) {
|
||||
Q_ASSERT(false);
|
||||
return QString();
|
||||
}
|
||||
return i->oldPath;
|
||||
}
|
||||
|
||||
QString TorrentTempData::getNewPath(const QString &hash)
|
||||
{
|
||||
QHash<QString, TorrentMoveState>::iterator i = torrentMoveStates.find(hash);
|
||||
if (i == torrentMoveStates.end()) {
|
||||
Q_ASSERT(false);
|
||||
return QString();
|
||||
}
|
||||
return i->newPath;
|
||||
}
|
||||
|
||||
QString TorrentTempData::getQueuedPath(const QString &hash)
|
||||
{
|
||||
QHash<QString, TorrentMoveState>::iterator i = torrentMoveStates.find(hash);
|
||||
if (i == torrentMoveStates.end()) {
|
||||
Q_ASSERT(false);
|
||||
return QString();
|
||||
}
|
||||
return i->queuedPath;
|
||||
}
|
||||
|
||||
void TorrentTempData::setAddPaused(const QString &hash, const bool &paused)
|
||||
{
|
||||
data[hash].add_paused = paused;
|
||||
}
|
||||
|
||||
bool TorrentTempData::isAddPaused(const QString &hash)
|
||||
{
|
||||
return data.value(hash).add_paused;
|
||||
}
|
||||
|
||||
void HiddenData::addData(const QString &hash)
|
||||
{
|
||||
data[hash] = false;
|
||||
}
|
||||
|
||||
bool HiddenData::hasData(const QString &hash)
|
||||
{
|
||||
return data.contains(hash);
|
||||
}
|
||||
|
||||
void HiddenData::deleteData(const QString &hash)
|
||||
{
|
||||
if (data.value(hash, false))
|
||||
metadata_counter--;
|
||||
data.remove(hash);
|
||||
}
|
||||
|
||||
int HiddenData::getSize()
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
|
||||
int HiddenData::getDownloadingSize()
|
||||
{
|
||||
return data.size() - metadata_counter;
|
||||
}
|
||||
|
||||
void HiddenData::gotMetadata(const QString &hash)
|
||||
{
|
||||
if (!data.contains(hash))
|
||||
return;
|
||||
data[hash] = true;
|
||||
metadata_counter++;
|
||||
}
|
||||
|
||||
TorrentPersistentData::TorrentPersistentData()
|
||||
: m_data(QIniSettings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent-resume")).value("torrents").toHash())
|
||||
, dirty(false)
|
||||
{
|
||||
timer.setSingleShot(true);
|
||||
timer.setInterval(5 * 1000);
|
||||
connect(&timer, SIGNAL(timeout()), SLOT(save()));
|
||||
}
|
||||
|
||||
TorrentPersistentData::~TorrentPersistentData()
|
||||
{
|
||||
save();
|
||||
}
|
||||
|
||||
void TorrentPersistentData::save()
|
||||
{
|
||||
if (!dirty)
|
||||
return;
|
||||
|
||||
QIniSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent-resume"));
|
||||
settings.setValue("torrents", m_data);
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
const QVariant TorrentPersistentData::value(const QString &key, const QVariant &defaultValue) const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
return m_data.value(key, defaultValue);
|
||||
}
|
||||
|
||||
void TorrentPersistentData::setValue(const QString &key, const QVariant &value)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
if (m_data.value(key) == value)
|
||||
return;
|
||||
dirty = true;
|
||||
timer.start();
|
||||
m_data.insert(key, value);
|
||||
}
|
||||
|
||||
TorrentPersistentData* TorrentPersistentData::instance()
|
||||
{
|
||||
if (!m_instance)
|
||||
m_instance = new TorrentPersistentData;
|
||||
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
void TorrentPersistentData::drop()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool TorrentPersistentData::isKnownTorrent(QString hash) const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
return m_data.contains(hash);
|
||||
}
|
||||
|
||||
QStringList TorrentPersistentData::knownTorrents() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
return m_data.keys();
|
||||
}
|
||||
|
||||
void TorrentPersistentData::setRatioLimit(const QString &hash, const qreal &ratio)
|
||||
{
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["max_ratio"] = ratio;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
|
||||
qreal TorrentPersistentData::getRatioLimit(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("max_ratio", USE_GLOBAL_RATIO).toReal();
|
||||
}
|
||||
|
||||
bool TorrentPersistentData::hasPerTorrentRatioLimit() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
QHash<QString, QVariant>::ConstIterator it = m_data.constBegin();
|
||||
QHash<QString, QVariant>::ConstIterator itend = m_data.constEnd();
|
||||
for (; it != itend; ++it)
|
||||
if (it.value().toHash().value("max_ratio", USE_GLOBAL_RATIO).toReal() >= 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void TorrentPersistentData::setAddedDate(const QString &hash, const QDateTime &time)
|
||||
{
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
if (!torrent.contains("add_date")) {
|
||||
torrent["add_date"] = time;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
}
|
||||
|
||||
QDateTime TorrentPersistentData::getAddedDate(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
QDateTime dt = torrent.value("add_date").toDateTime();
|
||||
if (!dt.isValid())
|
||||
dt = QDateTime::currentDateTime();
|
||||
return dt;
|
||||
}
|
||||
|
||||
void TorrentPersistentData::setErrorState(const QString &hash, const bool has_error)
|
||||
{
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["has_error"] = has_error;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
|
||||
bool TorrentPersistentData::hasError(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("has_error", false).toBool();
|
||||
}
|
||||
|
||||
QDateTime TorrentPersistentData::getSeedDate(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("seed_date").toDateTime();
|
||||
}
|
||||
|
||||
void TorrentPersistentData::deletePersistentData(const QString &hash)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
if (m_data.contains(hash)) {
|
||||
m_data.remove(hash);
|
||||
dirty = true;
|
||||
timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentPersistentData::saveTorrentPersistentData(const QTorrentHandle &h, const QString &save_path, const bool is_magnet)
|
||||
{
|
||||
Q_ASSERT(h.is_valid());
|
||||
QString hash = h.hash();
|
||||
qDebug("Saving persistent data for %s", qPrintable(hash));
|
||||
// Save persistent data
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["is_magnet"] = is_magnet;
|
||||
if (is_magnet)
|
||||
torrent["magnet_uri"] = misc::toQString(make_magnet_uri(h));
|
||||
torrent["seed"] = h.is_seed();
|
||||
torrent["priority"] = h.queue_position();
|
||||
if (save_path.isEmpty()) {
|
||||
qDebug("TorrentPersistantData: save path is %s", qPrintable(h.save_path()));
|
||||
torrent["save_path"] = h.save_path();
|
||||
}
|
||||
else {
|
||||
qDebug("TorrentPersistantData: overriding save path is %s", qPrintable(save_path));
|
||||
torrent["save_path"] = save_path; // Override torrent save path (e.g. because it is a temp dir)
|
||||
}
|
||||
// Label
|
||||
torrent["label"] = TorrentTempData::getLabel(hash);
|
||||
// Save data
|
||||
setValue(hash, torrent);
|
||||
qDebug("TorrentPersistentData: Saving save_path %s, hash: %s", qPrintable(h.save_path()), qPrintable(hash));
|
||||
// Set Added date
|
||||
setAddedDate(hash, QDateTime::currentDateTime());
|
||||
// Finally, remove temp data
|
||||
TorrentTempData::deleteTempData(hash);
|
||||
}
|
||||
|
||||
void TorrentPersistentData::saveSavePath(const QString &hash, const QString &save_path)
|
||||
{
|
||||
Q_ASSERT(!hash.isEmpty());
|
||||
qDebug("TorrentPersistentData::saveSavePath(%s)", qPrintable(save_path));
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["save_path"] = save_path;
|
||||
setValue(hash, torrent);
|
||||
qDebug("TorrentPersistentData: Saving save_path: %s, hash: %s", qPrintable(save_path), qPrintable(hash));
|
||||
}
|
||||
|
||||
void TorrentPersistentData::saveLabel(const QString &hash, const QString &label)
|
||||
{
|
||||
Q_ASSERT(!hash.isEmpty());
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["label"] = label;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
|
||||
void TorrentPersistentData::saveName(const QString &hash, const QString &name)
|
||||
{
|
||||
Q_ASSERT(!hash.isEmpty());
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["name"] = name;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
|
||||
void TorrentPersistentData::savePriority(const QTorrentHandle &h)
|
||||
{
|
||||
QString hash = h.hash();
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["priority"] = h.queue_position();
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
|
||||
void TorrentPersistentData::savePriority(const QString &hash, const int &queue_pos)
|
||||
{
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["priority"] = queue_pos;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
|
||||
void TorrentPersistentData::saveSeedStatus(const QTorrentHandle &h)
|
||||
{
|
||||
QString hash = h.hash();
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
bool was_seed = torrent.value("seed", false).toBool();
|
||||
if (was_seed != h.is_seed()) {
|
||||
torrent["seed"] = !was_seed;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentPersistentData::saveSeedStatus(const QString &hash, const bool seedStatus)
|
||||
{
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["seed"] = seedStatus;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
|
||||
void TorrentPersistentData::setHasMissingFiles(const QString& hash, bool missing)
|
||||
{
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
torrent["has_missing_files"] = missing;
|
||||
setValue(hash, torrent);
|
||||
}
|
||||
|
||||
QString TorrentPersistentData::getSavePath(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
//qDebug("TorrentPersistentData: getSavePath %s", data["save_path"].toString().toLocal8Bit().data());
|
||||
return torrent.value("save_path").toString();
|
||||
}
|
||||
|
||||
QString TorrentPersistentData::getLabel(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("label", "").toString();
|
||||
}
|
||||
|
||||
QString TorrentPersistentData::getName(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("name", "").toString();
|
||||
}
|
||||
|
||||
int TorrentPersistentData::getPriority(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("priority", -1).toInt();
|
||||
}
|
||||
|
||||
bool TorrentPersistentData::isSeed(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("seed", false).toBool();
|
||||
}
|
||||
|
||||
bool TorrentPersistentData::isMagnet(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("is_magnet", false).toBool();
|
||||
}
|
||||
|
||||
QString TorrentPersistentData::getMagnetUri(const QString &hash) const
|
||||
{
|
||||
const QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
Q_ASSERT(torrent.value("is_magnet", false).toBool());
|
||||
return torrent.value("magnet_uri").toString();
|
||||
}
|
||||
|
||||
bool TorrentPersistentData::getHasMissingFiles(const QString& hash)
|
||||
{
|
||||
QHash<QString, QVariant> torrent = value(hash).toHash();
|
||||
return torrent.value("has_missing_files").toBool();
|
||||
}
|
||||
181
src/core/torrentpersistentdata.h
Normal file
181
src/core/torrentpersistentdata.h
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef TORRENTPERSISTENTDATA_H
|
||||
#define TORRENTPERSISTENTDATA_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
#include <QReadWriteLock>
|
||||
#include <vector>
|
||||
#include "preferences.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QDateTime;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class QTorrentHandle;
|
||||
|
||||
class TorrentTempData
|
||||
{
|
||||
// This class stores strings w/o modifying separators
|
||||
public:
|
||||
static bool hasTempData(const QString &hash);
|
||||
static void deleteTempData(const QString &hash);
|
||||
static void setFilesPriority(const QString &hash, const std::vector<int> &pp);
|
||||
static void setFilesPath(const QString &hash, const QStringList &path_list);
|
||||
static void setSavePath(const QString &hash, const QString &save_path);
|
||||
static void setLabel(const QString &hash, const QString &label);
|
||||
static void setSequential(const QString &hash, const bool &sequential);
|
||||
static bool isSequential(const QString &hash);
|
||||
static void setSeedingMode(const QString &hash, const bool &seed);
|
||||
static bool isSeedingMode(const QString &hash);
|
||||
static QString getSavePath(const QString &hash);
|
||||
static QStringList getFilesPath(const QString &hash);
|
||||
static QString getLabel(const QString &hash);
|
||||
static void getFilesPriority(const QString &hash, std::vector<int> &fp);
|
||||
static bool isMoveInProgress(const QString &hash);
|
||||
static void enqueueMove(const QString &hash, const QString &queuedPath);
|
||||
static void startMove(const QString &hash, const QString &oldPath, const QString& newPath);
|
||||
static void finishMove(const QString &hash);
|
||||
static QString getOldPath(const QString &hash);
|
||||
static QString getNewPath(const QString &hash);
|
||||
static QString getQueuedPath(const QString &hash);
|
||||
static void setAddPaused(const QString &hash, const bool &paused);
|
||||
static bool isAddPaused(const QString &hash);
|
||||
|
||||
private:
|
||||
struct TorrentData
|
||||
{
|
||||
TorrentData();
|
||||
std::vector<int> files_priority;
|
||||
QStringList path_list;
|
||||
QString save_path;
|
||||
QString label;
|
||||
bool sequential;
|
||||
bool seed;
|
||||
bool add_paused;
|
||||
};
|
||||
|
||||
struct TorrentMoveState
|
||||
{
|
||||
TorrentMoveState(QString oldPath, QString newPath);
|
||||
// the moving occurs from oldPath to newPath
|
||||
// queuedPath is where files should be moved to, when current moving is completed
|
||||
QString oldPath;
|
||||
QString newPath;
|
||||
QString queuedPath;
|
||||
};
|
||||
|
||||
static QHash<QString, TorrentData> data;
|
||||
static QHash<QString, TorrentMoveState> torrentMoveStates;
|
||||
};
|
||||
|
||||
class HiddenData
|
||||
{
|
||||
public:
|
||||
static void addData(const QString &hash);
|
||||
static bool hasData(const QString &hash);
|
||||
static void deleteData(const QString &hash);
|
||||
static int getSize();
|
||||
static int getDownloadingSize();
|
||||
static void gotMetadata(const QString &hash);
|
||||
|
||||
private:
|
||||
static QHash<QString, bool> data;
|
||||
static unsigned int metadata_counter;
|
||||
};
|
||||
|
||||
class TorrentPersistentData: QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TorrentPersistentData)
|
||||
|
||||
public:
|
||||
enum RatioLimit
|
||||
{
|
||||
USE_GLOBAL_RATIO = -2,
|
||||
NO_RATIO_LIMIT = -1
|
||||
};
|
||||
|
||||
static TorrentPersistentData* instance();
|
||||
static void drop();
|
||||
~TorrentPersistentData();
|
||||
|
||||
bool isKnownTorrent(QString hash) const;
|
||||
QStringList knownTorrents() const;
|
||||
void setRatioLimit(const QString &hash, const qreal &ratio);
|
||||
qreal getRatioLimit(const QString &hash) const;
|
||||
bool hasPerTorrentRatioLimit() const;
|
||||
void setAddedDate(const QString &hash, const QDateTime &time);
|
||||
QDateTime getAddedDate(const QString &hash) const;
|
||||
void setErrorState(const QString &hash, const bool has_error);
|
||||
bool hasError(const QString &hash) const;
|
||||
QDateTime getSeedDate(const QString &hash) const;
|
||||
void deletePersistentData(const QString &hash);
|
||||
void saveTorrentPersistentData(const QTorrentHandle &h, const QString &save_path = QString::null, const bool is_magnet = false);
|
||||
|
||||
// Setters
|
||||
void saveSavePath(const QString &hash, const QString &save_path);
|
||||
void saveLabel(const QString &hash, const QString &label);
|
||||
void saveName(const QString &hash, const QString &name);
|
||||
void savePriority(const QTorrentHandle &h);
|
||||
void savePriority(const QString &hash, const int &queue_pos);
|
||||
void saveSeedStatus(const QTorrentHandle &h);
|
||||
void saveSeedStatus(const QString &hash, const bool seedStatus);
|
||||
void setHasMissingFiles(const QString &hash, bool missing);
|
||||
|
||||
// Getters
|
||||
QString getSavePath(const QString &hash) const;
|
||||
QString getLabel(const QString &hash) const;
|
||||
QString getName(const QString &hash) const;
|
||||
int getPriority(const QString &hash) const;
|
||||
bool isSeed(const QString &hash) const;
|
||||
bool isMagnet(const QString &hash) const;
|
||||
QString getMagnetUri(const QString &hash) const;
|
||||
bool getHasMissingFiles(const QString& hash);
|
||||
public slots:
|
||||
void save();
|
||||
|
||||
private:
|
||||
TorrentPersistentData();
|
||||
static TorrentPersistentData* m_instance;
|
||||
QHash<QString, QVariant> m_data;
|
||||
bool dirty;
|
||||
QTimer timer;
|
||||
mutable QReadWriteLock lock;
|
||||
const QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
void setValue(const QString &key, const QVariant &value);
|
||||
|
||||
};
|
||||
|
||||
#endif // TORRENTPERSISTENTDATA_H
|
||||
35
src/core/tracker/qpeer.h
Normal file
35
src/core/tracker/qpeer.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef QPEER_H
|
||||
#define QPEER_H
|
||||
|
||||
#include <libtorrent/entry.hpp>
|
||||
#include <QString>
|
||||
|
||||
struct QPeer {
|
||||
|
||||
bool operator!=(const QPeer &other) const {
|
||||
return qhash() != other.qhash();
|
||||
}
|
||||
|
||||
bool operator==(const QPeer &other) const {
|
||||
return qhash() == other.qhash();
|
||||
}
|
||||
|
||||
QString qhash() const {
|
||||
return ip+":"+QString::number(port);
|
||||
}
|
||||
|
||||
libtorrent::entry toEntry(bool no_peer_id) const {
|
||||
libtorrent::entry::dictionary_type peer_map;
|
||||
if (!no_peer_id)
|
||||
peer_map["id"] = libtorrent::entry(peer_id.toStdString());
|
||||
peer_map["ip"] = libtorrent::entry(ip.toStdString());
|
||||
peer_map["port"] = libtorrent::entry(port);
|
||||
return libtorrent::entry(peer_map);
|
||||
}
|
||||
|
||||
QString ip;
|
||||
QString peer_id;
|
||||
int port;
|
||||
};
|
||||
|
||||
#endif // QPEER_H
|
||||
239
src/core/tracker/qtracker.cpp
Normal file
239
src/core/tracker/qtracker.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
|
||||
#include "qtracker.h"
|
||||
#include "preferences.h"
|
||||
#include "httpresponsegenerator.h"
|
||||
#include "httprequestparser.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace libtorrent;
|
||||
|
||||
QTracker::QTracker(QObject *parent) :
|
||||
QTcpServer(parent)
|
||||
{
|
||||
Q_ASSERT(Preferences::instance()->isTrackerEnabled());
|
||||
connect(this, SIGNAL(newConnection()), this, SLOT(handlePeerConnection()));
|
||||
}
|
||||
|
||||
QTracker::~QTracker() {
|
||||
if (isListening()) {
|
||||
qDebug("Shutting down the embedded tracker...");
|
||||
close();
|
||||
}
|
||||
// TODO: Store the torrent list
|
||||
}
|
||||
|
||||
void QTracker::handlePeerConnection()
|
||||
{
|
||||
QTcpSocket *socket;
|
||||
while((socket = nextPendingConnection()))
|
||||
{
|
||||
qDebug("QTracker: New peer connection");
|
||||
connect(socket, SIGNAL(readyRead()), SLOT(readRequest()));
|
||||
}
|
||||
}
|
||||
|
||||
bool QTracker::start()
|
||||
{
|
||||
const int listen_port = Preferences::instance()->getTrackerPort();
|
||||
//
|
||||
if (isListening()) {
|
||||
if (serverPort() == listen_port) {
|
||||
// Already listening on the right port, just return
|
||||
return true;
|
||||
}
|
||||
// Wrong port, closing the server
|
||||
close();
|
||||
}
|
||||
qDebug("Starting the embedded tracker...");
|
||||
// Listen on the predefined port
|
||||
return listen(QHostAddress::Any, listen_port);
|
||||
}
|
||||
|
||||
void QTracker::readRequest()
|
||||
{
|
||||
QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
|
||||
QByteArray input = socket->readAll();
|
||||
//qDebug("QTracker: Raw request:\n%s", input.data());
|
||||
HttpRequest request;
|
||||
if (HttpRequestParser::parse(input, request) != HttpRequestParser::NoError) {
|
||||
qDebug("QTracker: Invalid HTTP Request:\n %s", qPrintable(input));
|
||||
respondInvalidRequest(socket, 100, "Invalid request type");
|
||||
return;
|
||||
}
|
||||
//qDebug("QTracker received the following request:\n%s", qPrintable(parser.toString()));
|
||||
// Request is correct, is it a GET request?
|
||||
if (request.method != "GET") {
|
||||
qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(request.method));
|
||||
respondInvalidRequest(socket, 100, "Invalid request type");
|
||||
return;
|
||||
}
|
||||
if (!request.path.startsWith("/announce", Qt::CaseInsensitive)) {
|
||||
qDebug("QTracker: Unrecognized path: %s", qPrintable(request.path));
|
||||
respondInvalidRequest(socket, 100, "Invalid request type");
|
||||
return;
|
||||
}
|
||||
|
||||
// OK, this is a GET request
|
||||
respondToAnnounceRequest(socket, request.gets);
|
||||
}
|
||||
|
||||
void QTracker::respondInvalidRequest(QTcpSocket *socket, int code, QString msg)
|
||||
{
|
||||
HttpResponse response(code, msg);
|
||||
socket->write(HttpResponseGenerator::generate(response));
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
|
||||
void QTracker::respondToAnnounceRequest(QTcpSocket *socket,
|
||||
const QMap<QString, QString>& get_parameters)
|
||||
{
|
||||
TrackerAnnounceRequest annonce_req;
|
||||
// IP
|
||||
annonce_req.peer.ip = socket->peerAddress().toString();
|
||||
// 1. Get info_hash
|
||||
if (!get_parameters.contains("info_hash")) {
|
||||
qDebug("QTracker: Missing info_hash");
|
||||
respondInvalidRequest(socket, 101, "Missing info_hash");
|
||||
return;
|
||||
}
|
||||
annonce_req.info_hash = get_parameters.value("info_hash");
|
||||
// info_hash cannot be longer than 20 bytes
|
||||
/*if (annonce_req.info_hash.toLatin1().length() > 20) {
|
||||
qDebug("QTracker: Info_hash is not 20 byte long: %s (%d)", qPrintable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length());
|
||||
respondInvalidRequest(socket, 150, "Invalid infohash");
|
||||
return;
|
||||
}*/
|
||||
// 2. Get peer ID
|
||||
if (!get_parameters.contains("peer_id")) {
|
||||
qDebug("QTracker: Missing peer_id");
|
||||
respondInvalidRequest(socket, 102, "Missing peer_id");
|
||||
return;
|
||||
}
|
||||
annonce_req.peer.peer_id = get_parameters.value("peer_id");
|
||||
// peer_id cannot be longer than 20 bytes
|
||||
/*if (annonce_req.peer.peer_id.length() > 20) {
|
||||
qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id));
|
||||
respondInvalidRequest(socket, 151, "Invalid peerid");
|
||||
return;
|
||||
}*/
|
||||
// 3. Get port
|
||||
if (!get_parameters.contains("port")) {
|
||||
qDebug("QTracker: Missing port");
|
||||
respondInvalidRequest(socket, 103, "Missing port");
|
||||
return;
|
||||
}
|
||||
bool ok = false;
|
||||
annonce_req.peer.port = get_parameters.value("port").toInt(&ok);
|
||||
if (!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) {
|
||||
qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port);
|
||||
respondInvalidRequest(socket, 103, "Missing port");
|
||||
return;
|
||||
}
|
||||
// 4. Get event
|
||||
annonce_req.event = "";
|
||||
if (get_parameters.contains("event")) {
|
||||
annonce_req.event = get_parameters.value("event");
|
||||
qDebug("QTracker: event is %s", qPrintable(annonce_req.event));
|
||||
}
|
||||
// 5. Get numwant
|
||||
annonce_req.numwant = 50;
|
||||
if (get_parameters.contains("numwant")) {
|
||||
int tmp = get_parameters.value("numwant").toInt();
|
||||
if (tmp > 0) {
|
||||
qDebug("QTracker: numwant=%d", tmp);
|
||||
annonce_req.numwant = tmp;
|
||||
}
|
||||
}
|
||||
// 6. no_peer_id (extension)
|
||||
annonce_req.no_peer_id = false;
|
||||
if (get_parameters.contains("no_peer_id")) {
|
||||
annonce_req.no_peer_id = true;
|
||||
}
|
||||
// 7. TODO: support "compact" extension
|
||||
// Done parsing, now let's reply
|
||||
if (m_torrents.contains(annonce_req.info_hash)) {
|
||||
if (annonce_req.event == "stopped") {
|
||||
qDebug("QTracker: Peer stopped downloading, deleting it from the list");
|
||||
m_torrents[annonce_req.info_hash].remove(annonce_req.peer.qhash());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Unknown torrent
|
||||
if (m_torrents.size() == MAX_TORRENTS) {
|
||||
// Reached max size, remove a random torrent
|
||||
m_torrents.erase(m_torrents.begin());
|
||||
}
|
||||
}
|
||||
// Register the user
|
||||
PeerList peers = m_torrents.value(annonce_req.info_hash);
|
||||
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
||||
// Too many peers, remove a random one
|
||||
peers.erase(peers.begin());
|
||||
}
|
||||
peers[annonce_req.peer.qhash()] = annonce_req.peer;
|
||||
m_torrents[annonce_req.info_hash] = peers;
|
||||
// Reply
|
||||
ReplyWithPeerList(socket, annonce_req);
|
||||
}
|
||||
|
||||
void QTracker::ReplyWithPeerList(QTcpSocket *socket, const TrackerAnnounceRequest &annonce_req)
|
||||
{
|
||||
// Prepare the entry for bencoding
|
||||
entry::dictionary_type reply_dict;
|
||||
reply_dict["interval"] = entry(ANNOUNCE_INTERVAL);
|
||||
QList<QPeer> peers = m_torrents.value(annonce_req.info_hash).values();
|
||||
entry::list_type peer_list;
|
||||
foreach (const QPeer & p, peers) {
|
||||
//if (p != annonce_req.peer)
|
||||
peer_list.push_back(p.toEntry(annonce_req.no_peer_id));
|
||||
}
|
||||
reply_dict["peers"] = entry(peer_list);
|
||||
entry reply_entry(reply_dict);
|
||||
// bencode
|
||||
std::vector<char> buf;
|
||||
bencode(std::back_inserter(buf), reply_entry);
|
||||
QByteArray reply(&buf[0], static_cast<int>(buf.size()));
|
||||
qDebug("QTracker: reply with the following bencoded data:\n %s", reply.constData());
|
||||
// HTTP reply
|
||||
HttpResponse response(200, "OK");
|
||||
response.content = reply;
|
||||
socket->write(HttpResponseGenerator::generate(response));
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
|
||||
|
||||
72
src/core/tracker/qtracker.h
Normal file
72
src/core/tracker/qtracker.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef QTRACKER_H
|
||||
#define QTRACKER_H
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QHash>
|
||||
|
||||
#include "trackerannouncerequest.h"
|
||||
#include "qpeer.h"
|
||||
|
||||
// static limits
|
||||
const int MAX_TORRENTS = 100;
|
||||
const int MAX_PEERS_PER_TORRENT = 1000;
|
||||
const int ANNOUNCE_INTERVAL = 1800; // 30min
|
||||
|
||||
typedef QHash<QString, QPeer> PeerList;
|
||||
typedef QHash<QString, PeerList> TorrentList;
|
||||
|
||||
/* Basic Bittorrent tracker implementation in Qt4 */
|
||||
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
|
||||
class QTracker : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(QTracker)
|
||||
|
||||
public:
|
||||
explicit QTracker(QObject *parent = 0);
|
||||
~QTracker();
|
||||
bool start();
|
||||
|
||||
protected slots:
|
||||
void readRequest();
|
||||
void handlePeerConnection();
|
||||
void respondInvalidRequest(QTcpSocket *socket, int code, QString msg);
|
||||
void respondToAnnounceRequest(QTcpSocket *socket, const QMap<QString, QString>& get_parameters);
|
||||
void ReplyWithPeerList(QTcpSocket *socket, const TrackerAnnounceRequest &annonce_req);
|
||||
|
||||
private:
|
||||
TorrentList m_torrents;
|
||||
|
||||
};
|
||||
|
||||
#endif // QTRACKER_H
|
||||
9
src/core/tracker/tracker.pri
Normal file
9
src/core/tracker/tracker.pri
Normal file
@@ -0,0 +1,9 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/qtracker.h \
|
||||
$$PWD/trackerannouncerequest.h \
|
||||
$$PWD/qpeer.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/qtracker.cpp
|
||||
15
src/core/tracker/trackerannouncerequest.h
Normal file
15
src/core/tracker/trackerannouncerequest.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef TRACKERANNOUNCEREQUEST_H
|
||||
#define TRACKERANNOUNCEREQUEST_H
|
||||
|
||||
#include <qpeer.h>
|
||||
|
||||
struct TrackerAnnounceRequest {
|
||||
QString info_hash;
|
||||
QString event;
|
||||
int numwant;
|
||||
QPeer peer;
|
||||
// Extensions
|
||||
bool no_peer_id;
|
||||
};
|
||||
|
||||
#endif // TRACKERANNOUNCEREQUEST_H
|
||||
Reference in New Issue
Block a user