You've already forked qBittorrent
mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-10-12 03:12:18 +02:00
Compare commits
34 Commits
release-4.
...
release-4.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6486fc5f4d | ||
![]() |
1e059ab1a2 | ||
![]() |
15b137211b | ||
![]() |
6f8f1d7bad | ||
![]() |
a31f0c0a3d | ||
![]() |
f977d1293a | ||
![]() |
1399be50cb | ||
![]() |
52dcf32cc8 | ||
![]() |
52b2b807ab | ||
![]() |
5cf4f00824 | ||
![]() |
faa6fad025 | ||
![]() |
9f94bbce3a | ||
![]() |
5c49b2486c | ||
![]() |
4f6e7f97c6 | ||
![]() |
7751c5b75c | ||
![]() |
a1a9f3317b | ||
![]() |
fb20f59a96 | ||
![]() |
a15e3407b0 | ||
![]() |
e267c2d37a | ||
![]() |
ae32edeb26 | ||
![]() |
34d38ef466 | ||
![]() |
120ee6b836 | ||
![]() |
7d25b6fce2 | ||
![]() |
068eff9e9f | ||
![]() |
31a55f79f1 | ||
![]() |
bac032e01c | ||
![]() |
b809941f02 | ||
![]() |
77c3758090 | ||
![]() |
5758817189 | ||
![]() |
acc9f08a05 | ||
![]() |
f3b7f17a7c | ||
![]() |
dfc3f047e2 | ||
![]() |
223ab7de84 | ||
![]() |
d2a4027347 |
18
.github/ISSUE_TEMPLATE.md
vendored
18
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,14 +1,20 @@
|
|||||||
**Please provide the following information**
|
**Please provide the following information**
|
||||||
|
|
||||||
### qBittorrent version and Operating System:
|
### qBittorrent version and Operating System
|
||||||
|
(type here)
|
||||||
|
|
||||||
### If on linux, libtorrent and Qt version:
|
### If on linux, libtorrent and Qt version
|
||||||
|
(type here)
|
||||||
|
|
||||||
### What is the problem:
|
### What is the problem
|
||||||
|
(type here)
|
||||||
|
|
||||||
### What is the expected behavior:
|
### What is the expected behavior
|
||||||
|
(type here)
|
||||||
|
|
||||||
### Steps to reproduce:
|
### Steps to reproduce
|
||||||
|
(type here)
|
||||||
|
|
||||||
### Extra info(if any):
|
### Extra info(if any)
|
||||||
|
(type here)
|
||||||
|
|
||||||
|
24
Changelog
24
Changelog
@@ -1,3 +1,27 @@
|
|||||||
|
* 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)
|
||||||
|
- BUGFIX: Allow drag-n-drop magnet links to mainwindow. Closes #7742. (Chocobo1)
|
||||||
|
- BUGFIX: Fix crash when aborting a torrent creation process. Closes #7783. (Chocobo1)
|
||||||
|
- BUGFIX: Correctly check if torrent passed during application start already exists. (sledgehammer999)
|
||||||
|
- WEBUI: Add ip subnet whitelist for bypassing webui auth (Thomas Piccirello)
|
||||||
|
- WEBUI: Fix logo missing in login page (Chocobo1)
|
||||||
|
- COSMETIC: Fix english typo. (sledgehammer999)
|
||||||
|
- OTHER: cmake: qtsingleapplication should always be built statically (luigino)
|
||||||
|
|
||||||
* Mon Nov 20 2017 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.0.0
|
* Mon Nov 20 2017 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.0.0
|
||||||
- FEATURE: Change qbittorrent logo. Issue #6467. (HVS, Atif Afzal, sledgehammer999)
|
- FEATURE: Change qbittorrent logo. Issue #6467. (HVS, Atif Afzal, sledgehammer999)
|
||||||
- FEATURE: New icon theme with SVG source, so we can scale it appropriately in the future. (Bert Verhelst)
|
- FEATURE: New icon theme with SVG source, so we can scale it appropriately in the future. (Bert Verhelst)
|
||||||
|
17
configure
vendored
17
configure
vendored
@@ -690,7 +690,6 @@ infodir
|
|||||||
docdir
|
docdir
|
||||||
oldincludedir
|
oldincludedir
|
||||||
includedir
|
includedir
|
||||||
runstatedir
|
|
||||||
localstatedir
|
localstatedir
|
||||||
sharedstatedir
|
sharedstatedir
|
||||||
sysconfdir
|
sysconfdir
|
||||||
@@ -784,7 +783,6 @@ datadir='${datarootdir}'
|
|||||||
sysconfdir='${prefix}/etc'
|
sysconfdir='${prefix}/etc'
|
||||||
sharedstatedir='${prefix}/com'
|
sharedstatedir='${prefix}/com'
|
||||||
localstatedir='${prefix}/var'
|
localstatedir='${prefix}/var'
|
||||||
runstatedir='${localstatedir}/run'
|
|
||||||
includedir='${prefix}/include'
|
includedir='${prefix}/include'
|
||||||
oldincludedir='/usr/include'
|
oldincludedir='/usr/include'
|
||||||
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
|
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
|
||||||
@@ -1037,15 +1035,6 @@ do
|
|||||||
| -silent | --silent | --silen | --sile | --sil)
|
| -silent | --silent | --silen | --sile | --sil)
|
||||||
silent=yes ;;
|
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)
|
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
|
||||||
ac_prev=sbindir ;;
|
ac_prev=sbindir ;;
|
||||||
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
|
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
|
||||||
@@ -1183,7 +1172,7 @@ fi
|
|||||||
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
|
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
|
||||||
datadir sysconfdir sharedstatedir localstatedir includedir \
|
datadir sysconfdir sharedstatedir localstatedir includedir \
|
||||||
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
|
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
|
||||||
libdir localedir mandir runstatedir
|
libdir localedir mandir
|
||||||
do
|
do
|
||||||
eval ac_val=\$$ac_var
|
eval ac_val=\$$ac_var
|
||||||
# Remove trailing slashes.
|
# Remove trailing slashes.
|
||||||
@@ -1336,7 +1325,6 @@ Fine tuning of the installation directories:
|
|||||||
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
|
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
|
||||||
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
|
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
|
||||||
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
|
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
|
||||||
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
|
|
||||||
--libdir=DIR object code libraries [EPREFIX/lib]
|
--libdir=DIR object code libraries [EPREFIX/lib]
|
||||||
--includedir=DIR C header files [PREFIX/include]
|
--includedir=DIR C header files [PREFIX/include]
|
||||||
--oldincludedir=DIR C header files for non-gcc [/usr/include]
|
--oldincludedir=DIR C header files for non-gcc [/usr/include]
|
||||||
@@ -4705,9 +4693,8 @@ fi
|
|||||||
libsubdirs="lib64 libx32 lib lib64" ;; #(
|
libsubdirs="lib64 libx32 lib lib64" ;; #(
|
||||||
ppc64|s390x|sparc64|aarch64|ppc64le) :
|
ppc64|s390x|sparc64|aarch64|ppc64le) :
|
||||||
libsubdirs="lib64 lib lib64" ;; #(
|
libsubdirs="lib64 lib lib64" ;; #(
|
||||||
libsubdirs="lib") :
|
|
||||||
;; #(
|
|
||||||
*) :
|
*) :
|
||||||
|
libsubdirs="lib"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -45,7 +45,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>4.0.0</string>
|
<string>4.0.2</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>qBit</string>
|
<string>qBit</string>
|
||||||
<key>CFBundleExecutable</key>
|
<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
|
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||||
|
|
||||||
; Program specific
|
; Program specific
|
||||||
!define PROG_VERSION "4.0.0"
|
!define PROG_VERSION "4.0.2"
|
||||||
|
|
||||||
!define MUI_FINISHPAGE_RUN
|
!define MUI_FINISHPAGE_RUN
|
||||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||||
|
2
dist/windows/qt.conf
vendored
2
dist/windows/qt.conf
vendored
@@ -2,4 +2,4 @@
|
|||||||
Translations = translations
|
Translations = translations
|
||||||
|
|
||||||
[Platforms]
|
[Platforms]
|
||||||
WindowsArguments = dpiawareness=1
|
;WindowsArguments = dpiawareness=1
|
||||||
|
@@ -114,7 +114,7 @@ AC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[
|
|||||||
AS_CASE([${host_cpu}],
|
AS_CASE([${host_cpu}],
|
||||||
[x86_64],[libsubdirs="lib64 libx32 lib lib64"],
|
[x86_64],[libsubdirs="lib64 libx32 lib lib64"],
|
||||||
[ppc64|s390x|sparc64|aarch64|ppc64le],[libsubdirs="lib64 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
|
dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give
|
||||||
|
@@ -105,6 +105,9 @@ Application::Application(const QString &id, int &argc, char **argv)
|
|||||||
, m_running(false)
|
, m_running(false)
|
||||||
, m_shutdownAct(ShutdownDialogAction::Exit)
|
, m_shutdownAct(ShutdownDialogAction::Exit)
|
||||||
, m_commandLineArgs(parseCommandLine(this->arguments()))
|
, m_commandLineArgs(parseCommandLine(this->arguments()))
|
||||||
|
#ifndef DISABLE_WEBUI
|
||||||
|
, m_webui(nullptr)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
qRegisterMetaType<Log::Msg>("Log::Msg");
|
qRegisterMetaType<Log::Msg>("Log::Msg");
|
||||||
|
|
||||||
@@ -518,15 +521,15 @@ int Application::exec(const QStringList ¶ms)
|
|||||||
#endif // DISABLE_GUI
|
#endif // DISABLE_GUI
|
||||||
|
|
||||||
m_running = true;
|
m_running = true;
|
||||||
|
|
||||||
|
// Now UI is ready to process signals from Session
|
||||||
|
BitTorrent::Session::instance()->startUpTorrents();
|
||||||
|
|
||||||
m_paramsQueue = params + m_paramsQueue;
|
m_paramsQueue = params + m_paramsQueue;
|
||||||
if (!m_paramsQueue.isEmpty()) {
|
if (!m_paramsQueue.isEmpty()) {
|
||||||
processParams(m_paramsQueue);
|
processParams(m_paramsQueue);
|
||||||
m_paramsQueue.clear();
|
m_paramsQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now UI is ready to process signals from Session
|
|
||||||
BitTorrent::Session::instance()->startUpTorrents();
|
|
||||||
|
|
||||||
return BaseApplication::exec();
|
return BaseApplication::exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,15 +602,15 @@ void Application::initializeTranslation()
|
|||||||
// Load translation
|
// Load translation
|
||||||
QString localeStr = pref->getLocale();
|
QString localeStr = pref->getLocale();
|
||||||
|
|
||||||
if (m_qtTranslator.load(QString::fromUtf8("qtbase_") + localeStr, QLibraryInfo::location(QLibraryInfo::TranslationsPath)) ||
|
if (m_qtTranslator.load(QLatin1String("qtbase_") + localeStr, QLibraryInfo::location(QLibraryInfo::TranslationsPath)) ||
|
||||||
m_qtTranslator.load(QString::fromUtf8("qt_") + localeStr, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
|
m_qtTranslator.load(QLatin1String("qt_") + localeStr, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
|
||||||
qDebug("Qt %s locale recognized, using translation.", qUtf8Printable(localeStr));
|
qDebug("Qt %s locale recognized, using translation.", qUtf8Printable(localeStr));
|
||||||
else
|
else
|
||||||
qDebug("Qt %s locale unrecognized, using default (en).", qUtf8Printable(localeStr));
|
qDebug("Qt %s locale unrecognized, using default (en).", qUtf8Printable(localeStr));
|
||||||
|
|
||||||
installTranslator(&m_qtTranslator);
|
installTranslator(&m_qtTranslator);
|
||||||
|
|
||||||
if (m_translator.load(QString::fromUtf8(":/lang/qbittorrent_") + localeStr))
|
if (m_translator.load(QLatin1String(":/lang/qbittorrent_") + localeStr))
|
||||||
qDebug("%s locale recognized, using translation.", qUtf8Printable(localeStr));
|
qDebug("%s locale recognized, using translation.", qUtf8Printable(localeStr));
|
||||||
else
|
else
|
||||||
qDebug("%s locale unrecognized, using default (en).", qUtf8Printable(localeStr));
|
qDebug("%s locale unrecognized, using default (en).", qUtf8Printable(localeStr));
|
||||||
|
@@ -16,7 +16,7 @@ else (GUI)
|
|||||||
list(APPEND QBT_QTSINGLEAPPLICATION_SOURCES qtsinglecoreapplication.cpp)
|
list(APPEND QBT_QTSINGLEAPPLICATION_SOURCES qtsinglecoreapplication.cpp)
|
||||||
endif (GUI)
|
endif (GUI)
|
||||||
|
|
||||||
add_library(qtsingleapplication ${QBT_QTSINGLEAPPLICATION_HEADERS} ${QBT_QTSINGLEAPPLICATION_SOURCES})
|
add_library(qtsingleapplication STATIC ${QBT_QTSINGLEAPPLICATION_HEADERS} ${QBT_QTSINGLEAPPLICATION_SOURCES})
|
||||||
target_include_directories(qtsingleapplication INTERFACE "${qtsingleapplication_SOURCE_DIR}")
|
target_include_directories(qtsingleapplication INTERFACE "${qtsingleapplication_SOURCE_DIR}")
|
||||||
target_link_qt_components(qtsingleapplication Network)
|
target_link_qt_components(qtsingleapplication Network)
|
||||||
|
|
||||||
|
@@ -102,6 +102,7 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int BUFFER_SIZE = 2 * 1024 * 1024; // 2 MiB
|
const int BUFFER_SIZE = 2 * 1024 * 1024; // 2 MiB
|
||||||
|
const int MAX_LOGGED_ERRORS = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterParserThread::FilterParserThread(QObject *parent)
|
FilterParserThread::FilterParserThread(QObject *parent)
|
||||||
@@ -134,6 +135,12 @@ int FilterParserThread::parseDATFilterFile()
|
|||||||
int start = 0;
|
int start = 0;
|
||||||
int endOfLine = -1;
|
int endOfLine = -1;
|
||||||
int nbLine = 0;
|
int nbLine = 0;
|
||||||
|
int parseErrorCount = 0;
|
||||||
|
const auto addLog = [&parseErrorCount](const QString &msg)
|
||||||
|
{
|
||||||
|
if (parseErrorCount <= MAX_LOGGED_ERRORS)
|
||||||
|
LogMsg(msg, Log::CRITICAL);
|
||||||
|
};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
|
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 endOfIPRange = ((firstComma == -1) ? (endOfLine - 1) : (firstComma - 1));
|
||||||
int delimIP = findAndNullDelimiter(buffer.data(), '-', start, endOfIPRange);
|
int delimIP = findAndNullDelimiter(buffer.data(), '-', start, endOfIPRange);
|
||||||
if (delimIP == -1) {
|
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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -210,7 +218,8 @@ int FilterParserThread::parseDATFilterFile()
|
|||||||
libt::address startAddr;
|
libt::address startAddr;
|
||||||
int newStart = trim(buffer.data(), start, delimIP - 1);
|
int newStart = trim(buffer.data(), start, delimIP - 1);
|
||||||
if (!parseIPAddress(buffer.data() + newStart, startAddr)) {
|
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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -218,14 +227,16 @@ int FilterParserThread::parseDATFilterFile()
|
|||||||
libt::address endAddr;
|
libt::address endAddr;
|
||||||
newStart = trim(buffer.data(), delimIP + 1, endOfIPRange);
|
newStart = trim(buffer.data(), delimIP + 1, endOfIPRange);
|
||||||
if (!parseIPAddress(buffer.data() + newStart, endAddr)) {
|
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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((startAddr.is_v4() != endAddr.is_v4())
|
if ((startAddr.is_v4() != endAddr.is_v4())
|
||||||
|| (startAddr.is_v6() != endAddr.is_v6())) {
|
|| (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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -238,8 +249,9 @@ int FilterParserThread::parseDATFilterFile()
|
|||||||
++ruleCount;
|
++ruleCount;
|
||||||
}
|
}
|
||||||
catch (std::exception &e) {
|
catch (std::exception &e) {
|
||||||
LogMsg(tr("IP filter exception thrown for line %1. Exception is: %2").arg(nbLine)
|
++parseErrorCount;
|
||||||
.arg(QString::fromLocal8Bit(e.what())), Log::CRITICAL);
|
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;
|
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;
|
return ruleCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,6 +283,12 @@ int FilterParserThread::parseP2PFilterFile()
|
|||||||
int start = 0;
|
int start = 0;
|
||||||
int endOfLine = -1;
|
int endOfLine = -1;
|
||||||
int nbLine = 0;
|
int nbLine = 0;
|
||||||
|
int parseErrorCount = 0;
|
||||||
|
const auto addLog = [&parseErrorCount](const QString &msg)
|
||||||
|
{
|
||||||
|
if (parseErrorCount <= MAX_LOGGED_ERRORS)
|
||||||
|
LogMsg(msg, Log::CRITICAL);
|
||||||
|
};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
|
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
|
// The "Some organization" part might contain a ':' char itself so we find the last occurrence
|
||||||
int partsDelimiter = findAndNullDelimiter(buffer.data(), ':', start, endOfLine, true);
|
int partsDelimiter = findAndNullDelimiter(buffer.data(), ':', start, endOfLine, true);
|
||||||
if (partsDelimiter == -1) {
|
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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -327,7 +349,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||||||
// IP Range should be split by a dash
|
// IP Range should be split by a dash
|
||||||
int delimIP = findAndNullDelimiter(buffer.data(), '-', partsDelimiter + 1, endOfLine);
|
int delimIP = findAndNullDelimiter(buffer.data(), '-', partsDelimiter + 1, endOfLine);
|
||||||
if (delimIP == -1) {
|
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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -335,7 +358,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||||||
libt::address startAddr;
|
libt::address startAddr;
|
||||||
int newStart = trim(buffer.data(), partsDelimiter + 1, delimIP - 1);
|
int newStart = trim(buffer.data(), partsDelimiter + 1, delimIP - 1);
|
||||||
if (!parseIPAddress(buffer.data() + newStart, startAddr)) {
|
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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -343,14 +367,16 @@ int FilterParserThread::parseP2PFilterFile()
|
|||||||
libt::address endAddr;
|
libt::address endAddr;
|
||||||
newStart = trim(buffer.data(), delimIP + 1, endOfLine);
|
newStart = trim(buffer.data(), delimIP + 1, endOfLine);
|
||||||
if (!parseIPAddress(buffer.data() + newStart, endAddr)) {
|
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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((startAddr.is_v4() != endAddr.is_v4())
|
if ((startAddr.is_v4() != endAddr.is_v4())
|
||||||
|| (startAddr.is_v6() != endAddr.is_v6())) {
|
|| (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;
|
start = endOfLine;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -362,8 +388,9 @@ int FilterParserThread::parseP2PFilterFile()
|
|||||||
++ruleCount;
|
++ruleCount;
|
||||||
}
|
}
|
||||||
catch (std::exception &e) {
|
catch (std::exception &e) {
|
||||||
LogMsg(tr("IP filter exception thrown for line %1. Exception is: %2").arg(nbLine)
|
++parseErrorCount;
|
||||||
.arg(QString::fromLocal8Bit(e.what())), Log::CRITICAL);
|
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;
|
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;
|
return ruleCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -304,7 +304,7 @@ Session::Session(QObject *parent)
|
|||||||
, m_btProtocol(BITTORRENT_SESSION_KEY("BTProtocol"), BTProtocol::Both
|
, m_btProtocol(BITTORRENT_SESSION_KEY("BTProtocol"), BTProtocol::Both
|
||||||
, clampValue(BTProtocol::Both, BTProtocol::UTP))
|
, clampValue(BTProtocol::Both, BTProtocol::UTP))
|
||||||
, m_isUTPRateLimited(BITTORRENT_SESSION_KEY("uTPRateLimited"), true)
|
, 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))
|
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
|
||||||
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
|
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
|
||||||
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
|
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
|
||||||
@@ -1418,8 +1418,11 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
|
|||||||
void Session::configurePeerClasses()
|
void Session::configurePeerClasses()
|
||||||
{
|
{
|
||||||
libt::ip_filter f;
|
libt::ip_filter f;
|
||||||
f.add_rule(libt::address_v4::from_string("0.0.0.0")
|
// address_v4::from_string("255.255.255.255") crashes on some people's systems
|
||||||
, libt::address_v4::from_string("255.255.255.255")
|
// 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);
|
, 1 << libt::session::global_peer_class_id);
|
||||||
#if TORRENT_USE_IPV6
|
#if TORRENT_USE_IPV6
|
||||||
// IPv6 may not be available on OS and the parsing
|
// IPv6 may not be available on OS and the parsing
|
||||||
|
@@ -35,8 +35,8 @@
|
|||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
#include <libtorrent/bencode.hpp>
|
#include <libtorrent/bencode.hpp>
|
||||||
#include <libtorrent/create_torrent.hpp>
|
#include <libtorrent/create_torrent.hpp>
|
||||||
#include <libtorrent/torrent_info.hpp>
|
|
||||||
#include <libtorrent/storage.hpp>
|
#include <libtorrent/storage.hpp>
|
||||||
|
#include <libtorrent/torrent_info.hpp>
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
@@ -44,16 +44,19 @@
|
|||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// do not include files and folders whose
|
||||||
|
// name starts with a .
|
||||||
|
bool fileFilter(const std::string &f)
|
||||||
|
{
|
||||||
|
return !Utils::Fs::fileName(QString::fromStdString(f)).startsWith('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace libt = libtorrent;
|
namespace libt = libtorrent;
|
||||||
using namespace BitTorrent;
|
using namespace BitTorrent;
|
||||||
|
|
||||||
// do not include files and folders whose
|
|
||||||
// name starts with a .
|
|
||||||
bool fileFilter(const std::string &f)
|
|
||||||
{
|
|
||||||
return !Utils::Fs::fileName(QString::fromStdString(f)).startsWith('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
TorrentCreatorThread::TorrentCreatorThread(QObject *parent)
|
TorrentCreatorThread::TorrentCreatorThread(QObject *parent)
|
||||||
: QThread(parent)
|
: QThread(parent)
|
||||||
, m_private(false)
|
, m_private(false)
|
||||||
@@ -64,7 +67,7 @@ TorrentCreatorThread::TorrentCreatorThread(QObject *parent)
|
|||||||
TorrentCreatorThread::~TorrentCreatorThread()
|
TorrentCreatorThread::~TorrentCreatorThread()
|
||||||
{
|
{
|
||||||
requestInterruption();
|
requestInterruption();
|
||||||
wait(1000);
|
wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentCreatorThread::create(const QString &inputPath, const QString &savePath, const QStringList &trackers,
|
void TorrentCreatorThread::create(const QString &inputPath, const QString &savePath, const QStringList &trackers,
|
||||||
|
@@ -30,6 +30,8 @@
|
|||||||
* Contact : hammered999@gmail.com
|
* Contact : hammered999@gmail.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "preferences.h"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
@@ -51,11 +53,10 @@
|
|||||||
#include <CoreServices/CoreServices.h>
|
#include <CoreServices/CoreServices.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "settingsstorage.h"
|
||||||
#include "utils/fs.h"
|
#include "utils/fs.h"
|
||||||
#include "utils/misc.h"
|
#include "utils/misc.h"
|
||||||
#include "settingsstorage.h"
|
|
||||||
#include "logger.h"
|
|
||||||
#include "preferences.h"
|
|
||||||
|
|
||||||
Preferences *Preferences::m_instance = 0;
|
Preferences *Preferences::m_instance = 0;
|
||||||
|
|
||||||
@@ -463,6 +464,38 @@ void Preferences::setWebUiLocalAuthEnabled(bool enabled)
|
|||||||
setValue("Preferences/WebUI/LocalHostAuth", enabled);
|
setValue("Preferences/WebUI/LocalHostAuth", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Preferences::isWebUiAuthSubnetWhitelistEnabled() const
|
||||||
|
{
|
||||||
|
return value("Preferences/WebUI/AuthSubnetWhitelistEnabled", false).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setWebUiAuthSubnetWhitelistEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
setValue("Preferences/WebUI/AuthSubnetWhitelistEnabled", enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
|
||||||
|
{
|
||||||
|
QList<Utils::Net::Subnet> subnets;
|
||||||
|
foreach (const QString &rawSubnet, value("Preferences/WebUI/AuthSubnetWhitelist").toStringList()) {
|
||||||
|
bool ok = false;
|
||||||
|
const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(rawSubnet.trimmed(), &ok);
|
||||||
|
if (ok)
|
||||||
|
subnets.append(subnet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return subnets;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setWebUiAuthSubnetWhitelist(const QList<Utils::Net::Subnet> &subnets)
|
||||||
|
{
|
||||||
|
QStringList subnetsStringList;
|
||||||
|
for (const Utils::Net::Subnet &subnet : subnets)
|
||||||
|
subnetsStringList.append(Utils::Net::subnetToString(subnet));
|
||||||
|
|
||||||
|
setValue("Preferences/WebUI/AuthSubnetWhitelist", subnetsStringList);
|
||||||
|
}
|
||||||
|
|
||||||
QString Preferences::getServerDomains() const
|
QString Preferences::getServerDomains() const
|
||||||
{
|
{
|
||||||
return value("Preferences/WebUI/ServerDomains", "*").toString();
|
return value("Preferences/WebUI/ServerDomains", "*").toString();
|
||||||
|
@@ -33,15 +33,18 @@
|
|||||||
#ifndef PREFERENCES_H
|
#ifndef PREFERENCES_H
|
||||||
#define PREFERENCES_H
|
#define PREFERENCES_H
|
||||||
|
|
||||||
#include <QTime>
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QHostAddress>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QSize>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QReadWriteLock>
|
|
||||||
#include <QNetworkCookie>
|
#include <QNetworkCookie>
|
||||||
|
#include <QReadWriteLock>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QTime>
|
||||||
|
#include <QTimer>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include "base/utils/net.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
enum scheduler_days
|
enum scheduler_days
|
||||||
@@ -170,10 +173,9 @@ public:
|
|||||||
bool isSearchEnabled() const;
|
bool isSearchEnabled() const;
|
||||||
void setSearchEnabled(bool enabled);
|
void setSearchEnabled(bool enabled);
|
||||||
|
|
||||||
|
// HTTP Server
|
||||||
bool isWebUiEnabled() const;
|
bool isWebUiEnabled() const;
|
||||||
void setWebUiEnabled(bool enabled);
|
void setWebUiEnabled(bool enabled);
|
||||||
bool isWebUiLocalAuthEnabled() const;
|
|
||||||
void setWebUiLocalAuthEnabled(bool enabled);
|
|
||||||
QString getServerDomains() const;
|
QString getServerDomains() const;
|
||||||
void setServerDomains(const QString &str);
|
void setServerDomains(const QString &str);
|
||||||
QString getWebUiAddress() const;
|
QString getWebUiAddress() const;
|
||||||
@@ -182,16 +184,28 @@ public:
|
|||||||
void setWebUiPort(quint16 port);
|
void setWebUiPort(quint16 port);
|
||||||
bool useUPnPForWebUIPort() const;
|
bool useUPnPForWebUIPort() const;
|
||||||
void setUPnPForWebUIPort(bool enabled);
|
void setUPnPForWebUIPort(bool enabled);
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
bool isWebUiLocalAuthEnabled() const;
|
||||||
|
void setWebUiLocalAuthEnabled(bool enabled);
|
||||||
|
bool isWebUiAuthSubnetWhitelistEnabled() const;
|
||||||
|
void setWebUiAuthSubnetWhitelistEnabled(bool enabled);
|
||||||
|
QList<Utils::Net::Subnet> getWebUiAuthSubnetWhitelist() const;
|
||||||
|
void setWebUiAuthSubnetWhitelist(const QList<Utils::Net::Subnet> &subnets);
|
||||||
QString getWebUiUsername() const;
|
QString getWebUiUsername() const;
|
||||||
void setWebUiUsername(const QString &username);
|
void setWebUiUsername(const QString &username);
|
||||||
QString getWebUiPassword() const;
|
QString getWebUiPassword() const;
|
||||||
void setWebUiPassword(const QString &new_password);
|
void setWebUiPassword(const QString &new_password);
|
||||||
|
|
||||||
|
// HTTPS
|
||||||
bool isWebUiHttpsEnabled() const;
|
bool isWebUiHttpsEnabled() const;
|
||||||
void setWebUiHttpsEnabled(bool enabled);
|
void setWebUiHttpsEnabled(bool enabled);
|
||||||
QByteArray getWebUiHttpsCertificate() const;
|
QByteArray getWebUiHttpsCertificate() const;
|
||||||
void setWebUiHttpsCertificate(const QByteArray &data);
|
void setWebUiHttpsCertificate(const QByteArray &data);
|
||||||
QByteArray getWebUiHttpsKey() const;
|
QByteArray getWebUiHttpsKey() const;
|
||||||
void setWebUiHttpsKey(const QByteArray &data);
|
void setWebUiHttpsKey(const QByteArray &data);
|
||||||
|
|
||||||
|
// Dynamic DNS
|
||||||
bool isDynDNSEnabled() const;
|
bool isDynDNSEnabled() const;
|
||||||
void setDynDNSEnabled(bool enabled);
|
void setDynDNSEnabled(bool enabled);
|
||||||
DNS::Service getDynDNSService() const;
|
DNS::Service getDynDNSService() const;
|
||||||
|
@@ -227,10 +227,9 @@ void Parser::parse(const QByteArray &feedData)
|
|||||||
// read and create items from a rss document
|
// read and create items from a rss document
|
||||||
void Parser::parse_impl(const QByteArray &feedData)
|
void Parser::parse_impl(const QByteArray &feedData)
|
||||||
{
|
{
|
||||||
qDebug() << Q_FUNC_INFO;
|
|
||||||
|
|
||||||
QXmlStreamReader xml(feedData);
|
QXmlStreamReader xml(feedData);
|
||||||
bool foundChannel = false;
|
bool foundChannel = false;
|
||||||
|
|
||||||
while (xml.readNextStartElement()) {
|
while (xml.readNextStartElement()) {
|
||||||
if (xml.name() == "rss") {
|
if (xml.name() == "rss") {
|
||||||
// Find channels
|
// Find channels
|
||||||
@@ -258,11 +257,15 @@ void Parser::parse_impl(const QByteArray &feedData)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xml.hasError())
|
if (!foundChannel) {
|
||||||
m_result.error = xml.errorString();
|
|
||||||
else if (!foundChannel)
|
|
||||||
m_result.error = tr("Invalid RSS feed.");
|
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
|
// Sort article list chronologically
|
||||||
// NOTE: We don't need to sort it here if articles are always
|
// NOTE: We don't need to sort it here if articles are always
|
||||||
// sorted in fetched XML in reverse chronological order
|
// 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();
|
return a1["date"].toDateTime() < a2["date"].toDateTime();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
emit finished(m_result);
|
emit finished(m_result);
|
||||||
m_result.articles.clear(); // clear articles only
|
m_result.articles.clear(); // clear articles only
|
||||||
@@ -288,35 +292,34 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (xml.isStartElement()) {
|
if (xml.isStartElement()) {
|
||||||
const QString text(xml.readElementText().trimmed());
|
|
||||||
|
|
||||||
if (name == QLatin1String("title")) {
|
if (name == QLatin1String("title")) {
|
||||||
article[Article::KeyTitle] = text;
|
article[Article::KeyTitle] = xml.readElementText().trimmed();
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("enclosure")) {
|
else if (name == QLatin1String("enclosure")) {
|
||||||
if (xml.attributes().value("type") == QLatin1String("application/x-bittorrent"))
|
if (xml.attributes().value("type") == QLatin1String("application/x-bittorrent"))
|
||||||
article[Article::KeyTorrentURL] = xml.attributes().value(QLatin1String("url")).toString();
|
article[Article::KeyTorrentURL] = xml.attributes().value(QLatin1String("url")).toString();
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("link")) {
|
else if (name == QLatin1String("link")) {
|
||||||
|
const QString text {xml.readElementText().trimmed()};
|
||||||
if (text.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive))
|
if (text.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive))
|
||||||
article[Article::KeyTorrentURL] = text; // magnet link instead of a news URL
|
article[Article::KeyTorrentURL] = text; // magnet link instead of a news URL
|
||||||
else
|
else
|
||||||
article[Article::KeyLink] = text;
|
article[Article::KeyLink] = text;
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("description")) {
|
else if (name == QLatin1String("description")) {
|
||||||
article[Article::KeyDescription] = text;
|
article[Article::KeyDescription] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("pubDate")) {
|
else if (name == QLatin1String("pubDate")) {
|
||||||
article[Article::KeyDate] = parseDate(text);
|
article[Article::KeyDate] = parseDate(xml.readElementText().trimmed());
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("author")) {
|
else if (name == QLatin1String("author")) {
|
||||||
article[Article::KeyAuthor] = text;
|
article[Article::KeyAuthor] = xml.readElementText().trimmed();
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("guid")) {
|
else if (name == QLatin1String("guid")) {
|
||||||
article[Article::KeyId] = text;
|
article[Article::KeyId] = xml.readElementText().trimmed();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
article[name] = text;
|
article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,17 +329,14 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
|
|||||||
|
|
||||||
void Parser::parseRSSChannel(QXmlStreamReader &xml)
|
void Parser::parseRSSChannel(QXmlStreamReader &xml)
|
||||||
{
|
{
|
||||||
qDebug() << Q_FUNC_INFO;
|
|
||||||
Q_ASSERT(xml.isStartElement() && xml.name() == "channel");
|
|
||||||
|
|
||||||
while (!xml.atEnd()) {
|
while (!xml.atEnd()) {
|
||||||
xml.readNext();
|
xml.readNext();
|
||||||
|
|
||||||
if (xml.isStartElement()) {
|
if (xml.isStartElement()) {
|
||||||
if (xml.name() == "title") {
|
if (xml.name() == QLatin1String("title")) {
|
||||||
m_result.title = xml.readElementText();
|
m_result.title = xml.readElementText();
|
||||||
}
|
}
|
||||||
else if (xml.name() == "lastBuildDate") {
|
else if (xml.name() == QLatin1String("lastBuildDate")) {
|
||||||
QString lastBuildDate = xml.readElementText();
|
QString lastBuildDate = xml.readElementText();
|
||||||
if (!lastBuildDate.isEmpty()) {
|
if (!lastBuildDate.isEmpty()) {
|
||||||
if (m_result.lastBuildDate == lastBuildDate) {
|
if (m_result.lastBuildDate == lastBuildDate) {
|
||||||
@@ -346,7 +346,7 @@ void Parser::parseRSSChannel(QXmlStreamReader &xml)
|
|||||||
m_result.lastBuildDate = lastBuildDate;
|
m_result.lastBuildDate = lastBuildDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (xml.name() == "item") {
|
else if (xml.name() == QLatin1String("item")) {
|
||||||
parseRssArticle(xml);
|
parseRssArticle(xml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,14 +366,12 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (xml.isStartElement()) {
|
if (xml.isStartElement()) {
|
||||||
const QString text(xml.readElementText().trimmed());
|
|
||||||
|
|
||||||
if (name == QLatin1String("title")) {
|
if (name == QLatin1String("title")) {
|
||||||
article[Article::KeyTitle] = text;
|
article[Article::KeyTitle] = xml.readElementText().trimmed();
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("link")) {
|
else if (name == QLatin1String("link")) {
|
||||||
QString link = (xml.attributes().isEmpty()
|
QString link = (xml.attributes().isEmpty()
|
||||||
? text
|
? xml.readElementText().trimmed()
|
||||||
: xml.attributes().value(QLatin1String("href")).toString());
|
: xml.attributes().value(QLatin1String("href")).toString());
|
||||||
|
|
||||||
if (link.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive))
|
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);
|
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
|
if (doubleContent) { // Duplicate content -> ignore
|
||||||
xml.readNext();
|
xml.skipCurrentElement();
|
||||||
|
|
||||||
while ((xml.name() != QLatin1String("summary")) && (xml.name() != QLatin1String("content")))
|
|
||||||
xml.readNext();
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to also parse broken articles, which don't use html '&' escapes
|
// Try to also parse broken articles, which don't use html '&' escapes
|
||||||
// Actually works great for non-broken content too
|
// Actually works great for non-broken content too
|
||||||
QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
|
||||||
if (!feedText.isEmpty())
|
if (!feedText.isEmpty()) {
|
||||||
article[Article::KeyDescription] = feedText.trimmed();
|
article[Article::KeyDescription] = feedText;
|
||||||
|
doubleContent = true;
|
||||||
doubleContent = true;
|
}
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("updated")) {
|
else if (name == QLatin1String("updated")) {
|
||||||
// ATOM uses standard compliant date, don't do fancy stuff
|
// 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());
|
article[Article::KeyDate] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("author")) {
|
else if (name == QLatin1String("author")) {
|
||||||
xml.readNext();
|
while (xml.readNextStartElement()) {
|
||||||
while (xml.name() != QLatin1String("author")) {
|
|
||||||
if (xml.name() == QLatin1String("name"))
|
if (xml.name() == QLatin1String("name"))
|
||||||
article[Article::KeyAuthor] = xml.readElementText().trimmed();
|
article[Article::KeyAuthor] = xml.readElementText().trimmed();
|
||||||
xml.readNext();
|
else
|
||||||
|
xml.skipCurrentElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (name == QLatin1String("id")) {
|
else if (name == QLatin1String("id")) {
|
||||||
article[Article::KeyId] = text;
|
article[Article::KeyId] = xml.readElementText().trimmed();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
article[name] = text;
|
article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,19 +424,16 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
|
|||||||
|
|
||||||
void Parser::parseAtomChannel(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();
|
m_baseUrl = xml.attributes().value("xml:base").toString();
|
||||||
|
|
||||||
while (!xml.atEnd()) {
|
while (!xml.atEnd()) {
|
||||||
xml.readNext();
|
xml.readNext();
|
||||||
|
|
||||||
if (xml.isStartElement()) {
|
if (xml.isStartElement()) {
|
||||||
if (xml.name() == "title") {
|
if (xml.name() == QLatin1String("title")) {
|
||||||
m_result.title = xml.readElementText();
|
m_result.title = xml.readElementText();
|
||||||
}
|
}
|
||||||
else if (xml.name() == "updated") {
|
else if (xml.name() == QLatin1String("updated")) {
|
||||||
QString lastBuildDate = xml.readElementText();
|
QString lastBuildDate = xml.readElementText();
|
||||||
if (!lastBuildDate.isEmpty()) {
|
if (!lastBuildDate.isEmpty()) {
|
||||||
if (m_result.lastBuildDate == lastBuildDate) {
|
if (m_result.lastBuildDate == lastBuildDate) {
|
||||||
@@ -452,7 +443,7 @@ void Parser::parseAtomChannel(QXmlStreamReader &xml)
|
|||||||
m_result.lastBuildDate = lastBuildDate;
|
m_result.lastBuildDate = lastBuildDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (xml.name() == "entry") {
|
else if (xml.name() == QLatin1String("entry")) {
|
||||||
parseAtomArticle(xml);
|
parseAtomArticle(xml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "rss_autodownloader.h"
|
#include "rss_autodownloader.h"
|
||||||
|
|
||||||
|
#include <QDataStream>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
#include "../bittorrent/magneturi.h"
|
#include "../bittorrent/magneturi.h"
|
||||||
#include "../bittorrent/session.h"
|
#include "../bittorrent/session.h"
|
||||||
@@ -63,6 +65,32 @@ const QString RulesFileName(QStringLiteral("download_rules.json"));
|
|||||||
|
|
||||||
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
|
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;
|
using namespace RSS;
|
||||||
|
|
||||||
QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr;
|
QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr;
|
||||||
@@ -84,8 +112,8 @@ AutoDownloader::AutoDownloader()
|
|||||||
connect(m_ioThread, &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater);
|
connect(m_ioThread, &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater);
|
||||||
connect(m_fileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
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")
|
LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
|
||||||
.arg(fileName).arg(errorString), Log::WARNING);
|
.arg(fileName).arg(errorString), Log::CRITICAL);
|
||||||
});
|
});
|
||||||
|
|
||||||
m_ioThread->start();
|
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()
|
void AutoDownloader::process()
|
||||||
{
|
{
|
||||||
if (m_processingQueue.isEmpty()) return; // processing was disabled
|
if (m_processingQueue.isEmpty()) return; // processing was disabled
|
||||||
@@ -276,39 +368,20 @@ void AutoDownloader::load()
|
|||||||
else if (rulesFile.open(QFile::ReadOnly))
|
else if (rulesFile.open(QFile::ReadOnly))
|
||||||
loadRules(rulesFile.readAll());
|
loadRules(rulesFile.readAll());
|
||||||
else
|
else
|
||||||
Logger::instance()->addMessage(
|
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
|
||||||
QString("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
|
.arg(rulesFile.fileName()).arg(rulesFile.errorString()), Log::CRITICAL);
|
||||||
.arg(rulesFile.fileName()).arg(rulesFile.errorString()), Log::WARNING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoDownloader::loadRules(const QByteArray &data)
|
void AutoDownloader::loadRules(const QByteArray &data)
|
||||||
{
|
{
|
||||||
QJsonParseError jsonError;
|
try {
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
|
const auto rules = rulesFromJSON(data);
|
||||||
if (jsonError.error != QJsonParseError::NoError) {
|
for (const auto &rule : rules)
|
||||||
Logger::instance()->addMessage(
|
setRule_impl(rule);
|
||||||
QString("Couldn't parse RSS AutoDownloader rules. Error: %1")
|
|
||||||
.arg(jsonError.errorString()), Log::WARNING);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
catch (const ParsingError &error) {
|
||||||
if (!jsonDoc.isObject()) {
|
LogMsg(tr("Couldn't load RSS AutoDownloader rules. Reason: %1")
|
||||||
Logger::instance()->addMessage(
|
.arg(error.message()), Log::CRITICAL);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +390,7 @@ void AutoDownloader::loadRulesLegacy()
|
|||||||
SettingsPtr settings = Profile::instance().applicationSettings(QStringLiteral("qBittorrent-rss"));
|
SettingsPtr settings = Profile::instance().applicationSettings(QStringLiteral("qBittorrent-rss"));
|
||||||
QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash();
|
QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash();
|
||||||
foreach (const QVariant &ruleVar, rules) {
|
foreach (const QVariant &ruleVar, rules) {
|
||||||
auto rule = AutoDownloadRule::fromVariantHash(ruleVar.toHash());
|
auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash());
|
||||||
if (!rule.name().isEmpty())
|
if (!rule.name().isEmpty())
|
||||||
insertRule(rule);
|
insertRule(rule);
|
||||||
}
|
}
|
||||||
@@ -385,3 +458,13 @@ void AutoDownloader::timerEvent(QTimerEvent *event)
|
|||||||
Q_UNUSED(event);
|
Q_UNUSED(event);
|
||||||
store();
|
store();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ParsingError::ParsingError(const QString &message)
|
||||||
|
: std::runtime_error(message.toUtf8().data())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ParsingError::message() const
|
||||||
|
{
|
||||||
|
return what();
|
||||||
|
}
|
||||||
|
@@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <QBasicTimer>
|
#include <QBasicTimer>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
@@ -49,6 +51,13 @@ namespace RSS
|
|||||||
|
|
||||||
class AutoDownloadRule;
|
class AutoDownloadRule;
|
||||||
|
|
||||||
|
class ParsingError : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ParsingError(const QString &message);
|
||||||
|
QString message() const;
|
||||||
|
};
|
||||||
|
|
||||||
class AutoDownloader final: public QObject
|
class AutoDownloader final: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -60,6 +69,12 @@ namespace RSS
|
|||||||
~AutoDownloader() override;
|
~AutoDownloader() override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum class RulesFileFormat
|
||||||
|
{
|
||||||
|
Legacy,
|
||||||
|
JSON
|
||||||
|
};
|
||||||
|
|
||||||
static AutoDownloader *instance();
|
static AutoDownloader *instance();
|
||||||
|
|
||||||
bool isProcessingEnabled() const;
|
bool isProcessingEnabled() const;
|
||||||
@@ -73,6 +88,9 @@ namespace RSS
|
|||||||
bool renameRule(const QString &ruleName, const QString &newRuleName);
|
bool renameRule(const QString &ruleName, const QString &newRuleName);
|
||||||
void removeRule(const QString &ruleName);
|
void removeRule(const QString &ruleName);
|
||||||
|
|
||||||
|
QByteArray exportRules(RulesFileFormat format = RulesFileFormat::JSON) const;
|
||||||
|
void importRules(const QByteArray &data, RulesFileFormat format = RulesFileFormat::JSON);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void processingStateChanged(bool enabled);
|
void processingStateChanged(bool enabled);
|
||||||
void ruleAdded(const QString &ruleName);
|
void ruleAdded(const QString &ruleName);
|
||||||
@@ -98,6 +116,10 @@ namespace RSS
|
|||||||
void loadRulesLegacy();
|
void loadRulesLegacy();
|
||||||
void store();
|
void store();
|
||||||
void storeDeferred();
|
void storeDeferred();
|
||||||
|
QByteArray exportRulesToJSONFormat() const;
|
||||||
|
void importRulesFromJSONFormat(const QByteArray &data);
|
||||||
|
QByteArray exportRulesToLegacyFormat() const;
|
||||||
|
void importRulesFromLegacyFormat(const QByteArray &data);
|
||||||
|
|
||||||
static QPointer<AutoDownloader> m_instance;
|
static QPointer<AutoDownloader> m_instance;
|
||||||
|
|
||||||
|
@@ -63,11 +63,29 @@ namespace
|
|||||||
QJsonValue triStateBoolToJsonValue(const TriStateBool &triStateBool)
|
QJsonValue triStateBoolToJsonValue(const TriStateBool &triStateBool)
|
||||||
{
|
{
|
||||||
switch (static_cast<int>(triStateBool)) {
|
switch (static_cast<int>(triStateBool)) {
|
||||||
case 0: return false; break;
|
case 0: return false;
|
||||||
case 1: return true; break;
|
case 1: return true;
|
||||||
default: return QJsonValue();
|
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"));
|
const QString Str_Name(QStringLiteral("name"));
|
||||||
@@ -378,21 +396,37 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
|
|||||||
return rule;
|
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());
|
AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict)
|
||||||
rule.setMustContain(varHash.value("must_contain").toString());
|
{
|
||||||
rule.setMustNotContain(varHash.value("must_not_contain").toString());
|
AutoDownloadRule rule(dict.value("name").toString());
|
||||||
rule.setEpisodeFilter(varHash.value("episode_filter").toString());
|
|
||||||
rule.setFeedURLs(varHash.value("affected_feeds").toStringList());
|
rule.setUseRegex(dict.value("use_regex", false).toBool());
|
||||||
rule.setEnabled(varHash.value("enabled", false).toBool());
|
rule.setMustContain(dict.value("must_contain").toString());
|
||||||
rule.setSavePath(varHash.value("save_path").toString());
|
rule.setMustNotContain(dict.value("must_not_contain").toString());
|
||||||
rule.setCategory(varHash.value("category_assigned").toString());
|
rule.setEpisodeFilter(dict.value("episode_filter").toString());
|
||||||
rule.setAddPaused(TriStateBool(varHash.value("add_paused").toInt() - 1));
|
rule.setFeedURLs(dict.value("affected_feeds").toStringList());
|
||||||
rule.setLastMatch(varHash.value("last_match").toDateTime());
|
rule.setEnabled(dict.value("enabled", false).toBool());
|
||||||
rule.setIgnoreDays(varHash.value("ignore_days").toInt());
|
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;
|
return rule;
|
||||||
}
|
}
|
||||||
|
@@ -84,7 +84,9 @@ namespace RSS
|
|||||||
|
|
||||||
QJsonObject toJsonObject() const;
|
QJsonObject toJsonObject() const;
|
||||||
static AutoDownloadRule fromJsonObject(const QJsonObject &jsonObj, const QString &name = "");
|
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:
|
private:
|
||||||
bool matches(const QString &articleTitle, const QString &expression) const;
|
bool matches(const QString &articleTitle, const QString &expression) const;
|
||||||
|
@@ -31,6 +31,7 @@
|
|||||||
#include "rss_feed.h"
|
#include "rss_feed.h"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
@@ -163,7 +163,7 @@ bool Session::moveItem(const QString &itemPath, const QString &destPath, QString
|
|||||||
auto item = m_itemsByPath.value(itemPath);
|
auto item = m_itemsByPath.value(itemPath);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
if (error)
|
if (error)
|
||||||
*error = tr("Item doesn't exists: %1.").arg(itemPath);
|
*error = tr("Item doesn't exist: %1.").arg(itemPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ bool Session::removeItem(const QString &itemPath, QString *error)
|
|||||||
auto item = m_itemsByPath.value(itemPath);
|
auto item = m_itemsByPath.value(itemPath);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
if (error)
|
if (error)
|
||||||
*error = tr("Item doesn't exists: %1.").arg(itemPath);
|
*error = tr("Item doesn't exist: %1.").arg(itemPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
namespace Utils
|
namespace Utils
|
||||||
{
|
{
|
||||||
@@ -38,5 +39,55 @@ namespace Utils
|
|||||||
{
|
{
|
||||||
return !QHostAddress(ip).isNull();
|
return !QHostAddress(ip).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Subnet parseSubnet(const QString &subnetStr, bool *ok)
|
||||||
|
{
|
||||||
|
const Subnet invalid = qMakePair(QHostAddress(), -1);
|
||||||
|
const Subnet subnet = QHostAddress::parseSubnet(subnetStr);
|
||||||
|
if (ok)
|
||||||
|
*ok = (subnet != invalid);
|
||||||
|
return subnet;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canParseSubnet(const QString &subnetStr)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
parseSubnet(subnetStr, &ok);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLoopbackAddress(const QHostAddress &addr)
|
||||||
|
{
|
||||||
|
return (addr == QHostAddress::LocalHost)
|
||||||
|
|| (addr == QHostAddress::LocalHostIPv6)
|
||||||
|
|| (addr == QHostAddress(QLatin1String("::ffff:127.0.0.1")));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isIPInRange(const QHostAddress &addr, const QList<Subnet> &subnets)
|
||||||
|
{
|
||||||
|
QHostAddress protocolEquivalentAddress;
|
||||||
|
bool addrConversionOk = false;
|
||||||
|
|
||||||
|
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
// always succeeds
|
||||||
|
protocolEquivalentAddress = QHostAddress(addr.toIPv6Address());
|
||||||
|
addrConversionOk = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// only succeeds when addr is an ipv4-mapped ipv6 address
|
||||||
|
protocolEquivalentAddress = QHostAddress(addr.toIPv4Address(&addrConversionOk));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const Subnet &subnet : subnets)
|
||||||
|
if (addr.isInSubnet(subnet) || (addrConversionOk && protocolEquivalentAddress.isInSubnet(subnet)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString subnetToString(const Subnet &subnet)
|
||||||
|
{
|
||||||
|
return subnet.first.toString() + '/' + QString::number(subnet.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,13 +28,26 @@
|
|||||||
|
|
||||||
#ifndef BASE_UTILS_NET_H
|
#ifndef BASE_UTILS_NET_H
|
||||||
#define BASE_UTILS_NET_H
|
#define BASE_UTILS_NET_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
|
class QHostAddress;
|
||||||
class QString;
|
class QString;
|
||||||
|
class QStringList;
|
||||||
|
|
||||||
namespace Utils
|
namespace Utils
|
||||||
{
|
{
|
||||||
namespace Net
|
namespace Net
|
||||||
{
|
{
|
||||||
|
using Subnet = QPair<QHostAddress, int>;
|
||||||
|
|
||||||
bool isValidIP(const QString &ip);
|
bool isValidIP(const QString &ip);
|
||||||
|
Subnet parseSubnet(const QString &subnetStr, bool *ok = nullptr);
|
||||||
|
bool canParseSubnet(const QString &subnetStr);
|
||||||
|
bool isLoopbackAddress(const QHostAddress &addr);
|
||||||
|
bool isIPInRange(const QHostAddress &addr, const QList<Subnet> &subnets);
|
||||||
|
QString subnetToString(const Subnet &subnet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,9 +33,9 @@
|
|||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QCollator>
|
#include <QCollator>
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
|
#include <QtGlobal>
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
#include <QThreadStorage>
|
#include <QThreadStorage>
|
||||||
#endif
|
#endif
|
||||||
@@ -45,110 +45,103 @@ namespace
|
|||||||
class NaturalCompare
|
class NaturalCompare
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit NaturalCompare(const bool caseSensitive = true)
|
explicit NaturalCompare(const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive)
|
||||||
: m_caseSensitive(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
|
// 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
|
// 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.
|
// 'μ' character is encoded differently and the native API can't cope with that.
|
||||||
// So default to using our custom natural sorting algorithm instead.
|
// So default to using our custom natural sorting algorithm instead.
|
||||||
// See #5238 and #5240
|
// See #5238 and #5240
|
||||||
// Without ICU library, QCollator doesn't support `setNumericMode(true)` on OS older than Win7
|
// Without ICU library, QCollator doesn't support `setNumericMode(true)` on an OS older than Win7
|
||||||
// if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS7)
|
#else
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
m_collator.setNumericMode(true);
|
m_collator.setNumericMode(true);
|
||||||
m_collator.setCaseSensitivity(caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
m_collator.setCaseSensitivity(caseSensitivity);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
#endif
|
#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
|
#ifdef Q_OS_WIN
|
||||||
int posL = 0;
|
return compare(left, right);
|
||||||
int posR = 0;
|
#else
|
||||||
while (true) {
|
return m_collator.compare(left, right);
|
||||||
while (true) {
|
#endif
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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;
|
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
|
// provide a single `NaturalCompare` instance for easy use
|
||||||
// https://doc.qt.io/qt-5/threads-reentrancy.html
|
// 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
|
#ifdef Q_OS_MAC // workaround for Apple xcode: https://stackoverflow.com/a/29929949
|
||||||
static QThreadStorage<NaturalCompare> nCmp;
|
static QThreadStorage<NaturalCompare> nCmp;
|
||||||
if (!nCmp.hasLocalData()) nCmp.setLocalData(NaturalCompare(true));
|
if (!nCmp.hasLocalData())
|
||||||
return (nCmp.localData())(left, right);
|
nCmp.setLocalData(NaturalCompare(Qt::CaseSensitive));
|
||||||
|
return (nCmp.localData())(left, right);
|
||||||
#else
|
#else
|
||||||
thread_local NaturalCompare nCmp(true);
|
thread_local NaturalCompare nCmp(Qt::CaseSensitive);
|
||||||
return nCmp(left, right);
|
return nCmp(left, right);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::String::naturalCompareCaseInsensitive(const QString &left, const QString &right)
|
#ifdef Q_OS_MAC
|
||||||
{
|
|
||||||
// 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
|
|
||||||
static QThreadStorage<NaturalCompare> nCmp;
|
static QThreadStorage<NaturalCompare> nCmp;
|
||||||
if (!nCmp.hasLocalData()) nCmp.setLocalData(NaturalCompare(false));
|
if (!nCmp.hasLocalData())
|
||||||
|
nCmp.setLocalData(NaturalCompare(Qt::CaseInsensitive));
|
||||||
return (nCmp.localData())(left, right);
|
return (nCmp.localData())(left, right);
|
||||||
#else
|
#else
|
||||||
thread_local NaturalCompare nCmp(false);
|
thread_local NaturalCompare nCmp(Qt::CaseInsensitive);
|
||||||
return nCmp(left, right);
|
return nCmp(left, right);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -188,4 +181,3 @@ QString Utils::String::wildcardToRegex(const QString &pattern)
|
|||||||
{
|
{
|
||||||
return qt_regexp_toCanonical(pattern, QRegExp::Wildcard);
|
return qt_regexp_toCanonical(pattern, QRegExp::Wildcard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -45,8 +45,12 @@ namespace Utils
|
|||||||
// Taken from https://crackstation.net/hashing-security.htm
|
// Taken from https://crackstation.net/hashing-security.htm
|
||||||
bool slowEquals(const QByteArray &a, const QByteArray &b);
|
bool slowEquals(const QByteArray &a, const QByteArray &b);
|
||||||
|
|
||||||
bool naturalCompareCaseSensitive(const QString &left, const QString &right);
|
int naturalCompare(const QString &left, const QString &right, const Qt::CaseSensitivity caseSensitivity);
|
||||||
bool naturalCompareCaseInsensitive(const QString &left, const QString &right);
|
template <Qt::CaseSensitivity caseSensitivity>
|
||||||
|
bool naturalLessThan(const QString &left, const QString &right)
|
||||||
|
{
|
||||||
|
return (naturalCompare(left, right, caseSensitivity) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
QString wildcardToRegex(const QString &pattern);
|
QString wildcardToRegex(const QString &pattern);
|
||||||
|
|
||||||
|
@@ -30,7 +30,6 @@ set(QBT_GUI_HEADERS
|
|||||||
about_imp.h
|
about_imp.h
|
||||||
addnewtorrentdialog.h
|
addnewtorrentdialog.h
|
||||||
advancedsettings.h
|
advancedsettings.h
|
||||||
advancedsettings.h
|
|
||||||
autoexpandabledialog.h
|
autoexpandabledialog.h
|
||||||
banlistoptions.h
|
banlistoptions.h
|
||||||
categoryfiltermodel.h
|
categoryfiltermodel.h
|
||||||
@@ -45,6 +44,7 @@ fspathedit.h
|
|||||||
fspathedit_p.h
|
fspathedit_p.h
|
||||||
guiiconprovider.h
|
guiiconprovider.h
|
||||||
hidabletabwidget.h
|
hidabletabwidget.h
|
||||||
|
ipsubnetwhitelistoptionsdialog.h
|
||||||
loglistwidget.h
|
loglistwidget.h
|
||||||
mainwindow.h
|
mainwindow.h
|
||||||
messageboxraised.h
|
messageboxraised.h
|
||||||
@@ -90,6 +90,7 @@ executionlog.cpp
|
|||||||
fspathedit.cpp
|
fspathedit.cpp
|
||||||
fspathedit_p.cpp
|
fspathedit_p.cpp
|
||||||
guiiconprovider.cpp
|
guiiconprovider.cpp
|
||||||
|
ipsubnetwhitelistoptionsdialog.cpp
|
||||||
loglistwidget.cpp
|
loglistwidget.cpp
|
||||||
mainwindow.cpp
|
mainwindow.cpp
|
||||||
messageboxraised.cpp
|
messageboxraised.cpp
|
||||||
@@ -135,6 +136,7 @@ mainwindow.ui
|
|||||||
about.ui
|
about.ui
|
||||||
banlistoptions.ui
|
banlistoptions.ui
|
||||||
cookiesdialog.ui
|
cookiesdialog.ui
|
||||||
|
ipsubnetwhitelistoptionsdialog.ui
|
||||||
previewselectdialog.ui
|
previewselectdialog.ui
|
||||||
login.ui
|
login.ui
|
||||||
downloadfromurldlg.ui
|
downloadfromurldlg.ui
|
||||||
|
@@ -128,7 +128,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
|||||||
|
|
||||||
// Load categories
|
// Load categories
|
||||||
QStringList categories = session->categories().keys();
|
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();
|
QString defaultCategory = settings()->loadValue(KEY_DEFAULTCATEGORY).toString();
|
||||||
|
|
||||||
if (!m_torrentParams.category.isEmpty())
|
if (!m_torrentParams.category.isEmpty())
|
||||||
|
@@ -70,8 +70,11 @@ void BanListOptions::on_buttonBox_accepted()
|
|||||||
IPList << index.data().toString();
|
IPList << index.data().toString();
|
||||||
}
|
}
|
||||||
BitTorrent::Session::instance()->setBannedIPs(IPList);
|
BitTorrent::Session::instance()->setBannedIPs(IPList);
|
||||||
|
QDialog::accept();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QDialog::reject();
|
||||||
}
|
}
|
||||||
QDialog::accept();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BanListOptions::on_buttonBanIP_clicked()
|
void BanListOptions::on_buttonBanIP_clicked()
|
||||||
|
@@ -50,8 +50,12 @@ bool CategoryFilterProxyModel::lessThan(const QModelIndex &left, const QModelInd
|
|||||||
{
|
{
|
||||||
// "All" and "Uncategorized" must be left in place
|
// "All" and "Uncategorized" must be left in place
|
||||||
if (CategoryFilterModel::isSpecialItem(left) || CategoryFilterModel::isSpecialItem(right))
|
if (CategoryFilterModel::isSpecialItem(left) || CategoryFilterModel::isSpecialItem(right))
|
||||||
return left.row() < right.row();
|
return (left < right);
|
||||||
else
|
|
||||||
return Utils::String::naturalCompareCaseInsensitive(
|
int result = Utils::String::naturalCompare(left.data().toString(), right.data().toString()
|
||||||
left.data().toString(), right.data().toString());
|
, Qt::CaseInsensitive);
|
||||||
|
if (result != 0)
|
||||||
|
return (result < 0);
|
||||||
|
|
||||||
|
return (mapFromSource(left) < mapFromSource(right));
|
||||||
}
|
}
|
||||||
|
@@ -60,7 +60,7 @@ class downloadFromURL : public QDialog, private Ui::downloadFromURL
|
|||||||
|
|
||||||
// Paste clipboard if there is an URL in it
|
// Paste clipboard if there is an URL in it
|
||||||
QString clip_txt = qApp->clipboard()->text();
|
QString clip_txt = qApp->clipboard()->text();
|
||||||
QStringList clip_txt_list = clip_txt.split(QString::fromUtf8("\n"));
|
QStringList clip_txt_list = clip_txt.split(QLatin1Char('\n'));
|
||||||
clip_txt.clear();
|
clip_txt.clear();
|
||||||
QStringList clip_txt_list_cleaned;
|
QStringList clip_txt_list_cleaned;
|
||||||
foreach (clip_txt, clip_txt_list) {
|
foreach (clip_txt, clip_txt_list) {
|
||||||
@@ -94,7 +94,7 @@ class downloadFromURL : public QDialog, private Ui::downloadFromURL
|
|||||||
void downloadButtonClicked()
|
void downloadButtonClicked()
|
||||||
{
|
{
|
||||||
QString urls = textUrls->toPlainText();
|
QString urls = textUrls->toPlainText();
|
||||||
QStringList url_list = urls.split(QString::fromUtf8("\n"));
|
QStringList url_list = urls.split(QLatin1Char('\n'));
|
||||||
QString url;
|
QString url;
|
||||||
QStringList url_list_cleaned;
|
QStringList url_list_cleaned;
|
||||||
foreach (url, url_list) {
|
foreach (url, url_list) {
|
||||||
|
@@ -426,92 +426,6 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
|||||||
POSSIBILITY OF SUCH DAMAGES.
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>END OF TERMS AND CONDITIONS</h3>
|
<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>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -55,6 +55,7 @@ HEADERS += \
|
|||||||
$$PWD/tagfilterproxymodel.h \
|
$$PWD/tagfilterproxymodel.h \
|
||||||
$$PWD/tagfilterwidget.h \
|
$$PWD/tagfilterwidget.h \
|
||||||
$$PWD/banlistoptions.h \
|
$$PWD/banlistoptions.h \
|
||||||
|
$$PWD/ipsubnetwhitelistoptionsdialog.h \
|
||||||
$$PWD/rss/rsswidget.h \
|
$$PWD/rss/rsswidget.h \
|
||||||
$$PWD/rss/articlelistwidget.h \
|
$$PWD/rss/articlelistwidget.h \
|
||||||
$$PWD/rss/feedlistwidget.h \
|
$$PWD/rss/feedlistwidget.h \
|
||||||
@@ -109,6 +110,7 @@ SOURCES += \
|
|||||||
$$PWD/tagfilterproxymodel.cpp \
|
$$PWD/tagfilterproxymodel.cpp \
|
||||||
$$PWD/tagfilterwidget.cpp \
|
$$PWD/tagfilterwidget.cpp \
|
||||||
$$PWD/banlistoptions.cpp \
|
$$PWD/banlistoptions.cpp \
|
||||||
|
$$PWD/ipsubnetwhitelistoptionsdialog.cpp \
|
||||||
$$PWD/rss/rsswidget.cpp \
|
$$PWD/rss/rsswidget.cpp \
|
||||||
$$PWD/rss/articlelistwidget.cpp \
|
$$PWD/rss/articlelistwidget.cpp \
|
||||||
$$PWD/rss/feedlistwidget.cpp \
|
$$PWD/rss/feedlistwidget.cpp \
|
||||||
@@ -150,6 +152,7 @@ FORMS += \
|
|||||||
$$PWD/search/searchtab.ui \
|
$$PWD/search/searchtab.ui \
|
||||||
$$PWD/cookiesdialog.ui \
|
$$PWD/cookiesdialog.ui \
|
||||||
$$PWD/banlistoptions.ui \
|
$$PWD/banlistoptions.ui \
|
||||||
|
$$PWD/ipsubnetwhitelistoptionsdialog.ui \
|
||||||
$$PWD/rss/rsswidget.ui \
|
$$PWD/rss/rsswidget.ui \
|
||||||
$$PWD/rss/automatedrssdownloader.ui \
|
$$PWD/rss/automatedrssdownloader.ui \
|
||||||
$$PWD/torrentcategorydialog.ui
|
$$PWD/torrentcategorydialog.ui
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user