1
mirror of https://github.com/qbittorrent/qBittorrent synced 2025-10-09 18:32:15 +02:00

Compare commits

..

16 Commits

Author SHA1 Message Date
sledgehammer999
6486fc5f4d Bump to 4.0.2 2017-12-01 01:59:53 +02:00
sledgehammer999
1e059ab1a2 Sync translations from Transifex and run lupdate. 2017-12-01 01:44:49 +02:00
Mike Tzou
15b137211b [GUI] Implement stable sort (#7703)
* NaturalCompare now returns compare result instead of "less than" result
* Change to stable sort in GUI components
* Add Utils::String::naturalLessThan() helper function
* Use Qt::CaseSensitivity type
2017-12-01 01:39:16 +02:00
thalieht
6f8f1d7bad Coding style for many files 2017-12-01 01:39:15 +02:00
sledgehammer999
a31f0c0a3d Stop logging IP filter parsing errors after a while.
When a lot of errors happen the resulting log lines introduce
a huge slowdown of the GUI, due to writing each log line to disk.

Closes #7755.
2017-12-01 01:39:08 +02:00
Evgeny Lensky
f977d1293a Fix i386 build (configure)
Fix building on i686 fails with wrong multilib library path #7845

and rebregenerate ./configure
2017-11-27 19:16:03 +02:00
sledgehammer999
1399be50cb Fix crash on some systems when creating address object for 255.255.255.255
Closes #7735.
2017-11-27 19:14:41 +02:00
Vladimir Golovnev (Glassez)
52dcf32cc8 Implement Import/Export RSS rules in JSON format 2017-11-27 19:14:40 +02:00
Vladimir Golovnev (Glassez)
52b2b807ab Implement Import/Export RSS rules in legacy format 2017-11-27 19:14:38 +02:00
sledgehammer999
5cf4f00824 Remove examples from gpl.html. Closes #7749. 2017-11-27 19:14:37 +02:00
TheNicker
faa6fad025 Fixed blurry text under Windows by setting DPI awareness to default 2017-11-27 19:14:36 +02:00
Vladimir Golovnev (Glassez)
9f94bbce3a Fix RSS Parser
Closes #7751
Closes #7763
Closes #7768
Closes #7786
2017-11-27 19:14:35 +02:00
Chocobo1
5c49b2486c Change MixedModeAlgorithm default to TCP. Closes #7779.
MixedModeAlgorithm::Proportional will throttle TCP connections when utp
is in use and users is expecting maximum speed no matter what, so now
disable the throttling.
2017-11-27 19:14:34 +02:00
thoradia
4f6e7f97c6 Fix missing include in rss_feed.cpp
Fixes #7805.
2017-11-27 19:14:33 +02:00
sledgehammer999
7751c5b75c Merge pull request #7835 from heirecka/v4_0_x
Fix build with --disable-webui
2017-11-23 01:50:21 +02:00
Heiko Becker
a1a9f3317b Fix build with --disable-webui
"app/application.cpp:108:7: error: class 'Application' does not have
any field named 'm_webui'"
2017-11-22 21:30:08 +01:00
104 changed files with 17436 additions and 12621 deletions

View File

@@ -1,3 +1,16 @@
* Fri Dec 01 2017 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.0.2
- BUGFIX: Fix crash on some systems when creating address object for 255.255.255.255. Closes #7735. (sledgehammer999)
- PERFORMANCE: Change MixedModeAlgorithm default to TCP. This was the v3_3_x default and should sustain higher speeds. Closes #7779. (Chocobo1)
- PERFORMANCE: Stop logging IP filter parsing errors after a while, otherwise the GUI freezes or qBittorrent doesn't start. (sledgehammer999)
- GUI: Implement stable sort. Rows in transfer list shouldn't flicker anymore. (Chocobo1)
- WEBUI: Fix build when webui is disabled. (Heiko Becker)
- RSS: Fix build because of missing header. Closes #7805. (thoradia)
- RSS: Fix RSS parser. (glassez)
- RSS: Implement Import/Export RSS rules in legacy(aka v3_3_x) format. (glassez)
- RSS: Implement Import/Export RSS rules in JSON format. (glassez)
- WINDOWS: Fixed blurry text under Windows by setting DPI awareness to default. (TheNicker)
- LINUX: Fix i386 build. (Evgeny Lensky)
* Wed Nov 22 2017 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.0.1
- BUGFIX: Fix crash on opening torrent/magnet (uninitialized pointer). Closes #7739 #7723. (sledgehammer999)
- BUGFIX: Enable preferences Apply button when ip banlist is modified (Thomas Piccirello)

17
configure vendored
View File

@@ -690,7 +690,6 @@ infodir
docdir
oldincludedir
includedir
runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -784,7 +783,6 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1037,15 +1035,6 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
-runstatedir | --runstatedir | --runstatedi | --runstated \
| --runstate | --runstat | --runsta | --runst | --runs \
| --run | --ru | --r)
ac_prev=runstatedir ;;
-runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
| --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
| --run=* | --ru=* | --r=*)
runstatedir=$ac_optarg ;;
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1183,7 +1172,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
libdir localedir mandir runstatedir
libdir localedir mandir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1336,7 +1325,6 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -4705,9 +4693,8 @@ fi
libsubdirs="lib64 libx32 lib lib64" ;; #(
ppc64|s390x|sparc64|aarch64|ppc64le) :
libsubdirs="lib64 lib lib64" ;; #(
libsubdirs="lib") :
;; #(
*) :
libsubdirs="lib"
;;
esac

2
dist/mac/Info.plist vendored
View File

@@ -45,7 +45,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.0.1</string>
<string>4.0.2</string>
<key>CFBundleSignature</key>
<string>qBit</string>
<key>CFBundleExecutable</key>

View File

@@ -27,7 +27,7 @@ XPStyle on
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
; Program specific
!define PROG_VERSION "4.0.1"
!define PROG_VERSION "4.0.2"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun

View File

@@ -2,4 +2,4 @@
Translations = translations
[Platforms]
WindowsArguments = dpiawareness=1
;WindowsArguments = dpiawareness=1

View File

