You've already forked qBittorrent
mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-10-09 18:32:15 +02:00
Compare commits
16 Commits
release-4.
...
release-4.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6486fc5f4d | ||
![]() |
1e059ab1a2 | ||
![]() |
15b137211b | ||
![]() |
6f8f1d7bad | ||
![]() |
a31f0c0a3d | ||
![]() |
f977d1293a | ||
![]() |
1399be50cb | ||
![]() |
52dcf32cc8 | ||
![]() |
52b2b807ab | ||
![]() |
5cf4f00824 | ||
![]() |
faa6fad025 | ||
![]() |
9f94bbce3a | ||
![]() |
5c49b2486c | ||
![]() |
4f6e7f97c6 | ||
![]() |
7751c5b75c | ||
![]() |
a1a9f3317b |
13
Changelog
13
Changelog
@@ -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
17
configure
vendored
@@ -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
2
dist/mac/Info.plist
vendored
@@ -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>
|
||||
|
2
dist/windows/options.nsi
vendored
2
dist/windows/options.nsi
vendored
@@ -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
|
||||
|
2
dist/windows/qt.conf
vendored
2
dist/windows/qt.conf
vendored
@@ -2,4 +2,4 @@
|
||||
Translations = translations
|
||||
|
||||
[Platforms]
|
||||
WindowsArguments = dpiawareness=1
|
||||
;WindowsArguments = dpiawareness=1
|
||||
|
@@ -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
|
||||
|
@@ -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");
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -31,6 +31,7 @@
|
||||
#include "rss_feed.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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())
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -61,7 +61,6 @@ public:
|
||||
COL_COUNT
|
||||
};
|
||||
|
||||
public:
|
||||
PeerListDelegate(QObject *parent) : QItemDelegate(parent) {}
|
||||
|
||||
~PeerListDelegate() {}
|
||||
|
@@ -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));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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
@@ -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
Reference in New Issue
Block a user