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:
Vladimir Golovnev (Glassez)
2015-01-18 15:13:06 +03:00
parent e4c7f52bb3
commit ff9a281b72
797 changed files with 841 additions and 829 deletions

35
src/core/core.pri Normal file
View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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

View 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;
}

View 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

View 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;
}

View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

489
src/core/preferences.h Normal file
View 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
View 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

View 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(&current_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(&current_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(&current_tag->alerts_mutex);
while (alerts.empty())
alerts_condvar.wait(&current_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(&current_tag->alerts_mutex);
event_posted = false;
if (!alerts.empty())
enqueueToMainThread();
}

View 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

View 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

View 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");
}

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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
}

View 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;
}

View 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

View 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);
}

View 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

View 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;
}

View 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

View 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);
}

View 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

View 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();
}
}

View 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

View 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

View 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()
{
}

View 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

View 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>

View File

@@ -0,0 +1,5 @@
INCLUDEPATH += $$PWD
HEADERS += $$PWD/notifications.h
SOURCES += $$PWD/notifications.cpp

View 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;

View 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
View 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
View 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

View 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();
}

View 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
View 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

View 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();
}

View 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

View File

@@ -0,0 +1,9 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/qtracker.h \
$$PWD/trackerannouncerequest.h \
$$PWD/qpeer.h
SOURCES += \
$$PWD/qtracker.cpp

View 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