@@ -114,7 +114,7 @@ AC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[
AS_CASE([${host_cpu}],
[x86_64],[libsubdirs="lib64 libx32 lib lib64"],
[ppc64|s390x|sparc64|aarch64|ppc64le],[libsubdirs="lib64 lib lib64"],
[libsubdirs="lib"],
[libsubdirs="lib"]
)
dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give

View File

@@ -105,7 +105,9 @@ Application::Application(const QString &id, int &argc, char **argv)
, m_running(false)
, m_shutdownAct(ShutdownDialogAction::Exit)
, m_commandLineArgs(parseCommandLine(this->arguments()))
#ifndef DISABLE_WEBUI
, m_webui(nullptr)
#endif
{
qRegisterMetaType<Log::Msg>("Log::Msg");

View File

@@ -102,6 +102,7 @@ namespace
}
const int BUFFER_SIZE = 2 * 1024 * 1024; // 2 MiB
const int MAX_LOGGED_ERRORS = 5;
}
FilterParserThread::FilterParserThread(QObject *parent)
@@ -134,6 +135,12 @@ int FilterParserThread::parseDATFilterFile()
int start = 0;
int endOfLine = -1;
int nbLine = 0;
int parseErrorCount = 0;
const auto addLog = [&parseErrorCount](const QString &msg)
{
if (parseErrorCount <= MAX_LOGGED_ERRORS)
LogMsg(msg, Log::CRITICAL);
};
while (true) {
bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
@@ -202,7 +209,8 @@ int FilterParserThread::parseDATFilterFile()
int endOfIPRange = ((firstComma == -1) ? (endOfLine - 1) : (firstComma - 1));
int delimIP = findAndNullDelimiter(buffer.data(), '-', start, endOfIPRange);
if (delimIP == -1) {
LogMsg(tr("IP filter line %1 is malformed.").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
start = endOfLine;
continue;
}
@@ -210,7 +218,8 @@ int FilterParserThread::parseDATFilterFile()
libt::address startAddr;
int newStart = trim(buffer.data(), start, delimIP - 1);
if (!parseIPAddress(buffer.data() + newStart, startAddr)) {
LogMsg(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
start = endOfLine;
continue;
}
@@ -218,14 +227,16 @@ int FilterParserThread::parseDATFilterFile()
libt::address endAddr;
newStart = trim(buffer.data(), delimIP + 1, endOfIPRange);
if (!parseIPAddress(buffer.data() + newStart, endAddr)) {
LogMsg(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
start = endOfLine;
continue;
}
if ((startAddr.is_v4() != endAddr.is_v4())
|| (startAddr.is_v6() != endAddr.is_v6())) {
LogMsg(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
start = endOfLine;
continue;
}
@@ -238,8 +249,9 @@ int FilterParserThread::parseDATFilterFile()
++ruleCount;
}
catch (std::exception &e) {
LogMsg(tr("IP filter exception thrown for line %1. Exception is: %2").arg(nbLine)
.arg(QString::fromLocal8Bit(e.what())), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
.arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
}
}
@@ -247,6 +259,9 @@ int FilterParserThread::parseDATFilterFile()
offset = 0;
}
if (parseErrorCount > MAX_LOGGED_ERRORS)
LogMsg(tr("%1 extra IP filter parsing errors occurred.", "513 extra IP filter parsing errors occurred.")
.arg(parseErrorCount - MAX_LOGGED_ERRORS), Log::CRITICAL);
return ruleCount;
}
@@ -268,6 +283,12 @@ int FilterParserThread::parseP2PFilterFile()
int start = 0;
int endOfLine = -1;
int nbLine = 0;
int parseErrorCount = 0;
const auto addLog = [&parseErrorCount](const QString &msg)
{
if (parseErrorCount <= MAX_LOGGED_ERRORS)
LogMsg(msg, Log::CRITICAL);
};
while (true) {
bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
@@ -319,7 +340,8 @@ int FilterParserThread::parseP2PFilterFile()
// The "Some organization" part might contain a ':' char itself so we find the last occurrence
int partsDelimiter = findAndNullDelimiter(buffer.data(), ':', start, endOfLine, true);
if (partsDelimiter == -1) {
LogMsg(tr("IP filter line %1 is malformed.").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
start = endOfLine;
continue;
}
@@ -327,7 +349,8 @@ int FilterParserThread::parseP2PFilterFile()
// IP Range should be split by a dash
int delimIP = findAndNullDelimiter(buffer.data(), '-', partsDelimiter + 1, endOfLine);
if (delimIP == -1) {
LogMsg(tr("IP filter line %1 is malformed.").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
start = endOfLine;
continue;
}
@@ -335,7 +358,8 @@ int FilterParserThread::parseP2PFilterFile()
libt::address startAddr;
int newStart = trim(buffer.data(), partsDelimiter + 1, delimIP - 1);
if (!parseIPAddress(buffer.data() + newStart, startAddr)) {
LogMsg(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
start = endOfLine;
continue;
}
@@ -343,14 +367,16 @@ int FilterParserThread::parseP2PFilterFile()
libt::address endAddr;
newStart = trim(buffer.data(), delimIP + 1, endOfLine);
if (!parseIPAddress(buffer.data() + newStart, endAddr)) {
LogMsg(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
start = endOfLine;
continue;
}
if ((startAddr.is_v4() != endAddr.is_v4())
|| (startAddr.is_v6() != endAddr.is_v6())) {
LogMsg(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
start = endOfLine;
continue;
}
@@ -362,8 +388,9 @@ int FilterParserThread::parseP2PFilterFile()
++ruleCount;
}
catch (std::exception &e) {
LogMsg(tr("IP filter exception thrown for line %1. Exception is: %2").arg(nbLine)
.arg(QString::fromLocal8Bit(e.what())), Log::CRITICAL);
++parseErrorCount;
addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
.arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
}
}
@@ -371,6 +398,9 @@ int FilterParserThread::parseP2PFilterFile()
offset = 0;
}
if (parseErrorCount > MAX_LOGGED_ERRORS)
LogMsg(tr("%1 extra IP filter parsing errors occurred.", "513 extra IP filter parsing errors occurred.")
.arg(parseErrorCount - MAX_LOGGED_ERRORS), Log::CRITICAL);
return ruleCount;
}

View File

@@ -304,7 +304,7 @@ Session::Session(QObject *parent)
, m_btProtocol(BITTORRENT_SESSION_KEY("BTProtocol"), BTProtocol::Both
, clampValue(BTProtocol::Both, BTProtocol::UTP))
, m_isUTPRateLimited(BITTORRENT_SESSION_KEY("uTPRateLimited"), true)
, m_utpMixedMode(BITTORRENT_SESSION_KEY("uTPMixedMode"), MixedModeAlgorithm::Proportional
, m_utpMixedMode(BITTORRENT_SESSION_KEY("uTPMixedMode"), MixedModeAlgorithm::TCP
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
@@ -1418,8 +1418,11 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
void Session::configurePeerClasses()
{
libt::ip_filter f;
f.add_rule(libt::address_v4::from_string("0.0.0.0")
, libt::address_v4::from_string("255.255.255.255")
// address_v4::from_string("255.255.255.255") crashes on some people's systems
// so instead we use address_v4::broadcast()
// Proactively do the same for 0.0.0.0 and address_v4::any()
f.add_rule(libt::address_v4::any()
, libt::address_v4::broadcast()
, 1 << libt::session::global_peer_class_id);
#if TORRENT_USE_IPV6
// IPv6 may not be available on OS and the parsing

View File

@@ -227,10 +227,9 @@ void Parser::parse(const QByteArray &feedData)
// read and create items from a rss document
void Parser::parse_impl(const QByteArray &feedData)
{
qDebug() << Q_FUNC_INFO;
QXmlStreamReader xml(feedData);
bool foundChannel = false;
while (xml.readNextStartElement()) {
if (xml.name() == "rss") {
// Find channels
@@ -258,11 +257,15 @@ void Parser::parse_impl(const QByteArray &feedData)
}
}
if (xml.hasError())
m_result.error = xml.errorString();
else if (!foundChannel)
if (!foundChannel) {
m_result.error = tr("Invalid RSS feed.");
else
}
else if (xml.hasError()) {
m_result.error = tr("%1 (line: %2, column: %3, offset: %4).")
.arg(xml.errorString()).arg(xml.lineNumber())
.arg(xml.columnNumber()).arg(xml.characterOffset());
}
else {
// Sort article list chronologically
// NOTE: We don't need to sort it here if articles are always
// sorted in fetched XML in reverse chronological order
@@ -271,6 +274,7 @@ void Parser::parse_impl(const QByteArray &feedData)
{
return a1["date"].toDateTime() < a2["date"].toDateTime();
});
}
emit finished(m_result);
m_result.articles.clear(); // clear articles only
@@ -288,35 +292,34 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
break;
if (xml.isStartElement()) {
const QString text(xml.readElementText().trimmed());
if (name == QLatin1String("title")) {
article[Article::KeyTitle] = text;
article[Article::KeyTitle] = xml.readElementText().trimmed();
}
else if (name == QLatin1String("enclosure")) {
if (xml.attributes().value("type") == QLatin1String("application/x-bittorrent"))
article[Article::KeyTorrentURL] = xml.attributes().value(QLatin1String("url")).toString();
}
else if (name == QLatin1String("link")) {
const QString text {xml.readElementText().trimmed()};
if (text.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive))
article[Article::KeyTorrentURL] = text; // magnet link instead of a news URL
else
article[Article::KeyLink] = text;
}
else if (name == QLatin1String("description")) {
article[Article::KeyDescription] = text;
article[Article::KeyDescription] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
}
else if (name == QLatin1String("pubDate")) {
article[Article::KeyDate] = parseDate(text);
article[Article::KeyDate] = parseDate(xml.readElementText().trimmed());
}
else if (name == QLatin1String("author")) {
article[Article::KeyAuthor] = text;
article[Article::KeyAuthor] = xml.readElementText().trimmed();
}
else if (name == QLatin1String("guid")) {
article[Article::KeyId] = text;
article[Article::KeyId] = xml.readElementText().trimmed();
}
else {
article[name] = text;
article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
}
}
}
@@ -326,17 +329,14 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
void Parser::parseRSSChannel(QXmlStreamReader &xml)
{
qDebug() << Q_FUNC_INFO;
Q_ASSERT(xml.isStartElement() && xml.name() == "channel");
while (!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == "title") {
if (xml.name() == QLatin1String("title")) {
m_result.title = xml.readElementText();
}
else if (xml.name() == "lastBuildDate") {
else if (xml.name() == QLatin1String("lastBuildDate")) {
QString lastBuildDate = xml.readElementText();
if (!lastBuildDate.isEmpty()) {
if (m_result.lastBuildDate == lastBuildDate) {
@@ -346,7 +346,7 @@ void Parser::parseRSSChannel(QXmlStreamReader &xml)
m_result.lastBuildDate = lastBuildDate;
}
}
else if (xml.name() == "item") {
else if (xml.name() == QLatin1String("item")) {
parseRssArticle(xml);
}
}
@@ -366,14 +366,12 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
break;
if (xml.isStartElement()) {
const QString text(xml.readElementText().trimmed());
if (name == QLatin1String("title")) {
article[Article::KeyTitle] = text;
article[Article::KeyTitle] = xml.readElementText().trimmed();
}
else if (name == QLatin1String("link")) {
QString link = (xml.attributes().isEmpty()
? text
? xml.readElementText().trimmed()
: xml.attributes().value(QLatin1String("href")).toString());
if (link.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive))
@@ -385,42 +383,38 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
article[Article::KeyLink] = (m_baseUrl.isEmpty() ? link : m_baseUrl + link);
}
else if ((name == QLatin1String("summary")) || (name == QLatin1String("content"))){
else if ((name == QLatin1String("summary")) || (name == QLatin1String("content"))) {
if (doubleContent) { // Duplicate content -> ignore
xml.readNext();
while ((xml.name() != QLatin1String("summary")) && (xml.name() != QLatin1String("content")))
xml.readNext();
xml.skipCurrentElement();
continue;
}
// Try to also parse broken articles, which don't use html '&' escapes
// Actually works great for non-broken content too
QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements);
if (!feedText.isEmpty())
article[Article::KeyDescription] = feedText.trimmed();
doubleContent = true;
QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
if (!feedText.isEmpty()) {
article[Article::KeyDescription] = feedText;
doubleContent = true;
}
}
else if (name == QLatin1String("updated")) {
// ATOM uses standard compliant date, don't do fancy stuff
QDateTime articleDate = QDateTime::fromString(text, Qt::ISODate);
QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
article[Article::KeyDate] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime());
}
else if (name == QLatin1String("author")) {
xml.readNext();
while (xml.name() != QLatin1String("author")) {
while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("name"))
article[Article::KeyAuthor] = xml.readElementText().trimmed();
xml.readNext();
else
xml.skipCurrentElement();
}
}
else if (name == QLatin1String("id")) {
article[Article::KeyId] = text;
article[Article::KeyId] = xml.readElementText().trimmed();
}
else {
article[name] = text;
article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
}
}
}
@@ -430,19 +424,16 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
void Parser::parseAtomChannel(QXmlStreamReader &xml)
{
qDebug() << Q_FUNC_INFO;
Q_ASSERT(xml.isStartElement() && xml.name() == "feed");
m_baseUrl = xml.attributes().value("xml:base").toString();
while (!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == "title") {
if (xml.name() == QLatin1String("title")) {
m_result.title = xml.readElementText();
}
else if (xml.name() == "updated") {
else if (xml.name() == QLatin1String("updated")) {
QString lastBuildDate = xml.readElementText();
if (!lastBuildDate.isEmpty()) {
if (m_result.lastBuildDate == lastBuildDate) {
@@ -452,7 +443,7 @@ void Parser::parseAtomChannel(QXmlStreamReader &xml)
m_result.lastBuildDate = lastBuildDate;
}
}
else if (xml.name() == "entry") {
else if (xml.name() == QLatin1String("entry")) {
parseAtomArticle(xml);
}
}

View File

@@ -28,6 +28,7 @@
#include "rss_autodownloader.h"
#include <QDataStream>
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
@@ -37,6 +38,7 @@
#include <QThread>
#include <QTimer>
#include <QVariant>
#include <QVector>
#include "../bittorrent/magneturi.h"
#include "../bittorrent/session.h"
@@ -63,6 +65,32 @@ const QString RulesFileName(QStringLiteral("download_rules.json"));
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
namespace
{
QVector<RSS::AutoDownloadRule> rulesFromJSON(const QByteArray &jsonData)
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
throw RSS::ParsingError(jsonError.errorString());
if (!jsonDoc.isObject())
throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format."));
const QJsonObject jsonObj {jsonDoc.object()};
QVector<RSS::AutoDownloadRule> rules;
for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it) {
const QJsonValue jsonVal {it.value()};
if (!jsonVal.isObject())
throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format."));
rules.append(RSS::AutoDownloadRule::fromJsonObject(jsonVal.toObject(), it.key()));
}
return rules;
}
}
using namespace RSS;
QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr;
@@ -84,8 +112,8 @@ AutoDownloader::AutoDownloader()
connect(m_ioThread, &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater);
connect(m_fileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
{
Logger::instance()->addMessage(QString("Couldn't save RSS AutoDownloader data in %1. Error: %2")
.arg(fileName).arg(errorString), Log::WARNING);
LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
.arg(fileName).arg(errorString), Log::CRITICAL);
});
m_ioThread->start();
@@ -174,6 +202,70 @@ void AutoDownloader::removeRule(const QString &ruleName)
}
}
QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) const
{
switch (format) {
case RulesFileFormat::Legacy:
return exportRulesToLegacyFormat();
default:
return exportRulesToJSONFormat();
}
}
void AutoDownloader::importRules(const QByteArray &data, AutoDownloader::RulesFileFormat format)
{
switch (format) {
case RulesFileFormat::Legacy:
importRulesFromLegacyFormat(data);
break;
default:
importRulesFromJSONFormat(data);
}
}
QByteArray AutoDownloader::exportRulesToJSONFormat() const
{
QJsonObject jsonObj;
for (const auto &rule : rules())
jsonObj.insert(rule.name(), rule.toJsonObject());
return QJsonDocument(jsonObj).toJson();
}
void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data)
{
const auto rules = rulesFromJSON(data);
for (const auto &rule : rules)
insertRule(rule);
}
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
{
QVariantHash dict;
for (const auto &rule : rules())
dict[rule.name()] = rule.toLegacyDict();
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_5);
out << dict;
return data;
}
void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
{
QDataStream in(data);
in.setVersion(QDataStream::Qt_4_5);
QVariantHash dict;
in >> dict;
if (in.status() != QDataStream::Ok)
throw ParsingError(tr("Invalid data format"));
for (const QVariant &val : dict)
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
}
void AutoDownloader::process()
{
if (m_processingQueue.isEmpty()) return; // processing was disabled
@@ -276,39 +368,20 @@ void AutoDownloader::load()
else if (rulesFile.open(QFile::ReadOnly))
loadRules(rulesFile.readAll());
else
Logger::instance()->addMessage(
QString("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
.arg(rulesFile.fileName()).arg(rulesFile.errorString()), Log::WARNING);
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
.arg(rulesFile.fileName()).arg(rulesFile.errorString()), Log::CRITICAL);
}
void AutoDownloader::loadRules(const QByteArray &data)
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
Logger::instance()->addMessage(
QString("Couldn't parse RSS AutoDownloader rules. Error: %1")
.arg(jsonError.errorString()), Log::WARNING);
return;
try {
const auto rules = rulesFromJSON(data);
for (const auto &rule : rules)
setRule_impl(rule);
}
if (!jsonDoc.isObject()) {
Logger::instance()->addMessage(
QString("Couldn't load RSS AutoDownloader rules. Invalid data format."), Log::WARNING);
return;
}
QJsonObject jsonObj = jsonDoc.object();
foreach (const QString &key, jsonObj.keys()) {
const QJsonValue jsonVal = jsonObj.value(key);
if (!jsonVal.isObject()) {
Logger::instance()->addMessage(
QString("Couldn't load RSS AutoDownloader rule '%1'. Invalid data format.")
.arg(key), Log::WARNING);
continue;
}
setRule_impl(AutoDownloadRule::fromJsonObject(jsonVal.toObject(), key));
catch (const ParsingError &error) {
LogMsg(tr("Couldn't load RSS AutoDownloader rules. Reason: %1")
.arg(error.message()), Log::CRITICAL);
}
}
@@ -317,7 +390,7 @@ void AutoDownloader::loadRulesLegacy()
SettingsPtr settings = Profile::instance().applicationSettings(QStringLiteral("qBittorrent-rss"));
QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash();
foreach (const QVariant &ruleVar, rules) {
auto rule = AutoDownloadRule::fromVariantHash(ruleVar.toHash());
auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash());
if (!rule.name().isEmpty())
insertRule(rule);
}
@@ -385,3 +458,13 @@ void AutoDownloader::timerEvent(QTimerEvent *event)
Q_UNUSED(event);
store();
}
ParsingError::ParsingError(const QString &message)
: std::runtime_error(message.toUtf8().data())
{
}
QString ParsingError::message() const
{
return what();
}

View File

@@ -28,6 +28,8 @@
#pragma once
#include <stdexcept>
#include <QBasicTimer>
#include <QHash>
#include <QList>
@@ -49,6 +51,13 @@ namespace RSS
class AutoDownloadRule;
class ParsingError : public std::runtime_error
{
public:
explicit ParsingError(const QString &message);
QString message() const;
};
class AutoDownloader final: public QObject
{
Q_OBJECT
@@ -60,6 +69,12 @@ namespace RSS
~AutoDownloader() override;
public:
enum class RulesFileFormat
{
Legacy,
JSON
};
static AutoDownloader *instance();
bool isProcessingEnabled() const;
@@ -73,6 +88,9 @@ namespace RSS
bool renameRule(const QString &ruleName, const QString &newRuleName);
void removeRule(const QString &ruleName);
QByteArray exportRules(RulesFileFormat format = RulesFileFormat::JSON) const;
void importRules(const QByteArray &data, RulesFileFormat format = RulesFileFormat::JSON);
signals:
void processingStateChanged(bool enabled);
void ruleAdded(const QString &ruleName);
@@ -98,6 +116,10 @@ namespace RSS
void loadRulesLegacy();
void store();
void storeDeferred();
QByteArray exportRulesToJSONFormat() const;
void importRulesFromJSONFormat(const QByteArray &data);
QByteArray exportRulesToLegacyFormat() const;
void importRulesFromLegacyFormat(const QByteArray &data);
static QPointer<AutoDownloader> m_instance;

View File

@@ -63,11 +63,29 @@ namespace
QJsonValue triStateBoolToJsonValue(const TriStateBool &triStateBool)
{
switch (static_cast<int>(triStateBool)) {
case 0: return false; break;
case 1: return true; break;
case 0: return false;
case 1: return true;
default: return QJsonValue();
}
}
TriStateBool addPausedLegacyToTriStateBool(int val)
{
switch (val) {
case 1: return TriStateBool::True; // always
case 2: return TriStateBool::False; // never
default: return TriStateBool::Undefined; // default
}
}
int triStateBoolToAddPausedLegacy(const TriStateBool &triStateBool)
{
switch (static_cast<int>(triStateBool)) {
case 0: return 2; // never
case 1: return 1; // always
default: return 0; // default
}
}
}
const QString Str_Name(QStringLiteral("name"));
@@ -378,21 +396,37 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
return rule;
}
AutoDownloadRule AutoDownloadRule::fromVariantHash(const QVariantHash &varHash)
QVariantHash AutoDownloadRule::toLegacyDict() const
{
AutoDownloadRule rule(varHash.value("name").toString());
return {{"name", name()},
{"must_contain", mustContain()},
{"must_not_contain", mustNotContain()},
{"save_path", savePath()},
{"affected_feeds", feedURLs()},
{"enabled", isEnabled()},
{"category_assigned", assignedCategory()},
{"use_regex", useRegex()},
{"add_paused", triStateBoolToAddPausedLegacy(addPaused())},
{"episode_filter", episodeFilter()},
{"last_match", lastMatch()},
{"ignore_days", ignoreDays()}};
}
rule.setUseRegex(varHash.value("use_regex", false).toBool());
rule.setMustContain(varHash.value("must_contain").toString());
rule.setMustNotContain(varHash.value("must_not_contain").toString());
rule.setEpisodeFilter(varHash.value("episode_filter").toString());
rule.setFeedURLs(varHash.value("affected_feeds").toStringList());
rule.setEnabled(varHash.value("enabled", false).toBool());
rule.setSavePath(varHash.value("save_path").toString());
rule.setCategory(varHash.value("category_assigned").toString());
rule.setAddPaused(TriStateBool(varHash.value("add_paused").toInt() - 1));
rule.setLastMatch(varHash.value("last_match").toDateTime());
rule.setIgnoreDays(varHash.value("ignore_days").toInt());
AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict)
{
AutoDownloadRule rule(dict.value("name").toString());
rule.setUseRegex(dict.value("use_regex", false).toBool());
rule.setMustContain(dict.value("must_contain").toString());
rule.setMustNotContain(dict.value("must_not_contain").toString());
rule.setEpisodeFilter(dict.value("episode_filter").toString());
rule.setFeedURLs(dict.value("affected_feeds").toStringList());
rule.setEnabled(dict.value("enabled", false).toBool());
rule.setSavePath(dict.value("save_path").toString());
rule.setCategory(dict.value("category_assigned").toString());
rule.setAddPaused(addPausedLegacyToTriStateBool(dict.value("add_paused").toInt()));
rule.setLastMatch(dict.value("last_match").toDateTime());
rule.setIgnoreDays(dict.value("ignore_days").toInt());
return rule;
}

View File

@@ -84,7 +84,9 @@ namespace RSS
QJsonObject toJsonObject() const;
static AutoDownloadRule fromJsonObject(const QJsonObject &jsonObj, const QString &name = "");
static AutoDownloadRule fromVariantHash(const QVariantHash &varHash);
QVariantHash toLegacyDict() const;
static AutoDownloadRule fromLegacyDict(const QVariantHash &dict);
private:
bool matches(const QString &articleTitle, const QString &expression) const;

View File

@@ -31,6 +31,7 @@
#include "rss_feed.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>

View File

@@ -33,9 +33,9 @@
#include <QByteArray>
#include <QCollator>
#include <QtGlobal>
#include <QLocale>
#include <QRegExp>
#include <QtGlobal>
#ifdef Q_OS_MAC
#include <QThreadStorage>
#endif
@@ -45,110 +45,103 @@ namespace
class NaturalCompare
{
public:
explicit NaturalCompare(const bool caseSensitive = true)
: m_caseSensitive(caseSensitive)
explicit NaturalCompare(const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive)
: m_caseSensitivity(caseSensitivity)
{
#if defined(Q_OS_WIN)
#ifdef Q_OS_WIN
// Without ICU library, QCollator uses the native API on Windows 7+. But that API
// sorts older versions of μTorrent differently than the newer ones because the
// 'μ' character is encoded differently and the native API can't cope with that.
// So default to using our custom natural sorting algorithm instead.
// See #5238 and #5240
// Without ICU library, QCollator doesn't support `setNumericMode(true)` on OS older than Win7
// if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS7)
return;
#endif
// Without ICU library, QCollator doesn't support `setNumericMode(true)` on an OS older than Win7
#else
m_collator.setNumericMode(true);
m_collator.setCaseSensitivity(caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
}
bool operator()(const QString &left, const QString &right) const
{
#if defined(Q_OS_WIN)
// Without ICU library, QCollator uses the native API on Windows 7+. But that API
// sorts older versions of μTorrent differently than the newer ones because the
// 'μ' character is encoded differently and the native API can't cope with that.
// So default to using our custom natural sorting algorithm instead.
// See #5238 and #5240
// Without ICU library, QCollator doesn't support `setNumericMode(true)` on OS older than Win7
// if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS7)
return lessThan(left, right);
m_collator.setCaseSensitivity(caseSensitivity);
#endif
return (m_collator.compare(left, right) < 0);
}
bool lessThan(const QString &left, const QString &right) const
int operator()(const QString &left, const QString &right) const
{
// Return value `false` indicates `right` should go before `left`, otherwise, after
int posL = 0;
int posR = 0;
while (true) {
while (true) {
if ((posL == left.size()) || (posR == right.size()))
return (left.size() < right.size()); // when a shorter string is another string's prefix, shorter string place before longer string
QChar leftChar = m_caseSensitive ? left[posL] : left[posL].toLower();
QChar rightChar = m_caseSensitive ? right[posR] : right[posR].toLower();
if (leftChar == rightChar)
; // compare next character
else if (leftChar.isDigit() && rightChar.isDigit())
break; // Both are digits, break this loop and compare numbers
else
return leftChar < rightChar;
++posL;
++posR;
}
int startL = posL;
while ((posL < left.size()) && left[posL].isDigit())
++posL;
int numL = left.midRef(startL, posL - startL).toInt();
int startR = posR;
while ((posR < right.size()) && right[posR].isDigit())
++posR;
int numR = right.midRef(startR, posR - startR).toInt();
if (numL != numR)
return (numL < numR);
// Strings + digits do match and we haven't hit string end
// Do another round
}
return false;
#ifdef Q_OS_WIN
return compare(left, right);
#else
return m_collator.compare(left, right);
#endif
}
private:
int compare(const QString &left, const QString &right) const
{
// Return value <0: `left` is smaller than `right`
// Return value >0: `left` is greater than `right`
// Return value =0: both strings are equal
int posL = 0;
int posR = 0;
while (true) {
if ((posL == left.size()) || (posR == right.size()))
return (left.size() - right.size()); // when a shorter string is another string's prefix, shorter string place before longer string
const QChar leftChar = (m_caseSensitivity == Qt::CaseSensitive) ? left[posL] : left[posL].toLower();
const QChar rightChar = (m_caseSensitivity == Qt::CaseSensitive) ? right[posR] : right[posR].toLower();
if (leftChar == rightChar) {
// compare next character
++posL;
++posR;
}
else if (leftChar.isDigit() && rightChar.isDigit()) {
// Both are digits, compare the numbers
const auto consumeNumber = [](const QString &str, int &pos) -> int
{
const int start = pos;
while ((pos < str.size()) && str[pos].isDigit())
++pos;
return str.midRef(start, (pos - start)).toInt();
};
const int numL = consumeNumber(left, posL);
const int numR = consumeNumber(right, posR);
if (numL != numR)
return (numL - numR);
// String + digits do match and we haven't hit the end of both strings
// then continue to consume the remainings
}
else {
return (leftChar.unicode() - rightChar.unicode());
}
}
}
QCollator m_collator;
const bool m_caseSensitive;
const Qt::CaseSensitivity m_caseSensitivity;
};
}
bool Utils::String::naturalCompareCaseSensitive(const QString &left, const QString &right)
int Utils::String::naturalCompare(const QString &left, const QString &right, const Qt::CaseSensitivity caseSensitivity)
{
// provide a single `NaturalCompare` instance for easy use
// https://doc.qt.io/qt-5/threads-reentrancy.html
if (caseSensitivity == Qt::CaseSensitive) {
#ifdef Q_OS_MAC // workaround for Apple xcode: https://stackoverflow.com/a/29929949
static QThreadStorage<NaturalCompare> nCmp;
if (!nCmp.hasLocalData()) nCmp.setLocalData(NaturalCompare(true));
return (nCmp.localData())(left, right);
static QThreadStorage<NaturalCompare> nCmp;
if (!nCmp.hasLocalData())
nCmp.setLocalData(NaturalCompare(Qt::CaseSensitive));
return (nCmp.localData())(left, right);
#else
thread_local NaturalCompare nCmp(true);
return nCmp(left, right);
thread_local NaturalCompare nCmp(Qt::CaseSensitive);
return nCmp(left, right);
#endif
}
}
bool Utils::String::naturalCompareCaseInsensitive(const QString &left, const QString &right)
{
// provide a single `NaturalCompare` instance for easy use
// https://doc.qt.io/qt-5/threads-reentrancy.html
#ifdef Q_OS_MAC // workaround for Apple xcode: https://stackoverflow.com/a/29929949
#ifdef Q_OS_MAC
static QThreadStorage<NaturalCompare> nCmp;
if (!nCmp.hasLocalData()) nCmp.setLocalData(NaturalCompare(false));
if (!nCmp.hasLocalData())
nCmp.setLocalData(NaturalCompare(Qt::CaseInsensitive));
return (nCmp.localData())(left, right);
#else
thread_local NaturalCompare nCmp(false);
thread_local NaturalCompare nCmp(Qt::CaseInsensitive);
return nCmp(left, right);
#endif
}
@@ -188,4 +181,3 @@ QString Utils::String::wildcardToRegex(const QString &pattern)
{
return qt_regexp_toCanonical(pattern, QRegExp::Wildcard);
}

View File

@@ -45,8 +45,12 @@ namespace Utils
// Taken from https://crackstation.net/hashing-security.htm
bool slowEquals(const QByteArray &a, const QByteArray &b);
bool naturalCompareCaseSensitive(const QString &left, const QString &right);
bool naturalCompareCaseInsensitive(const QString &left, const QString &right);
int naturalCompare(const QString &left, const QString &right, const Qt::CaseSensitivity caseSensitivity);
template <Qt::CaseSensitivity caseSensitivity>
bool naturalLessThan(const QString &left, const QString &right)
{
return (naturalCompare(left, right, caseSensitivity) < 0);
}
QString wildcardToRegex(const QString &pattern);

View File

@@ -128,7 +128,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
// Load categories
QStringList categories = session->categories().keys();
std::sort(categories.begin(), categories.end(), Utils::String::naturalCompareCaseInsensitive);
std::sort(categories.begin(), categories.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
QString defaultCategory = settings()->loadValue(KEY_DEFAULTCATEGORY).toString();
if (!m_torrentParams.category.isEmpty())

View File

@@ -50,8 +50,12 @@ bool CategoryFilterProxyModel::lessThan(const QModelIndex &left, const QModelInd
{
// "All" and "Uncategorized" must be left in place
if (CategoryFilterModel::isSpecialItem(left) || CategoryFilterModel::isSpecialItem(right))
return left.row() < right.row();
else
return Utils::String::naturalCompareCaseInsensitive(
left.data().toString(), right.data().toString());
return (left < right);
int result = Utils::String::naturalCompare(left.data().toString(), right.data().toString()
, Qt::CaseInsensitive);
if (result != 0)
return (result < 0);
return (mapFromSource(left) < mapFromSource(right));
}

View File

@@ -426,92 +426,6 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
</p>
<h3>END OF TERMS AND CONDITIONS</h3>
<h3><a name="howto"></a><a name="SEC4" href="#TOC4">How to Apply These Terms to Your New Programs</a></h3>
<p>
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
</p>
<p>
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
</p>
<pre>
<var>one line to give the program's name and an idea of what it does.</var>
Copyright (C) <var>yyyy</var> <var>name of author</var>
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.
</pre>
<p>
Also add information on how to contact you by electronic and paper mail.
</p>
<p>
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
</p>
<pre>
Gnomovision version 69, Copyright (C) <var>year</var> <var>name of author</var>
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
type `show w'. This is free software, and you are welcome
to redistribute it under certain conditions; type `show c'
for details.
</pre>
<p>
The hypothetical commands <samp>`show w'</samp> and <samp>`show c'</samp> should show
the appropriate parts of the General Public License. Of course, the
commands you use may be called something other than <samp>`show w'</samp> and
<samp>`show c'</samp>; they could even be mouse-clicks or menu items--whatever
suits your program.
</p>
<p>
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
</p>
<pre>
Yoyodyne, Inc., hereby disclaims all copyright
interest in the program `Gnomovision'
(which makes passes at compilers) written
by James Hacker.
<var>signature of Ty Coon</var>, 1 April 1989
Ty Coon, President of Vice
</pre>
<p>
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the
<a href="https://www.gnu.org/licenses/lgpl.html">GNU Lesser General Public License</a>
instead of this License.
</p>
<h3>END OF TERMS AND CONDITIONS</h3>
</body>
</html>

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,8 +24,6 @@
* 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 "downloadedpiecesbar.h"
@@ -127,24 +125,24 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
return true;
}
QVector<float> scaled_pieces = bitfieldToFloatVector(m_pieces, image2.width());
QVector<float> scaled_pieces_dl = bitfieldToFloatVector(m_downloadedPieces, image2.width());
QVector<float> scaledPieces = bitfieldToFloatVector(m_pieces, image2.width());
QVector<float> scaledPiecesDl = bitfieldToFloatVector(m_downloadedPieces, image2.width());
// filling image
for (int x = 0; x < scaled_pieces.size(); ++x) {
float pieces2_val = scaled_pieces.at(x);
float pieces2_val_dl = scaled_pieces_dl.at(x);
if (pieces2_val_dl != 0) {
float fill_ratio = pieces2_val + pieces2_val_dl;
float ratio = pieces2_val_dl / fill_ratio;
for (int x = 0; x < scaledPieces.size(); ++x) {
float piecesToValue = scaledPieces.at(x);
float piecesToValueDl = scaledPiecesDl.at(x);
if (piecesToValueDl != 0) {
float fillRatio = piecesToValue + piecesToValueDl;
float ratio = piecesToValueDl / fillRatio;
QRgb mixedColor = mixTwoColors(pieceColor().rgb(), m_dlPieceColor.rgb(), ratio);
mixedColor = mixTwoColors(backgroundColor().rgb(), mixedColor, fill_ratio);
mixedColor = mixTwoColors(backgroundColor().rgb(), mixedColor, fillRatio);
image2.setPixel(x, 0, mixedColor);
}
else {
image2.setPixel(x, 0, pieceColors()[pieces2_val * 255]);
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
}
}
image = image2;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,20 +24,18 @@
* 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 DOWNLOADEDPIECESBAR_H
#define DOWNLOADEDPIECESBAR_H
#include <QWidget>
#include <QBitArray>
#include <QVector>
#include <QWidget>
#include "piecesbar.h"
class DownloadedPiecesBar: public PiecesBar
class DownloadedPiecesBar : public PiecesBar
{
using base = PiecesBar;
Q_OBJECT

View File

@@ -61,7 +61,6 @@ public:
COL_COUNT
};
public:
PeerListDelegate(QObject *parent) : QItemDelegate(parent) {}
~PeerListDelegate() {}

View File

@@ -50,13 +50,21 @@ protected:
switch (sortColumn()) {
case PeerListDelegate::IP:
case PeerListDelegate::CLIENT: {
QString vL = left.data().toString();
QString vR = right.data().toString();
return Utils::String::naturalCompareCaseInsensitive(vL, vR);
}
};
const QString strL = left.data().toString();
const QString strR = right.data().toString();
const int result = Utils::String::naturalCompare(strL, strR, Qt::CaseInsensitive);
if (result != 0)
return (result < 0);
return QSortFilterProxyModel::lessThan(left, right);
return (mapFromSource(left) < mapFromSource(right));
}
break;
default:
if (left.data() != right.data())
return QSortFilterProxyModel::lessThan(left, right);
return (mapFromSource(left) < mapFromSource(right));
};
}
};

View File

@@ -231,8 +231,8 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
addPeerAct = menu.addAction(GuiIconProvider::instance()->getIcon("user-group-new"), tr("Add a new peer..."));
emptyMenu = false;
}
QAction *banAct = 0;
QAction *copyPeerAct = 0;
QAction *banAct = nullptr;
QAction *copyPeerAct = nullptr;
if (!selectionModel()->selectedRows().isEmpty()) {
copyPeerAct = menu.addAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Copy IP:port"));
menu.addSeparator();
@@ -241,7 +241,8 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
}
if (emptyMenu) return;
QAction *act = menu.exec(QCursor::pos());
if (act == 0) return;
if (!act) return;
if (act == addPeerAct) {
QList<BitTorrent::PeerAddress> peersList = PeersAdditionDlg::askForPeers(this);
int peerCount = 0;
@@ -249,7 +250,7 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
if (torrent->connectPeer(addr)) {
qDebug("Adding peer %s...", qUtf8Printable(addr.ip.toString()));
Logger::instance()->addMessage(tr("Manually adding peer '%1'...").arg(addr.ip.toString()));
peerCount++;
++peerCount;
}
else {
Logger::instance()->addMessage(tr("The peer '%1' could not be added to this torrent.").arg(addr.ip.toString()), Log::WARNING);
@@ -277,8 +278,7 @@ void PeerListWidget::banSelectedPeers()
int ret = QMessageBox::question(this, tr("Ban peer permanently"), tr("Are you sure you want to ban permanently the selected peers?"),
tr("&Yes"), tr("&No"),
QString(), 0, 1);
if (ret)
return;
if (ret) return;
QModelIndexList selectedIndexes = selectionModel()->selectedRows();
foreach (const QModelIndex &index, selectedIndexes) {

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,14 +24,12 @@
* 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 "peersadditiondlg.h"
#include <QMessageBox>
#include <QHostAddress>
#include <QMessageBox>
#include "ui_peersadditiondlg.h"

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,8 +24,6 @@
* 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 PEERADDITION_H
@@ -42,7 +40,7 @@ namespace Ui
class addPeersDialog;
}
class PeersAdditionDlg: public QDialog
class PeersAdditionDlg : public QDialog
{
Q_OBJECT
@@ -60,7 +58,6 @@ private:
Ui::addPeersDialog *m_ui;
QList<BitTorrent::PeerAddress> m_peersList;
};
#endif // PEERADDITION_H

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,8 +24,6 @@
* 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 "pieceavailabilitybar.h"
@@ -134,12 +132,12 @@ bool PieceAvailabilityBar::updateImage(QImage &image)
return true;
}
QVector<float> scaled_pieces = intToFloatVector(m_pieces, image2.width());
QVector<float> scaledPieces = intToFloatVector(m_pieces, image2.width());
// filling image
for (int x = 0; x < scaled_pieces.size(); ++x) {
float pieces2_val = scaled_pieces.at(x);
image2.setPixel(x, 0, pieceColors()[pieces2_val * 255]);
for (int x = 0; x < scaledPieces.size(); ++x) {
float piecesToValue = scaledPieces.at(x);
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
}
image = image2;
return true;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,8 +24,6 @@
* 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 PIECEAVAILABILITYBAR_H
@@ -33,7 +31,7 @@
#include "piecesbar.h"
class PieceAvailabilityBar: public PiecesBar
class PieceAvailabilityBar : public PiecesBar
{
using base = PiecesBar;
Q_OBJECT

View File

@@ -41,7 +41,7 @@ namespace BitTorrent
class TorrentHandle;
}
class PiecesBar: public QWidget
class PiecesBar : public QWidget
{
using base = QWidget;
Q_OBJECT

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,8 +24,6 @@
* 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 PROPERTIESWIDGET_H
@@ -36,32 +34,30 @@
#include "base/bittorrent/torrenthandle.h"
class TransferListWidget;
class TorrentContentFilterModel;
class PropListDelegate;
class torrent_file;
class PeerListWidget;
class TrackerList;
class SpeedWidget;
class MainWindow;
class DownloadedPiecesBar;
class PieceAvailabilityBar;
class PropTabBar;
class LineEdit;
QT_BEGIN_NAMESPACE
class QAction;
class QPushButton;
class QTimer;
class QTreeView;
QT_END_NAMESPACE
class DownloadedPiecesBar;
class LineEdit;
class MainWindow;
class PeerListWidget;
class PieceAvailabilityBar;
class PropListDelegate;
class PropTabBar;
class SpeedWidget;
class torrent_file;
class TorrentContentFilterModel;
class TrackerList;
class TransferListWidget;
namespace Ui
{
class PropertiesWidget;
}
class PropertiesWidget: public QWidget
class PropertiesWidget : public QWidget
{
Q_OBJECT
Q_DISABLE_COPY(PropertiesWidget)
@@ -73,13 +69,13 @@ public:
VISIBLE
};
PropertiesWidget(QWidget *parent, MainWindow *main_window, TransferListWidget *transferList);
PropertiesWidget(QWidget *parent, MainWindow *mainWindow, TransferListWidget *transferList);
~PropertiesWidget();
BitTorrent::TorrentHandle *getCurrentTorrent() const;
TrackerList *getTrackerList() const { return trackerList; }
PeerListWidget *getPeerList() const { return peersList; }
TrackerList *getTrackerList() const;
PeerListWidget *getPeerList() const;
QTreeView *getFilesList() const;
SpeedWidget *getSpeedWidget() const { return speedWidget; }
SpeedWidget *getSpeedWidget() const;
public slots:
void setVisibility(bool visible);
@@ -93,7 +89,7 @@ public slots:
protected:
QPushButton *getButtonFromIndex(int index);
bool applyPriorities();
void applyPriorities();
protected slots:
void loadTorrentInfos(BitTorrent::TorrentHandle *const torrent);
@@ -113,28 +109,28 @@ protected slots:
private:
void openFile(const QModelIndex &index);
void openFolder(const QModelIndex &index, bool containing_folder);
void openFolder(const QModelIndex &index, bool containingFolder);
Ui::PropertiesWidget *m_ui;
TransferListWidget *transferList;
MainWindow *main_window;
TransferListWidget *m_transferList;
MainWindow *m_mainWindow;
BitTorrent::TorrentHandle *m_torrent;
QTimer *refreshTimer;
SlideState state;
TorrentContentFilterModel *PropListModel;
PropListDelegate *PropDelegate;
PeerListWidget *peersList;
TrackerList *trackerList;
SpeedWidget *speedWidget;
QList<int> slideSizes;
DownloadedPiecesBar *downloaded_pieces;
PieceAvailabilityBar *pieces_availability;
QTimer *m_refreshTimer;
SlideState m_state;
TorrentContentFilterModel *m_propListModel;
PropListDelegate *m_propListDelegate;
PeerListWidget *m_peerList;
TrackerList *m_trackerList;
SpeedWidget *m_speedWidget;
QList<int> m_slideSizes;
DownloadedPiecesBar *m_downloadedPieces;
PieceAvailabilityBar *m_piecesAvailability;
PropTabBar *m_tabBar;
LineEdit *m_contentFilterLine;
QShortcut *editHotkeyFile;
QShortcut *editHotkeyWeb;
QShortcut *deleteHotkeyWeb;
QShortcut *openHotkeyFile;
QShortcut *m_editHotkeyFile;
QShortcut *m_editHotkeyWeb;
QShortcut *m_deleteHotkeyWeb;
QShortcut *m_openHotkeyFile;
private slots:
void filterText(const QString &filter);

Some files were not shown because too many files have changed in this diff Show More