mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2026-01-04 22:52:33 -06:00
Merge branch 'master' into stylized-icons-main-bar
# Conflicts: # src/icons/skin/ratio.png
This commit is contained in:
109
src/CMakeLists.txt
Normal file
109
src/CMakeLists.txt
Normal file
@@ -0,0 +1,109 @@
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_STANDARD "11")
|
||||
add_definitions(-DBOOST_NO_CXX11_RVALUE_REFERENCES)
|
||||
|
||||
include(MacroLinkQtComponents)
|
||||
|
||||
find_package(LibtorrentRasterbar REQUIRED)
|
||||
include_directories(SYSTEM ${LibtorrentRasterbar_INCLUDE_DIRS})
|
||||
add_compile_options(${LibtorrentRasterbar_DEFINITIONS})
|
||||
|
||||
# Boost
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
find_package(Boost 1.35 REQUIRED COMPONENTS system)
|
||||
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
|
||||
|
||||
# Qt
|
||||
if (QT5)
|
||||
add_definitions(-DQBT_USES_QT5)
|
||||
list(APPEND QBT_QT_COMPONENTS Core Network Xml)
|
||||
if (GUI)
|
||||
list (APPEND QBT_QT_COMPONENTS Concurrent Gui Widgets)
|
||||
endif (GUI)
|
||||
if (DBUS)
|
||||
list (APPEND QBT_QT_COMPONENTS DBus)
|
||||
endif (DBUS)
|
||||
find_package(Qt5 5.2.0 COMPONENTS ${QBT_QT_COMPONENTS} REQUIRED)
|
||||
else (QT5)
|
||||
list(APPEND QBT_QT_COMPONENTS QtCore QtNetwork QtXml)
|
||||
if (GUI)
|
||||
list (APPEND QBT_QT_COMPONENTS QtGui)
|
||||
endif (GUI)
|
||||
if (DBUS)
|
||||
list (APPEND QBT_QT_COMPONENTS QtDBus)
|
||||
endif (DBUS)
|
||||
find_package(Qt4 4.8.0 COMPONENTS ${QBT_QT_COMPONENTS} REQUIRED)
|
||||
include(${QT_USE_FILE})
|
||||
endif (QT5)
|
||||
|
||||
set(CMAKE_AUTOMOC True)
|
||||
list(APPEND CMAKE_AUTORCC_OPTIONS -compress 9 -threshold 5)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# defines
|
||||
add_definitions(-DQT_NO_CAST_TO_ASCII)
|
||||
# Fast concatenation (Qt >= 4.6)
|
||||
add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS)
|
||||
if (WIN32)
|
||||
add_definitions(-DNOMINMAX)
|
||||
endif (WIN32)
|
||||
|
||||
if (NOT GUI)
|
||||
add_definitions(-DDISABLE_GUI -DDISABLE_COUNTRIES_RESOLUTION)
|
||||
endif (NOT GUI)
|
||||
|
||||
if (NOT WEBUI)
|
||||
add_definitions(-DDISABLE_WEBUI)
|
||||
endif (NOT WEBUI)
|
||||
|
||||
if (STACKTRACE_WIN)
|
||||
add_definitions(-DSTACKTRACE_WIN)
|
||||
endif(STACKTRACE_WIN)
|
||||
# nogui {
|
||||
# TARGET = qbittorrent-nox
|
||||
# } else {
|
||||
# CONFIG(static) {
|
||||
# DEFINES += QBT_STATIC_QT
|
||||
# QTPLUGIN += qico
|
||||
# }
|
||||
# TARGET = qbittorrent
|
||||
# }
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
add_compile_options(-Wformat -Wformat-security)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
message(STATUS "Project is built in DEBUG mode.")
|
||||
else (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
message(STATUS "Project is built in RELEASE mode.")
|
||||
message(STATUS "Disabling debug output.")
|
||||
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
||||
endif (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
|
||||
set(QBT_USE_GUI ${GUI})
|
||||
set(QBT_USE_WEBUI ${WEBUI})
|
||||
set(QBT_USES_QT5 ${QT5})
|
||||
|
||||
configure_file(config.h.cmakein ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||
|
||||
|
||||
add_subdirectory(base)
|
||||
|
||||
if (SYSTEM_QTSINGLEAPPLICATION)
|
||||
find_package(QtSingleApplication REQUIRED)
|
||||
include_directories(${QTSINGLEAPPLICATION_INCLUDE_DIR})
|
||||
else (SYSTEM_QTSINGLEAPPLICATION)
|
||||
include_directories(app/qtsingleapplication)
|
||||
endif (SYSTEM_QTSINGLEAPPLICATION)
|
||||
|
||||
if (GUI)
|
||||
add_subdirectory(gui)
|
||||
endif (GUI)
|
||||
|
||||
if (WEBUI)
|
||||
add_subdirectory(webui)
|
||||
endif (WEBUI)
|
||||
|
||||
add_subdirectory(app)
|
||||
122
src/app/CMakeLists.txt
Normal file
122
src/app/CMakeLists.txt
Normal file
@@ -0,0 +1,122 @@
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(QBT_APP_HEADERS
|
||||
application.h
|
||||
)
|
||||
|
||||
set(QBT_APP_SOURCES
|
||||
application.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
# translations
|
||||
file(GLOB QBT_TS_FILES ../lang/*.ts)
|
||||
get_filename_component(QBT_QM_FILES_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/../lang" ABSOLUTE)
|
||||
set_source_files_properties(${QBT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${QBT_QM_FILES_BINARY_DIR}")
|
||||
|
||||
if (QT5)
|
||||
find_package(Qt5 COMPONENTS LinguistTools REQUIRED)
|
||||
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES})
|
||||
else (QT5)
|
||||
qt4_add_translation(QBT_QM_FILES ${QBT_TS_FILES})
|
||||
endif (QT5)
|
||||
|
||||
get_filename_component(_lang_qrc_src "${CMAKE_CURRENT_SOURCE_DIR}/../lang.qrc" ABSOLUTE)
|
||||
get_filename_component(_lang_qrc_dst "${CMAKE_CURRENT_BINARY_DIR}/../lang.qrc" ABSOLUTE)
|
||||
get_filename_component(_lang_qrc_dst_dir "${CMAKE_CURRENT_BINARY_DIR}/../" ABSOLUTE)
|
||||
|
||||
message(STATUS "copying ${_lang_qrc_src} -> ${_lang_qrc_dst}")
|
||||
file(COPY ${_lang_qrc_src} DESTINATION ${_lang_qrc_dst_dir})
|
||||
|
||||
set_source_files_properties("${_lang_qrc_dst}" PROPERTIES GENERATED True)
|
||||
foreach(qm_file ${QBT_QM_FILES})
|
||||
set_source_files_properties("${_lang_qrc_dst}" PROPERTIES OBJECT_DEPENDS ${qm_file})
|
||||
endforeach()
|
||||
|
||||
set(QBT_APP_RESOURCES
|
||||
../icons.qrc
|
||||
"${_lang_qrc_dst}"
|
||||
)
|
||||
|
||||
# With AUTORCC rcc is ran by cmake before language files are generated,
|
||||
# and thus we call rcc explicitly
|
||||
if (QT5)
|
||||
qt5_add_resources(QBT_APP_RESOURCE_SOURCE ${QBT_APP_RESOURCES})
|
||||
else (QT5)
|
||||
qt4_add_resources(QBT_APP_RESOURCE_SOURCE ${QBT_APP_RESOURCES})
|
||||
endif (QT5)
|
||||
|
||||
if (WIN32)
|
||||
if (MINGW)
|
||||
list (APPEND QBT_APP_SOURCES ../qbittorrent_mingw.rc)
|
||||
else (MINGW)
|
||||
list (APPEND QBT_APP_SOURCES ../qbittorrent.rc)
|
||||
endif (MINGW)
|
||||
endif (WIN32)
|
||||
|
||||
if (UNIX)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace.h)
|
||||
endif (UNIX)
|
||||
|
||||
if (STACKTRACE_WIN)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace_win.h)
|
||||
if (GUI)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace_win_dlg.h)
|
||||
endif (GUI)
|
||||
endif (STACKTRACE_WIN)
|
||||
|
||||
# usesystemqtsingleapplication {
|
||||
# nogui {
|
||||
# CONFIG += qtsinglecoreapplication
|
||||
# } else {
|
||||
# CONFIG += qtsingleapplication
|
||||
# }
|
||||
# } else {
|
||||
# nogui {
|
||||
# include(qtsingleapplication/qtsinglecoreapplication.pri)
|
||||
# } else {
|
||||
# include(qtsingleapplication/qtsingleapplication.pri)
|
||||
# }
|
||||
# }
|
||||
|
||||
# upgrade code
|
||||
list(APPEND QBT_APP_HEADERS upgrade.h)
|
||||
list(APPEND QBT_TARGET_LIBRARIES qbt_base)
|
||||
|
||||
if (GUI)
|
||||
set(QBT_TARGET_NAME qbittorrent)
|
||||
list(APPEND QBT_TARGET_LIBRARIES qbt_searchengine qbt_gui)
|
||||
include_directories(../gui
|
||||
${CMAKE_CURRENT_BINARY_DIR}/../gui
|
||||
)
|
||||
else (GUI)
|
||||
set(QBT_TARGET_NAME qbittorrent-nox)
|
||||
endif (GUI)
|
||||
|
||||
if (WEBUI)
|
||||
list(APPEND QBT_TARGET_LIBRARIES qbt_webui)
|
||||
endif (WEBUI)
|
||||
|
||||
add_executable(${QBT_TARGET_NAME} ${QBT_APP_HEADERS} ${QBT_APP_SOURCES} ${QBT_QM_FILES} ${QBT_APP_RESOURCE_SOURCE})
|
||||
set_target_properties(${QBT_TARGET_NAME} PROPERTIES AUTOUIC True)
|
||||
|
||||
if (GUI)
|
||||
if (WIN32)
|
||||
set_target_properties(${QBT_TARGET_NAME} PROPERTIES WIN32_EXECUTABLE True)
|
||||
endif (WIN32)
|
||||
if (APPLE)
|
||||
set_target_properties(${QBT_TARGET_NAME} PROPERTIES MACOSX_BUNDLE True)
|
||||
endif (APPLE)
|
||||
endif (GUI)
|
||||
|
||||
target_link_libraries(${QBT_TARGET_NAME} ${QBT_TARGET_LIBRARIES})
|
||||
|
||||
if (SYSTEM_QTSINGLEAPPLICATION)
|
||||
target_link_libraries(${QBT_TARGET_NAME} ${QTSINGLEAPPLICATION_LIBRARIES})
|
||||
else (SYSTEM_QTSINGLEAPPLICATION)
|
||||
add_subdirectory(qtsingleapplication)
|
||||
target_link_libraries(${QBT_TARGET_NAME} qtsingleapplication)
|
||||
endif (SYSTEM_QTSINGLEAPPLICATION)
|
||||
|
||||
# installation
|
||||
install(TARGETS ${QBT_TARGET_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime)
|
||||
@@ -14,8 +14,14 @@ usesystemqtsingleapplication {
|
||||
}
|
||||
}
|
||||
|
||||
HEADERS += $$PWD/application.h
|
||||
SOURCES += $$PWD/application.cpp
|
||||
HEADERS += \
|
||||
$$PWD/application.h \
|
||||
$$PWD/filelogger.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/application.cpp \
|
||||
$$PWD/filelogger.cpp \
|
||||
$$PWD/main.cpp
|
||||
|
||||
unix: HEADERS += $$PWD/stacktrace.h
|
||||
strace_win {
|
||||
@@ -26,7 +32,5 @@ strace_win {
|
||||
}
|
||||
}
|
||||
|
||||
SOURCES += $$PWD/main.cpp
|
||||
|
||||
# upgrade code
|
||||
HEADERS += $$PWD/upgrade.h
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#ifndef DISABLE_GUI
|
||||
#include "gui/guiiconprovider.h"
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Windows.h>
|
||||
#include <windows.h>
|
||||
#include <QSharedMemory>
|
||||
#include <QSessionManager>
|
||||
#endif // Q_OS_WIN
|
||||
@@ -58,19 +58,40 @@
|
||||
#endif
|
||||
|
||||
#include "application.h"
|
||||
#include "core/logger.h"
|
||||
#include "core/preferences.h"
|
||||
#include "core/utils/fs.h"
|
||||
#include "core/utils/misc.h"
|
||||
#include "core/iconprovider.h"
|
||||
#include "core/scanfoldersmodel.h"
|
||||
#include "core/net/smtp.h"
|
||||
#include "core/net/downloadmanager.h"
|
||||
#include "core/net/geoipmanager.h"
|
||||
#include "core/bittorrent/session.h"
|
||||
#include "core/bittorrent/torrenthandle.h"
|
||||
#include "filelogger.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/settingsstorage.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/iconprovider.h"
|
||||
#include "base/scanfoldersmodel.h"
|
||||
#include "base/net/smtp.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/net/geoipmanager.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
|
||||
static const char PARAMS_SEPARATOR[] = "|";
|
||||
namespace
|
||||
{
|
||||
#define SETTINGS_KEY(name) "Application/" name
|
||||
|
||||
// FileLogger properties keys
|
||||
#define FILELOGGER_SETTINGS_KEY(name) SETTINGS_KEY("FileLogger/") name
|
||||
const QString KEY_FILELOGGER_ENABLED = FILELOGGER_SETTINGS_KEY("Enabled");
|
||||
const QString KEY_FILELOGGER_PATH = FILELOGGER_SETTINGS_KEY("Path");
|
||||
const QString KEY_FILELOGGER_BACKUP = FILELOGGER_SETTINGS_KEY("Backup");
|
||||
const QString KEY_FILELOGGER_DELETEOLD = FILELOGGER_SETTINGS_KEY("DeleteOld");
|
||||
const QString KEY_FILELOGGER_MAXSIZE = FILELOGGER_SETTINGS_KEY("MaxSize");
|
||||
const QString KEY_FILELOGGER_AGE = FILELOGGER_SETTINGS_KEY("Age");
|
||||
const QString KEY_FILELOGGER_AGETYPE = FILELOGGER_SETTINGS_KEY("AgeType");
|
||||
|
||||
//just a shortcut
|
||||
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
|
||||
|
||||
const QString LOG_FOLDER("logs");
|
||||
const char PARAMS_SEPARATOR[] = "|";
|
||||
}
|
||||
|
||||
Application::Application(const QString &id, int &argc, char **argv)
|
||||
: BaseApplication(id, argc, argv)
|
||||
@@ -80,6 +101,7 @@ Application::Application(const QString &id, int &argc, char **argv)
|
||||
#endif
|
||||
{
|
||||
Logger::initInstance();
|
||||
SettingsStorage::initInstance();
|
||||
Preferences::initInstance();
|
||||
|
||||
#if defined(Q_OS_MACX) && !defined(DISABLE_GUI)
|
||||
@@ -92,7 +114,9 @@ Application::Application(const QString &id, int &argc, char **argv)
|
||||
setApplicationName("qBittorrent");
|
||||
initializeTranslation();
|
||||
#ifndef DISABLE_GUI
|
||||
setStyleSheet("QStatusBar::item { border-width: 0; }");
|
||||
#ifdef QBT_USES_QT5
|
||||
setAttribute(Qt::AA_UseHighDpiPixmaps, true); // opt-in to the high DPI pixmap support
|
||||
#endif // QBT_USES_QT5
|
||||
setQuitOnLastWindowClosed(false);
|
||||
#ifdef Q_OS_WIN
|
||||
connect(this, SIGNAL(commitDataRequest(QSessionManager &)), this, SLOT(shutdownCleanup(QSessionManager &)), Qt::DirectConnection);
|
||||
@@ -102,9 +126,105 @@ Application::Application(const QString &id, int &argc, char **argv)
|
||||
connect(this, SIGNAL(messageReceived(const QString &)), SLOT(processMessage(const QString &)));
|
||||
connect(this, SIGNAL(aboutToQuit()), SLOT(cleanup()));
|
||||
|
||||
if (isFileLoggerEnabled())
|
||||
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
||||
|
||||
Logger::instance()->addMessage(tr("qBittorrent %1 started", "qBittorrent v3.2.0alpha started").arg(VERSION));
|
||||
}
|
||||
|
||||
bool Application::isFileLoggerEnabled() const
|
||||
{
|
||||
return settings()->loadValue(KEY_FILELOGGER_ENABLED, true).toBool();
|
||||
}
|
||||
|
||||
void Application::setFileLoggerEnabled(bool value)
|
||||
{
|
||||
if (value && !m_fileLogger)
|
||||
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
||||
else if (!value)
|
||||
delete m_fileLogger;
|
||||
settings()->storeValue(KEY_FILELOGGER_ENABLED, value);
|
||||
}
|
||||
|
||||
QString Application::fileLoggerPath() const
|
||||
{
|
||||
return settings()->loadValue(KEY_FILELOGGER_PATH, QVariant(Utils::Fs::QDesktopServicesDataLocation() + LOG_FOLDER)).toString();
|
||||
}
|
||||
|
||||
void Application::setFileLoggerPath(const QString &value)
|
||||
{
|
||||
if (m_fileLogger)
|
||||
m_fileLogger->changePath(value);
|
||||
settings()->storeValue(KEY_FILELOGGER_PATH, value);
|
||||
}
|
||||
|
||||
bool Application::isFileLoggerBackup() const
|
||||
{
|
||||
return settings()->loadValue(KEY_FILELOGGER_BACKUP, true).toBool();
|
||||
}
|
||||
|
||||
void Application::setFileLoggerBackup(bool value)
|
||||
{
|
||||
if (m_fileLogger)
|
||||
m_fileLogger->setBackup(value);
|
||||
settings()->storeValue(KEY_FILELOGGER_BACKUP, value);
|
||||
}
|
||||
|
||||
bool Application::isFileLoggerDeleteOld() const
|
||||
{
|
||||
return settings()->loadValue(KEY_FILELOGGER_DELETEOLD, true).toBool();
|
||||
}
|
||||
|
||||
void Application::setFileLoggerDeleteOld(bool value)
|
||||
{
|
||||
if (value && m_fileLogger)
|
||||
m_fileLogger->deleteOld(fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
||||
settings()->storeValue(KEY_FILELOGGER_DELETEOLD, value);
|
||||
}
|
||||
|
||||
int Application::fileLoggerMaxSize() const
|
||||
{
|
||||
int val = settings()->loadValue(KEY_FILELOGGER_MAXSIZE, 10).toInt();
|
||||
if (val < 1)
|
||||
return 1;
|
||||
if (val > 1000)
|
||||
return 1000;
|
||||
return val;
|
||||
}
|
||||
|
||||
void Application::setFileLoggerMaxSize(const int value)
|
||||
{
|
||||
if (m_fileLogger)
|
||||
m_fileLogger->setMaxSize(value);
|
||||
settings()->storeValue(KEY_FILELOGGER_MAXSIZE, std::min(std::max(value, 1), 1000));
|
||||
}
|
||||
|
||||
int Application::fileLoggerAge() const
|
||||
{
|
||||
int val = settings()->loadValue(KEY_FILELOGGER_AGE, 6).toInt();
|
||||
if (val < 1)
|
||||
return 1;
|
||||
if (val > 365)
|
||||
return 365;
|
||||
return val;
|
||||
}
|
||||
|
||||
void Application::setFileLoggerAge(const int value)
|
||||
{
|
||||
settings()->storeValue(KEY_FILELOGGER_AGE, std::min(std::max(value, 1), 365));
|
||||
}
|
||||
|
||||
int Application::fileLoggerAgeType() const
|
||||
{
|
||||
int val = settings()->loadValue(KEY_FILELOGGER_AGETYPE, 1).toInt();
|
||||
return (val < 0 || val > 2) ? 1 : val;
|
||||
}
|
||||
|
||||
void Application::setFileLoggerAgeType(const int value)
|
||||
{
|
||||
settings()->storeValue(KEY_FILELOGGER_AGETYPE, (value < 0 || value > 2) ? 1 : value);
|
||||
}
|
||||
|
||||
void Application::processMessage(const QString &message)
|
||||
{
|
||||
QStringList params = message.split(QLatin1String(PARAMS_SEPARATOR), QString::SkipEmptyParts);
|
||||
@@ -144,7 +264,7 @@ void Application::torrentFinished(BitTorrent::TorrentHandle *const torrent)
|
||||
QString program = pref->getAutoRunProgram();
|
||||
|
||||
program.replace("%N", torrent->name());
|
||||
program.replace("%L", torrent->label());
|
||||
program.replace("%L", torrent->category());
|
||||
program.replace("%F", Utils::Fs::toNativePath(torrent->contentPath()));
|
||||
program.replace("%R", Utils::Fs::toNativePath(torrent->rootPath()));
|
||||
program.replace("%D", Utils::Fs::toNativePath(torrent->savePath()));
|
||||
@@ -186,7 +306,14 @@ void Application::allTorrentsFinished()
|
||||
else if (shutdown)
|
||||
action = ShutdownAction::Shutdown;
|
||||
|
||||
if (!ShutdownConfirmDlg::askForConfirmation(action)) return;
|
||||
if ((action == ShutdownAction::None) && (!pref->dontConfirmAutoExit())) {
|
||||
if (!ShutdownConfirmDlg::askForConfirmation(action))
|
||||
return;
|
||||
}
|
||||
else { //exit and shutdown
|
||||
if (!ShutdownConfirmDlg::askForConfirmation(action))
|
||||
return;
|
||||
}
|
||||
|
||||
// Actually shut down
|
||||
if (suspend || hibernate || shutdown) {
|
||||
@@ -225,7 +352,7 @@ void Application::processParams(const QStringList ¶ms)
|
||||
foreach (QString param, params) {
|
||||
param = param.trimmed();
|
||||
#ifndef DISABLE_GUI
|
||||
if (Preferences::instance()->useAdditionDialog())
|
||||
if (AddNewTorrentDialog::isEnabled())
|
||||
AddNewTorrentDialog::show(param, m_window);
|
||||
else
|
||||
#endif
|
||||
@@ -357,7 +484,7 @@ void Application::initializeTranslation()
|
||||
}
|
||||
|
||||
if (m_qtTranslator.load(
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
QString::fromUtf8("qtbase_") + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath)) ||
|
||||
m_qtTranslator.load(
|
||||
#endif
|
||||
@@ -462,10 +589,12 @@ void Application::cleanup()
|
||||
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||
Net::GeoIPManager::freeInstance();
|
||||
#endif
|
||||
Net::DownloadManager::freeInstance();
|
||||
Preferences::freeInstance();
|
||||
SettingsStorage::freeInstance();
|
||||
delete m_fileLogger;
|
||||
Logger::freeInstance();
|
||||
IconProvider::freeInstance();
|
||||
Net::DownloadManager::freeInstance();
|
||||
#ifndef DISABLE_GUI
|
||||
#ifdef Q_OS_WIN
|
||||
typedef BOOL (WINAPI *PSHUTDOWNBRDESTROY)(HWND);
|
||||
|
||||
@@ -50,12 +50,14 @@ QT_END_NAMESPACE
|
||||
typedef QtSingleCoreApplication BaseApplication;
|
||||
#endif
|
||||
|
||||
#include "core/utils/misc.h"
|
||||
#include "base/utils/misc.h"
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
class WebUI;
|
||||
#endif
|
||||
|
||||
class FileLogger;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentHandle;
|
||||
@@ -74,6 +76,22 @@ public:
|
||||
int exec(const QStringList ¶ms);
|
||||
bool sendParams(const QStringList ¶ms);
|
||||
|
||||
// FileLogger properties
|
||||
bool isFileLoggerEnabled() const;
|
||||
void setFileLoggerEnabled(bool value);
|
||||
QString fileLoggerPath() const;
|
||||
void setFileLoggerPath(const QString &path);
|
||||
bool isFileLoggerBackup() const;
|
||||
void setFileLoggerBackup(bool value);
|
||||
bool isFileLoggerDeleteOld() const;
|
||||
void setFileLoggerDeleteOld(bool value);
|
||||
int fileLoggerMaxSize() const;
|
||||
void setFileLoggerMaxSize(const int value);
|
||||
int fileLoggerAge() const;
|
||||
void setFileLoggerAge(const int value);
|
||||
int fileLoggerAgeType() const;
|
||||
void setFileLoggerAgeType(const int value);
|
||||
|
||||
protected:
|
||||
#ifndef DISABLE_GUI
|
||||
#ifdef Q_OS_MAC
|
||||
@@ -103,6 +121,9 @@ private:
|
||||
QPointer<WebUI> m_webui;
|
||||
#endif
|
||||
|
||||
// FileLog
|
||||
QPointer<FileLogger> m_fileLogger;
|
||||
|
||||
QTranslator m_qtTranslator;
|
||||
QTranslator m_translator;
|
||||
QStringList m_paramsQueue;
|
||||
|
||||
176
src/app/filelogger.cpp
Normal file
176
src/app/filelogger.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 sledgehammer999 <hammered999@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include "filelogger.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize, const bool deleteOld, const int age, const FileLogAgeType ageType)
|
||||
: m_backup(backup)
|
||||
, m_maxSize(maxSize)
|
||||
, m_logFile(nullptr)
|
||||
{
|
||||
m_flusher.setInterval(0);
|
||||
m_flusher.setSingleShot(true);
|
||||
connect(&m_flusher, SIGNAL(timeout()), SLOT(flushLog()));
|
||||
|
||||
changePath(path);
|
||||
if (deleteOld)
|
||||
this->deleteOld(age, ageType);
|
||||
|
||||
const Logger* const logger = Logger::instance();
|
||||
foreach (const Log::Msg& msg, logger->getMessages())
|
||||
addLogMessage(msg);
|
||||
|
||||
connect(logger, SIGNAL(newLogMessage(const Log::Msg &)), SLOT(addLogMessage(const Log::Msg &)));
|
||||
}
|
||||
|
||||
FileLogger::~FileLogger()
|
||||
{
|
||||
if (!m_logFile) return;
|
||||
closeLogFile();
|
||||
delete m_logFile;
|
||||
}
|
||||
|
||||
void FileLogger::changePath(const QString& newPath)
|
||||
{
|
||||
QString tmpPath = Utils::Fs::fromNativePath(newPath);
|
||||
QDir dir(tmpPath);
|
||||
dir.mkpath(tmpPath);
|
||||
tmpPath = dir.absoluteFilePath("qbittorrent.log");
|
||||
|
||||
if (tmpPath != m_path) {
|
||||
m_path = tmpPath;
|
||||
|
||||
if (m_logFile) {
|
||||
closeLogFile();
|
||||
delete m_logFile;
|
||||
}
|
||||
m_logFile = new QFile(m_path);
|
||||
openLogFile();
|
||||
}
|
||||
}
|
||||
|
||||
void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
|
||||
{
|
||||
QDateTime date = QDateTime::currentDateTime();
|
||||
QDir dir(m_path);
|
||||
|
||||
switch (ageType) {
|
||||
case DAYS:
|
||||
date = date.addDays(age);
|
||||
break;
|
||||
case MONTHS:
|
||||
date = date.addMonths(age);
|
||||
break;
|
||||
default:
|
||||
date = date.addYears(age);
|
||||
}
|
||||
|
||||
foreach (const QFileInfo file, dir.entryInfoList(QStringList("qbittorrent.log.bak*"), QDir::Files | QDir::Writable, QDir::Time | QDir::Reversed)) {
|
||||
if (file.lastModified() < date)
|
||||
break;
|
||||
Utils::Fs::forceRemove(file.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
void FileLogger::setBackup(bool value)
|
||||
{
|
||||
m_backup = value;
|
||||
}
|
||||
|
||||
void FileLogger::setMaxSize(int value)
|
||||
{
|
||||
m_maxSize = value;
|
||||
}
|
||||
|
||||
void FileLogger::addLogMessage(const Log::Msg &msg)
|
||||
{
|
||||
if (!m_logFile) return;
|
||||
|
||||
QTextStream str(m_logFile);
|
||||
|
||||
switch (msg.type) {
|
||||
case Log::INFO:
|
||||
str << "(I) ";
|
||||
break;
|
||||
case Log::WARNING:
|
||||
str << "(W) ";
|
||||
break;
|
||||
case Log::CRITICAL:
|
||||
str << "(C) ";
|
||||
break;
|
||||
default:
|
||||
str << "(N) ";
|
||||
}
|
||||
|
||||
str << QDateTime::fromMSecsSinceEpoch(msg.timestamp).toString(Qt::ISODate) << " - " << msg.message << endl;
|
||||
|
||||
if (m_backup && (m_logFile->size() >= (m_maxSize * 1024 * 1024))) {
|
||||
closeLogFile();
|
||||
int counter = 0;
|
||||
QString backupLogFilename = m_path + ".bak";
|
||||
|
||||
while (QFile::exists(backupLogFilename)) {
|
||||
++counter;
|
||||
backupLogFilename = m_path + ".bak" + QString::number(counter);
|
||||
}
|
||||
|
||||
QFile::rename(m_path, backupLogFilename);
|
||||
openLogFile();
|
||||
}
|
||||
else {
|
||||
m_flusher.start();
|
||||
}
|
||||
}
|
||||
|
||||
void FileLogger::flushLog()
|
||||
{
|
||||
if (m_logFile)
|
||||
m_logFile->flush();
|
||||
}
|
||||
|
||||
void FileLogger::openLogFile()
|
||||
{
|
||||
if (!m_logFile->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)
|
||||
|| !m_logFile->setPermissions(QFile::ReadOwner | QFile::WriteOwner)) {
|
||||
delete m_logFile;
|
||||
m_logFile = nullptr;
|
||||
Logger::instance()->addMessage(tr("An error occured while trying to open the log file. Logging to file is disabled."), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
void FileLogger::closeLogFile()
|
||||
{
|
||||
m_flusher.stop();
|
||||
m_logFile->close();
|
||||
}
|
||||
79
src/app/filelogger.h
Normal file
79
src/app/filelogger.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 sledgehammer999 <hammered999@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef FILELOGGER_H
|
||||
#define FILELOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class QFile;
|
||||
|
||||
namespace Log
|
||||
{
|
||||
struct Msg;
|
||||
}
|
||||
|
||||
class FileLogger : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(FileLogger)
|
||||
|
||||
public:
|
||||
enum FileLogAgeType
|
||||
{
|
||||
DAYS,
|
||||
MONTHS,
|
||||
YEARS
|
||||
};
|
||||
|
||||
FileLogger(const QString &path, const bool backup, const int maxSize, const bool deleteOld, const int age, const FileLogAgeType ageType);
|
||||
~FileLogger();
|
||||
|
||||
void changePath(const QString &newPath);
|
||||
void deleteOld(const int age, const FileLogAgeType ageType);
|
||||
void setBackup(bool value);
|
||||
void setMaxSize(int value);
|
||||
|
||||
private slots:
|
||||
void addLogMessage(const Log::Msg &msg);
|
||||
void flushLog();
|
||||
|
||||
private:
|
||||
void openLogFile();
|
||||
void closeLogFile();
|
||||
|
||||
QString m_path;
|
||||
bool m_backup;
|
||||
int m_maxSize;
|
||||
QFile *m_logFile;
|
||||
QTimer m_flusher;
|
||||
};
|
||||
|
||||
#endif // FILELOGGER_H
|
||||
|
||||
121
src/app/main.cpp
121
src/app/main.cpp
@@ -42,7 +42,7 @@
|
||||
#include <QSplashScreen>
|
||||
#ifdef QBT_STATIC_QT
|
||||
#include <QtPlugin>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
Q_IMPORT_PLUGIN(QICOPlugin)
|
||||
#else
|
||||
Q_IMPORT_PLUGIN(qico)
|
||||
@@ -72,17 +72,22 @@ Q_IMPORT_PLUGIN(qico)
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include "application.h"
|
||||
#include "core/utils/misc.h"
|
||||
#include "core/preferences.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/preferences.h"
|
||||
|
||||
#include "upgrade.h"
|
||||
|
||||
// Signal handlers
|
||||
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||
void sigintHandler(int);
|
||||
void sigtermHandler(int);
|
||||
void sigsegvHandler(int);
|
||||
void sigabrtHandler(int);
|
||||
void sigNormalHandler(int signum);
|
||||
void sigAbnormalHandler(int signum);
|
||||
// sys_signame[] is only defined in BSD
|
||||
const char *sysSigName[] = {
|
||||
"", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL",
|
||||
"SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP",
|
||||
"SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO",
|
||||
"SIGPWR", "SIGUNUSED"
|
||||
};
|
||||
#endif
|
||||
|
||||
struct QBtCommandLineParameters
|
||||
@@ -210,6 +215,21 @@ int main(int argc, char *argv[])
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN) && defined(QBT_USES_QT5)
|
||||
// This affects only Windows apparently and Qt5.
|
||||
// When QNetworkAccessManager is instantiated it regularly starts polling
|
||||
// the network interfaces to see what's available and their status.
|
||||
// This polling creates jitter and high ping with wifi interfaces.
|
||||
// So here we disable it for lack of better measure.
|
||||
// It will also spew this message in the console: QObject::startTimer: Timers cannot have negative intervals
|
||||
// For more info see:
|
||||
// 1. https://github.com/qbittorrent/qBittorrent/issues/4209
|
||||
// 2. https://bugreports.qt.io/browse/QTBUG-40332
|
||||
// 3. https://bugreports.qt.io/browse/QTBUG-46015
|
||||
|
||||
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
if (!upgrade()) return EXIT_FAILURE;
|
||||
#else
|
||||
@@ -240,10 +260,10 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||
signal(SIGABRT, sigabrtHandler);
|
||||
signal(SIGTERM, sigtermHandler);
|
||||
signal(SIGINT, sigintHandler);
|
||||
signal(SIGSEGV, sigsegvHandler);
|
||||
signal(SIGINT, sigNormalHandler);
|
||||
signal(SIGTERM, sigNormalHandler);
|
||||
signal(SIGABRT, sigAbnormalHandler);
|
||||
signal(SIGSEGV, sigAbnormalHandler);
|
||||
#endif
|
||||
|
||||
return app->exec(params.torrents);
|
||||
@@ -303,58 +323,41 @@ QBtCommandLineParameters parseCommandLine()
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||
void sigintHandler(int)
|
||||
void sigNormalHandler(int signum)
|
||||
{
|
||||
signal(SIGINT, 0);
|
||||
qDebug("Catching SIGINT, exiting cleanly");
|
||||
qApp->exit();
|
||||
}
|
||||
|
||||
void sigtermHandler(int)
|
||||
{
|
||||
signal(SIGTERM, 0);
|
||||
qDebug("Catching SIGTERM, exiting cleanly");
|
||||
qApp->exit();
|
||||
}
|
||||
|
||||
void sigsegvHandler(int)
|
||||
{
|
||||
signal(SIGABRT, 0);
|
||||
signal(SIGSEGV, 0);
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
std::cerr << "\n\n*************************************************************\n";
|
||||
std::cerr << "Catching SIGSEGV, please report a bug at http://bug.qbittorrent.org\nand provide the following backtrace:\n";
|
||||
std::cerr << "qBittorrent version: " << VERSION << std::endl;
|
||||
print_stacktrace();
|
||||
#else
|
||||
const char str1[] = "Catching signal: ";
|
||||
const char *sigName = sysSigName[signum];
|
||||
const char str2[] = "\nExiting cleanly\n";
|
||||
write(STDERR_FILENO, str1, strlen(str1));
|
||||
write(STDERR_FILENO, sigName, strlen(sigName));
|
||||
write(STDERR_FILENO, str2, strlen(str2));
|
||||
#endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
signal(signum, SIG_DFL);
|
||||
qApp->exit(); // unsafe, but exit anyway
|
||||
}
|
||||
|
||||
void sigAbnormalHandler(int signum)
|
||||
{
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
const char str1[] = "\n\n*************************************************************\nCatching signal: ";
|
||||
const char *sigName = sysSigName[signum];
|
||||
const char str2[] = "\nPlease file a bug report at http://bug.qbittorrent.org and provide the following information:\n\n"
|
||||
"qBittorrent version: " VERSION "\n";
|
||||
write(STDERR_FILENO, str1, strlen(str1));
|
||||
write(STDERR_FILENO, sigName, strlen(sigName));
|
||||
write(STDERR_FILENO, str2, strlen(str2));
|
||||
print_stacktrace(); // unsafe
|
||||
#endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
#ifdef STACKTRACE_WIN
|
||||
StraceDlg dlg;
|
||||
StraceDlg dlg; // unsafe
|
||||
dlg.setStacktraceString(straceWin::getBacktrace());
|
||||
dlg.exec();
|
||||
#endif
|
||||
#endif
|
||||
raise(SIGSEGV);
|
||||
#endif // STACKTRACE_WIN
|
||||
signal(signum, SIG_DFL);
|
||||
raise(signum);
|
||||
}
|
||||
|
||||
void sigabrtHandler(int)
|
||||
{
|
||||
signal(SIGABRT, 0);
|
||||
signal(SIGSEGV, 0);
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
std::cerr << "\n\n*************************************************************\n";
|
||||
std::cerr << "Catching SIGABRT, please report a bug at http://bug.qbittorrent.org\nand provide the following backtrace:\n";
|
||||
std::cerr << "qBittorrent version: " << VERSION << std::endl;
|
||||
print_stacktrace();
|
||||
#else
|
||||
#ifdef STACKTRACE_WIN
|
||||
StraceDlg dlg;
|
||||
dlg.setStacktraceString(straceWin::getBacktrace());
|
||||
dlg.exec();
|
||||
#endif
|
||||
#endif
|
||||
raise(SIGABRT);
|
||||
}
|
||||
#endif
|
||||
#endif // defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
void showSplashScreen()
|
||||
@@ -365,9 +368,9 @@ void showSplashScreen()
|
||||
painter.setPen(QPen(Qt::white));
|
||||
painter.setFont(QFont("Arial", 22, QFont::Black));
|
||||
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
|
||||
QSplashScreen *splash = new QSplashScreen(splash_img, Qt::WindowStaysOnTopHint);
|
||||
QTimer::singleShot(1500, splash, SLOT(deleteLater()));
|
||||
QSplashScreen *splash = new QSplashScreen(splash_img);
|
||||
splash->show();
|
||||
QTimer::singleShot(1500, splash, SLOT(deleteLater()));
|
||||
qApp->processEvents();
|
||||
}
|
||||
#endif
|
||||
|
||||
32
src/app/qtsingleapplication/CMakeLists.txt
Normal file
32
src/app/qtsingleapplication/CMakeLists.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
set(QBT_QTSINGLEAPPLICATION_HEADERS
|
||||
qtlocalpeer.h
|
||||
)
|
||||
|
||||
set(QBT_QTSINGLEAPPLICATION_SOURCES
|
||||
qtlocalpeer.cpp
|
||||
)
|
||||
|
||||
if (GUI)
|
||||
list(APPEND QBT_QTSINGLEAPPLICATION_HEADERS qtsingleapplication.h)
|
||||
list(APPEND QBT_QTSINGLEAPPLICATION_SOURCES qtsingleapplication.cpp)
|
||||
else (GUI)
|
||||
list(APPEND QBT_QTSINGLEAPPLICATION_HEADERS qtsinglecoreapplication.h)
|
||||
list(APPEND QBT_QTSINGLEAPPLICATION_SOURCES qtsinglecoreapplication.cpp)
|
||||
endif (GUI)
|
||||
|
||||
add_library(qtsingleapplication ${QBT_QTSINGLEAPPLICATION_HEADERS} ${QBT_QTSINGLEAPPLICATION_SOURCES})
|
||||
|
||||
if (QT4_FOUND)
|
||||
target_link_libraries(qtsingleapplication Qt4::QtNetwork)
|
||||
else (QT4_FOUND)
|
||||
target_link_libraries(qtsingleapplication Qt5::Network)
|
||||
endif (QT4_FOUND)
|
||||
|
||||
if (GUI)
|
||||
if (QT4_FOUND)
|
||||
target_link_libraries(qtsingleapplication Qt4::QtGui)
|
||||
else (QT4_FOUND)
|
||||
target_link_libraries(qtsingleapplication Qt5::Widgets)
|
||||
endif(QT4_FOUND)
|
||||
endif (GUI)
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef STACKTRACE_WIN_H
|
||||
#define STACKTRACE_WIN_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <stdio.h>
|
||||
@@ -256,9 +259,9 @@ const QString straceWin::getBacktrace()
|
||||
}
|
||||
}
|
||||
|
||||
logStream << "\n\nList of linked Modules:\n";
|
||||
EnumModulesContext modulesContext(hProcess, logStream);
|
||||
SymEnumerateModules64(hProcess, EnumModulesCB, (PVOID)&modulesContext);
|
||||
//logStream << "\n\nList of linked Modules:\n";
|
||||
//EnumModulesContext modulesContext(hProcess, logStream);
|
||||
//SymEnumerateModules64(hProcess, EnumModulesCB, (PVOID)&modulesContext);
|
||||
logStream << "```";
|
||||
return log;
|
||||
}
|
||||
@@ -266,3 +269,5 @@ const QString straceWin::getBacktrace()
|
||||
#pragma warning(pop)
|
||||
#pragma optimize("g", on)
|
||||
#endif
|
||||
|
||||
#endif // STACKTRACE_WIN_H
|
||||
|
||||
@@ -1,51 +1,77 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 The qBittorrent project
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STACKTRACE_WIN_DLG_H
|
||||
#define STACKTRACE_WIN_DLG_H
|
||||
|
||||
#include <QTextStream>
|
||||
#include <QClipboard>
|
||||
#include "boost/version.hpp"
|
||||
#include "libtorrent/version.hpp"
|
||||
#include <QString>
|
||||
#include <QDialog>
|
||||
#include "base/utils/misc.h"
|
||||
#include "ui_stacktrace_win_dlg.h"
|
||||
|
||||
class StraceDlg: public QDialog, private Ui::errorDialog
|
||||
class StraceDlg : public QDialog, private Ui::errorDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StraceDlg(QWidget* parent = 0): QDialog(parent)
|
||||
StraceDlg(QWidget* parent = 0)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setupUi(this);
|
||||
}
|
||||
|
||||
~StraceDlg()
|
||||
{
|
||||
}
|
||||
|
||||
void setStacktraceString(const QString& trace)
|
||||
{
|
||||
QString htmlStr;
|
||||
QTextStream outStream(&htmlStr);
|
||||
outStream << "<p align=center><b><font size=7 color=red>" <<
|
||||
"qBittorrent has crashed" <<
|
||||
"</font></b></p>" <<
|
||||
"<font size=4>" <<
|
||||
"<p>" <<
|
||||
"Please report a bug at <a href=\"http://bugs.qbittorrent.org\">" <<
|
||||
"http://bugs.qbittorrent.org</a>" <<
|
||||
" and provide the following backtrace." <<
|
||||
"</p>" <<
|
||||
"</font>" <<
|
||||
"<br/><hr><br/>" <<
|
||||
"<p align=center><font size=4>qBittorrent version: " << VERSION <<
|
||||
"<br/>Libtorrent version: " << LIBTORRENT_VERSION <<
|
||||
"<br/>Qt version: " << QT_VERSION_STR <<
|
||||
"<br/>Boost version: " << QString::number(BOOST_VERSION / 100000) << '.' <<
|
||||
QString::number((BOOST_VERSION / 100) % 1000) << '.' <<
|
||||
QString::number(BOOST_VERSION % 100) << "</font></p><br/>"
|
||||
"<pre><code>" <<
|
||||
trace <<
|
||||
"</code></pre>" <<
|
||||
"<br/><hr><br/><br/>";
|
||||
// try to call Qt function as less as possible
|
||||
QString htmlStr = QString(
|
||||
"<p align=center><b><font size=7 color=red>"
|
||||
"qBittorrent has crashed"
|
||||
"</font></b></p>"
|
||||
"<font size=4><p>"
|
||||
"Please file a bug report at "
|
||||
"<a href=\"http://bugs.qbittorrent.org\">http://bugs.qbittorrent.org</a> "
|
||||
"and provide the following information:"
|
||||
"</p></font>"
|
||||
"<br/><hr><br/>"
|
||||
"<p align=center><font size=4>"
|
||||
"qBittorrent version: " VERSION "<br/>"
|
||||
"Libtorrent version: %1<br/>"
|
||||
"Qt version: " QT_VERSION_STR "<br/>"
|
||||
"Boost version: %2<br/>"
|
||||
"OS version: %3"
|
||||
"</font></p><br/>"
|
||||
"<pre><code>%4</code></pre>"
|
||||
"<br/><hr><br/><br/>")
|
||||
.arg(Utils::Misc::libtorrentVersionString())
|
||||
.arg(Utils::Misc::boostVersionString())
|
||||
.arg(Utils::Misc::osName())
|
||||
.arg(trace);
|
||||
|
||||
errorText->setHtml(htmlStr);
|
||||
}
|
||||
|
||||
@@ -36,13 +36,17 @@
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QRegExp>
|
||||
#ifndef DISABLE_GUI
|
||||
#include <QMessageBox>
|
||||
#endif
|
||||
|
||||
#include "core/logger.h"
|
||||
#include "core/utils/fs.h"
|
||||
#include "core/utils/misc.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "base/qinisettings.h"
|
||||
#include "base/preferences.h"
|
||||
|
||||
bool userAcceptsUpgrade()
|
||||
{
|
||||
@@ -73,7 +77,7 @@ bool userAcceptsUpgrade()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool upgradeResumeFile(const QString &filepath)
|
||||
bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent = QVariantHash())
|
||||
{
|
||||
QFile file1(filepath);
|
||||
if (!file1.open(QIODevice::ReadOnly))
|
||||
@@ -85,18 +89,37 @@ bool upgradeResumeFile(const QString &filepath)
|
||||
libtorrent::lazy_entry fastOld;
|
||||
libtorrent::error_code ec;
|
||||
libtorrent::lazy_bdecode(data.constData(), data.constData() + data.size(), fastOld, ec);
|
||||
if ((fastOld.type() != libtorrent::lazy_entry::dict_t) && !ec) return false;
|
||||
if (ec || (fastOld.type() != libtorrent::lazy_entry::dict_t)) return false;
|
||||
|
||||
libtorrent::entry fastNew;
|
||||
fastNew = fastOld;
|
||||
|
||||
int priority = fastOld.dict_find_int_value("qBt-queuePosition");
|
||||
QFile file2(QString("%1.%2").arg(filepath).arg(priority > 0 ? priority : 0));
|
||||
bool v3_3 = false;
|
||||
int queuePosition = 0;
|
||||
QString outFilePath = filepath;
|
||||
QRegExp rx(QLatin1String("([A-Fa-f0-9]{40})\\.fastresume\\.(\\d+)$"));
|
||||
if (rx.indexIn(filepath) != -1) {
|
||||
// old v3.3.x format
|
||||
queuePosition = rx.cap(2).toInt();
|
||||
v3_3 = true;
|
||||
outFilePath.replace(QRegExp("\\.\\d+$"), "");
|
||||
}
|
||||
else {
|
||||
queuePosition = fastOld.dict_find_int_value("qBt-queuePosition", 0);
|
||||
fastNew["qBt-name"] = Utils::String::toStdString(oldTorrent.value("name").toString());
|
||||
fastNew["qBt-tempPathDisabled"] = false;
|
||||
}
|
||||
|
||||
// in versions < 3.3 we have -1 for seeding torrents, so we convert it to 0
|
||||
fastNew["qBt-queuePosition"] = (queuePosition >= 0 ? queuePosition : 0);
|
||||
|
||||
QFile file2(outFilePath);
|
||||
QVector<char> out;
|
||||
libtorrent::bencode(std::back_inserter(out), fastNew);
|
||||
if (file2.open(QIODevice::WriteOnly)) {
|
||||
if (file2.write(&out[0], out.size()) == out.size()) {
|
||||
Utils::Fs::forceRemove(filepath);
|
||||
if (v3_3)
|
||||
Utils::Fs::forceRemove(filepath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -106,26 +129,84 @@ bool upgradeResumeFile(const QString &filepath)
|
||||
|
||||
bool upgrade(bool ask = true)
|
||||
{
|
||||
// Upgrade preferences
|
||||
Preferences::instance()->upgrade();
|
||||
|
||||
QString backupFolderPath = Utils::Fs::expandPathAbs(Utils::Fs::QDesktopServicesDataLocation() + "BT_backup");
|
||||
QDir backupFolderDir(backupFolderPath);
|
||||
if (!backupFolderDir.exists()) return true;
|
||||
|
||||
QStringList backupFiles = backupFolderDir.entryList(QStringList() << QLatin1String("*.fastresume"), QDir::Files, QDir::Unsorted);
|
||||
if (!backupFiles.isEmpty()) {
|
||||
if (ask && !userAcceptsUpgrade()) return false;
|
||||
// ****************************************************************************************
|
||||
// Silently converts old v3.3.x .fastresume files
|
||||
QStringList backupFiles_3_3 = backupFolderDir.entryList(
|
||||
QStringList(QLatin1String("*.fastresume.*")), QDir::Files, QDir::Unsorted);
|
||||
foreach (const QString &backupFile, backupFiles_3_3)
|
||||
upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile));
|
||||
// ****************************************************************************************
|
||||
|
||||
QRegExp rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
|
||||
foreach (QString backupFile, backupFiles) {
|
||||
if (rx.indexIn(backupFile) != -1) {
|
||||
if (!upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile)))
|
||||
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent with hash: %1").arg(rx.cap(1)), Log::WARNING);
|
||||
}
|
||||
else {
|
||||
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent. Invalid fastresume file name: %1").arg(backupFile), Log::WARNING);
|
||||
}
|
||||
QIniSettings *oldResumeSettings = new QIniSettings("qBittorrent", "qBittorrent-resume");
|
||||
QString oldResumeFilename = oldResumeSettings->fileName();
|
||||
QVariantHash oldResumeData = oldResumeSettings->value("torrents").toHash();
|
||||
delete oldResumeSettings;
|
||||
|
||||
if (oldResumeData.isEmpty()) {
|
||||
Utils::Fs::forceRemove(oldResumeFilename);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ask && !userAcceptsUpgrade()) return false;
|
||||
|
||||
QStringList backupFiles = backupFolderDir.entryList(
|
||||
QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted);
|
||||
QRegExp rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
|
||||
foreach (QString backupFile, backupFiles) {
|
||||
if (rx.indexIn(backupFile) != -1) {
|
||||
if (upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile), oldResumeData[rx.cap(1)].toHash()))
|
||||
oldResumeData.remove(rx.cap(1));
|
||||
else
|
||||
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent with hash: %1").arg(rx.cap(1)), Log::WARNING);
|
||||
}
|
||||
else {
|
||||
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent. Invalid fastresume file name: %1").arg(backupFile), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const QString &hash, oldResumeData.keys()) {
|
||||
QVariantHash oldTorrent = oldResumeData[hash].toHash();
|
||||
if (oldTorrent.value("is_magnet", false).toBool()) {
|
||||
libtorrent::entry resumeData;
|
||||
resumeData["qBt-magnetUri"] = Utils::String::toStdString(oldTorrent.value("magnet_uri").toString());
|
||||
resumeData["qBt-paused"] = false;
|
||||
resumeData["qBt-forced"] = false;
|
||||
|
||||
resumeData["qBt-savePath"] = Utils::String::toStdString(oldTorrent.value("save_path").toString());
|
||||
resumeData["qBt-ratioLimit"] = Utils::String::toStdString(QString::number(oldTorrent.value("max_ratio", -2).toReal()));
|
||||
resumeData["qBt-label"] = Utils::String::toStdString(oldTorrent.value("label").toString());
|
||||
resumeData["qBt-name"] = Utils::String::toStdString(oldTorrent.value("name").toString());
|
||||
resumeData["qBt-seedStatus"] = oldTorrent.value("seed").toBool();
|
||||
resumeData["qBt-tempPathDisabled"] = false;
|
||||
|
||||
int queuePosition = oldTorrent.value("priority", 0).toInt();
|
||||
resumeData["qBt-queuePosition"] = (queuePosition >= 0 ? queuePosition : 0);
|
||||
|
||||
QString filename = QString("%1.fastresume").arg(hash);
|
||||
QString filepath = backupFolderDir.absoluteFilePath(filename);
|
||||
|
||||
QFile resumeFile(filepath);
|
||||
QVector<char> out;
|
||||
libtorrent::bencode(std::back_inserter(out), resumeData);
|
||||
if (resumeFile.open(QIODevice::WriteOnly))
|
||||
resumeFile.write(&out[0], out.size());
|
||||
}
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
QString backupResumeFilename = oldResumeFilename + ".bak";
|
||||
while (QFile::exists(backupResumeFilename)) {
|
||||
++counter;
|
||||
backupResumeFilename = oldResumeFilename + ".bak" + QString::number(counter);
|
||||
}
|
||||
QFile::rename(oldResumeFilename, backupResumeFilename);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
126
src/base/CMakeLists.txt
Normal file
126
src/base/CMakeLists.txt
Normal file
@@ -0,0 +1,126 @@
|
||||
find_package(ZLIB REQUIRED)
|
||||
include_directories(SYSTEM ${ZLIB_INCLUDE_DIRS})
|
||||
|
||||
set(QBT_BASE_HEADERS
|
||||
bittorrent/cachestatus.h
|
||||
bittorrent/infohash.h
|
||||
bittorrent/magneturi.h
|
||||
bittorrent/peerinfo.h
|
||||
bittorrent/private/bandwidthscheduler.h
|
||||
bittorrent/private/filterparserthread.h
|
||||
bittorrent/private/resumedatasavingmanager.h
|
||||
bittorrent/private/speedmonitor.h
|
||||
bittorrent/private/statistics.h
|
||||
bittorrent/session.h
|
||||
bittorrent/sessionstatus.h
|
||||
bittorrent/torrentcreatorthread.h
|
||||
bittorrent/torrenthandle.h
|
||||
bittorrent/torrentinfo.h
|
||||
bittorrent/tracker.h
|
||||
bittorrent/trackerentry.h
|
||||
http/connection.h
|
||||
http/irequesthandler.h
|
||||
http/requestparser.h
|
||||
http/responsebuilder.h
|
||||
http/responsegenerator.h
|
||||
http/server.h
|
||||
http/types.h
|
||||
net/dnsupdater.h
|
||||
net/downloadhandler.h
|
||||
net/downloadmanager.h
|
||||
net/geoipmanager.h
|
||||
net/portforwarder.h
|
||||
net/private/geoipdatabase.h
|
||||
net/reverseresolution.h
|
||||
net/smtp.h
|
||||
rss/private/rssparser.h
|
||||
rss/rssarticle.h
|
||||
rss/rssdownloadrule.h
|
||||
rss/rssdownloadrulelist.h
|
||||
rss/rssfeed.h
|
||||
rss/rssfile.h
|
||||
rss/rssfolder.h
|
||||
rss/rssmanager.h
|
||||
utils/fs.h
|
||||
utils/gzip.h
|
||||
utils/misc.h
|
||||
utils/string.h
|
||||
filesystemwatcher.h
|
||||
iconprovider.h
|
||||
logger.h
|
||||
preferences.h
|
||||
qinisettings.h
|
||||
scanfoldersmodel.h
|
||||
searchengine.h
|
||||
settingsstorage.h
|
||||
torrentfilter.h
|
||||
tristatebool.h
|
||||
types.h
|
||||
unicodestrings.h
|
||||
)
|
||||
|
||||
set(QBT_BASE_SOURCES
|
||||
bittorrent/cachestatus.cpp
|
||||
bittorrent/infohash.cpp
|
||||
bittorrent/magneturi.cpp
|
||||
bittorrent/peerinfo.cpp
|
||||
bittorrent/private/bandwidthscheduler.cpp
|
||||
bittorrent/private/filterparserthread.cpp
|
||||
bittorrent/private/resumedatasavingmanager.cpp
|
||||
bittorrent/private/speedmonitor.cpp
|
||||
bittorrent/private/statistics.cpp
|
||||
bittorrent/session.cpp
|
||||
bittorrent/sessionstatus.cpp
|
||||
bittorrent/torrentcreatorthread.cpp
|
||||
bittorrent/torrenthandle.cpp
|
||||
bittorrent/torrentinfo.cpp
|
||||
bittorrent/tracker.cpp
|
||||
bittorrent/trackerentry.cpp
|
||||
http/connection.cpp
|
||||
http/requestparser.cpp
|
||||
http/responsebuilder.cpp
|
||||
http/responsegenerator.cpp
|
||||
http/server.cpp
|
||||
net/dnsupdater.cpp
|
||||
net/downloadhandler.cpp
|
||||
net/downloadmanager.cpp
|
||||
net/geoipmanager.cpp
|
||||
net/portforwarder.cpp
|
||||
net/private/geoipdatabase.cpp
|
||||
net/reverseresolution.cpp
|
||||
net/smtp.cpp
|
||||
rss/private/rssparser.cpp
|
||||
rss/rssarticle.cpp
|
||||
rss/rssdownloadrule.cpp
|
||||
rss/rssdownloadrulelist.cpp
|
||||
rss/rssfeed.cpp
|
||||
rss/rssfile.cpp
|
||||
rss/rssfolder.cpp
|
||||
rss/rssmanager.cpp
|
||||
utils/fs.cpp
|
||||
utils/gzip.cpp
|
||||
utils/misc.cpp
|
||||
utils/string.cpp
|
||||
filesystemwatcher.cpp
|
||||
iconprovider.cpp
|
||||
logger.cpp
|
||||
preferences.cpp
|
||||
scanfoldersmodel.cpp
|
||||
searchengine.cpp
|
||||
settingsstorage.cpp
|
||||
torrentfilter.cpp
|
||||
tristatebool.cpp
|
||||
)
|
||||
|
||||
add_library(qbt_base STATIC ${QBT_BASE_HEADERS} ${QBT_BASE_SOURCES})
|
||||
target_link_libraries(qbt_base ${ZLIB_LIBRARIES} ${LibtorrentRasterbar_LIBRARIES})
|
||||
target_link_qt_components(qbt_base Core Network Xml)
|
||||
if (QT4_FOUND)
|
||||
if (GUI)
|
||||
target_link_libraries(qbt_base Qt4::QtGui)
|
||||
endif (GUI)
|
||||
else (QT4_FOUND)
|
||||
if (GUI)
|
||||
target_link_libraries(qbt_base Qt5::Gui Qt5::Widgets)
|
||||
endif (GUI)
|
||||
endif (QT4_FOUND)
|
||||
@@ -4,6 +4,7 @@ HEADERS += \
|
||||
$$PWD/filesystemwatcher.h \
|
||||
$$PWD/qinisettings.h \
|
||||
$$PWD/logger.h \
|
||||
$$PWD/settingsstorage.h \
|
||||
$$PWD/preferences.h \
|
||||
$$PWD/iconprovider.h \
|
||||
$$PWD/http/irequesthandler.h \
|
||||
@@ -36,18 +37,29 @@ HEADERS += \
|
||||
$$PWD/bittorrent/private/bandwidthscheduler.h \
|
||||
$$PWD/bittorrent/private/filterparserthread.h \
|
||||
$$PWD/bittorrent/private/statistics.h \
|
||||
$$PWD/bittorrent/private/resumedatasavingmanager.h \
|
||||
$$PWD/rss/rssmanager.h \
|
||||
$$PWD/rss/rssfeed.h \
|
||||
$$PWD/rss/rssfolder.h \
|
||||
$$PWD/rss/rssfile.h \
|
||||
$$PWD/rss/rssarticle.h \
|
||||
$$PWD/rss/rssdownloadrule.h \
|
||||
$$PWD/rss/rssdownloadrulelist.h \
|
||||
$$PWD/rss/private/rssparser.h \
|
||||
$$PWD/utils/fs.h \
|
||||
$$PWD/utils/gzip.h \
|
||||
$$PWD/utils/misc.h \
|
||||
$$PWD/utils/string.h \
|
||||
$$PWD/unicodestrings.h \
|
||||
$$PWD/torrentfilter.h \
|
||||
$$PWD/scanfoldersmodel.h
|
||||
$$PWD/scanfoldersmodel.h \
|
||||
$$PWD/searchengine.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/tristatebool.cpp \
|
||||
$$PWD/filesystemwatcher.cpp \
|
||||
$$PWD/logger.cpp \
|
||||
$$PWD/settingsstorage.cpp \
|
||||
$$PWD/preferences.cpp \
|
||||
$$PWD/iconprovider.cpp \
|
||||
$$PWD/http/connection.cpp \
|
||||
@@ -78,9 +90,19 @@ SOURCES += \
|
||||
$$PWD/bittorrent/private/bandwidthscheduler.cpp \
|
||||
$$PWD/bittorrent/private/filterparserthread.cpp \
|
||||
$$PWD/bittorrent/private/statistics.cpp \
|
||||
$$PWD/bittorrent/private/resumedatasavingmanager.cpp \
|
||||
$$PWD/rss/rssmanager.cpp \
|
||||
$$PWD/rss/rssfeed.cpp \
|
||||
$$PWD/rss/rssfolder.cpp \
|
||||
$$PWD/rss/rssarticle.cpp \
|
||||
$$PWD/rss/rssdownloadrule.cpp \
|
||||
$$PWD/rss/rssdownloadrulelist.cpp \
|
||||
$$PWD/rss/rssfile.cpp \
|
||||
$$PWD/rss/private/rssparser.cpp \
|
||||
$$PWD/utils/fs.cpp \
|
||||
$$PWD/utils/gzip.cpp \
|
||||
$$PWD/utils/misc.cpp \
|
||||
$$PWD/utils/string.cpp \
|
||||
$$PWD/torrentfilter.cpp \
|
||||
$$PWD/scanfoldersmodel.cpp
|
||||
$$PWD/scanfoldersmodel.cpp \
|
||||
$$PWD/searchengine.cpp
|
||||
@@ -26,6 +26,7 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <libtorrent/version.hpp>
|
||||
#include "cachestatus.h"
|
||||
|
||||
using namespace BitTorrent;
|
||||
@@ -50,7 +51,11 @@ qreal CacheStatus::readRatio() const
|
||||
|
||||
int CacheStatus::jobQueueLength() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
return m_nativeStatus.job_queue_length;
|
||||
#else
|
||||
return m_nativeStatus.queued_jobs;
|
||||
#endif
|
||||
}
|
||||
|
||||
int CacheStatus::averageJobTime() const
|
||||
@@ -29,8 +29,8 @@
|
||||
#ifndef BITTORRENT_CACHESTATUS_H
|
||||
#define BITTORRENT_CACHESTATUS_H
|
||||
|
||||
#include <libtorrent/disk_io_thread.hpp>
|
||||
#include <QtGlobal>
|
||||
#include <libtorrent/disk_io_thread.hpp>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
@@ -26,24 +26,56 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/error_code.hpp>
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
|
||||
#include "core/utils/string.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "magneturi.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
QString bcLinkToMagnet(QString bcLink)
|
||||
{
|
||||
QByteArray rawBc = bcLink.toUtf8();
|
||||
rawBc = rawBc.mid(8); // skip bc://bt/
|
||||
rawBc = QByteArray::fromBase64(rawBc); // Decode base64
|
||||
// Format is now AA/url_encoded_filename/size_bytes/info_hash/ZZ
|
||||
QStringList parts = QString(rawBc).split("/");
|
||||
if (parts.size() != 5) return QString();
|
||||
|
||||
QString filename = parts.at(1);
|
||||
QString hash = parts.at(3);
|
||||
QString magnet = "magnet:?xt=urn:btih:" + hash;
|
||||
magnet += "&dn=" + filename;
|
||||
return magnet;
|
||||
}
|
||||
}
|
||||
|
||||
namespace libt = libtorrent;
|
||||
using namespace BitTorrent;
|
||||
|
||||
MagnetUri::MagnetUri(const QString &url)
|
||||
MagnetUri::MagnetUri(const QString &source)
|
||||
: m_valid(false)
|
||||
, m_url(url)
|
||||
, m_url(source)
|
||||
{
|
||||
if (url.isEmpty()) return;
|
||||
if (source.isEmpty()) return;
|
||||
|
||||
if (source.startsWith("bc://bt/", Qt::CaseInsensitive)) {
|
||||
qDebug("Creating magnet link from bc link");
|
||||
m_url = bcLinkToMagnet(source);
|
||||
}
|
||||
else if (((source.size() == 40) && !source.contains(QRegExp("[^0-9A-Fa-f]")))
|
||||
|| ((source.size() == 32) && !source.contains(QRegExp("[^2-7A-Za-z]")))) {
|
||||
m_url = "magnet:?xt=urn:btih:" + source;
|
||||
}
|
||||
|
||||
libt::error_code ec;
|
||||
libt::parse_magnet_uri(url.toUtf8().constData(), m_addTorrentParams, ec);
|
||||
libt::parse_magnet_uri(m_url.toUtf8().constData(), m_addTorrentParams, ec);
|
||||
if (ec) return;
|
||||
|
||||
m_valid = true;
|
||||
@@ -43,7 +43,7 @@ namespace BitTorrent
|
||||
class MagnetUri
|
||||
{
|
||||
public:
|
||||
explicit MagnetUri(const QString &url = QString());
|
||||
explicit MagnetUri(const QString &source = QString());
|
||||
|
||||
bool isValid() const;
|
||||
InfoHash hash() const;
|
||||
@@ -26,9 +26,10 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "core/net/geoipmanager.h"
|
||||
#include "core/utils/string.h"
|
||||
#include "core/unicodestrings.h"
|
||||
#include "base/net/geoipmanager.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "base/unicodestrings.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "peerinfo.h"
|
||||
|
||||
namespace libt = libtorrent;
|
||||
@@ -49,9 +50,11 @@ PeerAddress::PeerAddress(QHostAddress ip, ushort port)
|
||||
|
||||
// PeerInfo
|
||||
|
||||
PeerInfo::PeerInfo(const libt::peer_info &nativeInfo)
|
||||
PeerInfo::PeerInfo(const TorrentHandle *torrent, const libt::peer_info &nativeInfo)
|
||||
: m_nativeInfo(nativeInfo)
|
||||
{
|
||||
calcRelevance(torrent);
|
||||
determineFlags();
|
||||
}
|
||||
|
||||
bool PeerInfo::fromDHT() const
|
||||
@@ -253,3 +256,160 @@ QString PeerInfo::connectionType() const
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
void PeerInfo::calcRelevance(const TorrentHandle *torrent)
|
||||
{
|
||||
const QBitArray &allPieces = torrent->pieces();
|
||||
const QBitArray &peerPieces = pieces();
|
||||
|
||||
int localMissing = 0;
|
||||
int remoteHaves = 0;
|
||||
|
||||
for (int i = 0; i < allPieces.size(); ++i) {
|
||||
if (!allPieces[i]) {
|
||||
++localMissing;
|
||||
if (peerPieces[i])
|
||||
++remoteHaves;
|
||||
}
|
||||
}
|
||||
|
||||
if (localMissing == 0)
|
||||
m_relevance = 0.0;
|
||||
else
|
||||
m_relevance = static_cast<qreal>(remoteHaves) / localMissing;
|
||||
}
|
||||
|
||||
qreal PeerInfo::relevance() const
|
||||
{
|
||||
return m_relevance;
|
||||
}
|
||||
|
||||
void PeerInfo::determineFlags()
|
||||
{
|
||||
if (isInteresting()) {
|
||||
//d = Your client wants to download, but peer doesn't want to send (interested and choked)
|
||||
if (isRemoteChocked()) {
|
||||
m_flags += "d ";
|
||||
m_flagsDescription += tr("interested(local) and choked(peer)");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
else {
|
||||
//D = Currently downloading (interested and not choked)
|
||||
m_flags += "D ";
|
||||
m_flagsDescription += tr("interested(local) and unchoked(peer)");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
if (isRemoteInterested()) {
|
||||
//u = Peer wants your client to upload, but your client doesn't want to (interested and choked)
|
||||
if (isChocked()) {
|
||||
m_flags += "u ";
|
||||
m_flagsDescription += tr("interested(peer) and choked(local)");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
else {
|
||||
//U = Currently uploading (interested and not choked)
|
||||
m_flags += "U ";
|
||||
m_flagsDescription += tr("interested(peer) and unchoked(local)");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
//O = Optimistic unchoke
|
||||
if (optimisticUnchoke()) {
|
||||
m_flags += "O ";
|
||||
m_flagsDescription += tr("optimistic unchoke");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//S = Peer is snubbed
|
||||
if (isSnubbed()) {
|
||||
m_flags += "S ";
|
||||
m_flagsDescription += tr("peer snubbed");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//I = Peer is an incoming connection
|
||||
if (!isLocalConnection()) {
|
||||
m_flags += "I ";
|
||||
m_flagsDescription += tr("incoming connection");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//K = Peer is unchoking your client, but your client is not interested
|
||||
if (!isRemoteChocked() && !isInteresting()) {
|
||||
m_flags += "K ";
|
||||
m_flagsDescription += tr("not interested(local) and unchoked(peer)");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//? = Your client unchoked the peer but the peer is not interested
|
||||
if (!isChocked() && !isRemoteInterested()) {
|
||||
m_flags += "? ";
|
||||
m_flagsDescription += tr("not interested(peer) and unchoked(local)");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//X = Peer was included in peerlists obtained through Peer Exchange (PEX)
|
||||
if (fromPeX()) {
|
||||
m_flags += "X ";
|
||||
m_flagsDescription += tr("peer from PEX");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//H = Peer was obtained through DHT
|
||||
if (fromDHT()) {
|
||||
m_flags += "H ";
|
||||
m_flagsDescription += tr("peer from DHT");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//E = Peer is using Protocol Encryption (all traffic)
|
||||
if (isRC4Encrypted()) {
|
||||
m_flags += "E ";
|
||||
m_flagsDescription += tr("encrypted traffic");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//e = Peer is using Protocol Encryption (handshake)
|
||||
if (isPlaintextEncrypted()) {
|
||||
m_flags += "e ";
|
||||
m_flagsDescription += tr("encrypted handshake");
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//P = Peer is using uTorrent uTP
|
||||
|
||||
if (useUTPSocket()) {
|
||||
m_flags += "P ";
|
||||
m_flagsDescription += QString::fromUtf8(C_UTP);
|
||||
m_flagsDescription += ", ";
|
||||
}
|
||||
|
||||
//L = Peer is local
|
||||
if (fromLSD()) {
|
||||
m_flags += "L";
|
||||
m_flagsDescription += tr("peer from LSD");
|
||||
}
|
||||
|
||||
m_flags = m_flags.trimmed();
|
||||
m_flagsDescription = m_flagsDescription.trimmed();
|
||||
if (m_flagsDescription.endsWith(',', Qt::CaseInsensitive))
|
||||
m_flagsDescription.chop(1);
|
||||
}
|
||||
|
||||
QString PeerInfo::flags() const
|
||||
{
|
||||
return m_flags;
|
||||
}
|
||||
|
||||
QString PeerInfo::flagsDescription() const
|
||||
{
|
||||
return m_flagsDescription;
|
||||
}
|
||||
|
||||
int PeerInfo::downloadingPieceIndex() const
|
||||
{
|
||||
return m_nativeInfo.downloading_piece_index;
|
||||
}
|
||||
@@ -33,9 +33,12 @@
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QBitArray>
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentHandle;
|
||||
|
||||
struct PeerAddress
|
||||
{
|
||||
QHostAddress ip;
|
||||
@@ -47,8 +50,10 @@ namespace BitTorrent
|
||||
|
||||
class PeerInfo
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(PeerInfo)
|
||||
|
||||
public:
|
||||
PeerInfo(const libtorrent::peer_info &nativeInfo);
|
||||
PeerInfo(const TorrentHandle *torrent, const libtorrent::peer_info &nativeInfo);
|
||||
|
||||
bool fromDHT() const;
|
||||
bool fromPeX() const;
|
||||
@@ -89,12 +94,22 @@ namespace BitTorrent
|
||||
qlonglong totalDownload() const;
|
||||
QBitArray pieces() const;
|
||||
QString connectionType() const;
|
||||
qreal relevance() const;
|
||||
QString flags() const;
|
||||
QString flagsDescription() const;
|
||||
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||
QString country() const;
|
||||
#endif
|
||||
int downloadingPieceIndex() const;
|
||||
|
||||
private:
|
||||
void calcRelevance(const TorrentHandle *torrent);
|
||||
void determineFlags();
|
||||
|
||||
libtorrent::peer_info m_nativeInfo;
|
||||
qreal m_relevance;
|
||||
QString m_flags;
|
||||
QString m_flagsDescription;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/preferences.h"
|
||||
#include "base/preferences.h"
|
||||
#include "bandwidthscheduler.h"
|
||||
|
||||
BandwidthScheduler::BandwidthScheduler(QObject *parent)
|
||||
@@ -36,7 +36,7 @@
|
||||
#include <libtorrent/session.hpp>
|
||||
#include <libtorrent/ip_filter.hpp>
|
||||
|
||||
#include "core/logger.h"
|
||||
#include "base/logger.h"
|
||||
#include "filterparserthread.h"
|
||||
|
||||
namespace libt = libtorrent;
|
||||
65
src/base/bittorrent/private/resumedatasavingmanager.cpp
Normal file
65
src/base/bittorrent/private/resumedatasavingmanager.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#ifdef QBT_USES_QT5
|
||||
#include <QSaveFile>
|
||||
#else
|
||||
#include <QFile>
|
||||
#endif
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "resumedatasavingmanager.h"
|
||||
|
||||
ResumeDataSavingManager::ResumeDataSavingManager(const QString &resumeFolderPath)
|
||||
: m_resumeDataDir(resumeFolderPath)
|
||||
{
|
||||
}
|
||||
|
||||
void ResumeDataSavingManager::saveResumeData(QString infoHash, QByteArray data) const
|
||||
{
|
||||
QString filename = QString("%1.fastresume").arg(infoHash);
|
||||
QString filepath = m_resumeDataDir.absoluteFilePath(filename);
|
||||
|
||||
qDebug() << "Saving resume data in" << filepath;
|
||||
#ifdef QBT_USES_QT5
|
||||
QSaveFile resumeFile(filepath);
|
||||
#else
|
||||
QFile resumeFile(filepath);
|
||||
#endif
|
||||
if (resumeFile.open(QIODevice::WriteOnly)) {
|
||||
resumeFile.write(data);
|
||||
#ifdef QBT_USES_QT5
|
||||
if (!resumeFile.commit()) {
|
||||
Logger::instance()->addMessage(QString("Couldn't save resume data in %1. Error: %2")
|
||||
.arg(filepath).arg(resumeFile.errorString()), Log::WARNING);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
50
src/base/bittorrent/private/resumedatasavingmanager.h
Normal file
50
src/base/bittorrent/private/resumedatasavingmanager.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef RESUMEDATASAVINGMANAGER_H
|
||||
#define RESUMEDATASAVINGMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
|
||||
class ResumeDataSavingManager: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ResumeDataSavingManager(const QString &resumeFolderPath);
|
||||
|
||||
public slots:
|
||||
void saveResumeData(QString infoHash, QByteArray data) const;
|
||||
|
||||
private:
|
||||
QDir m_resumeDataDir;
|
||||
};
|
||||
|
||||
#endif // RESUMEDATASAVINGMANAGER_H
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
#include <libtorrent/session.hpp>
|
||||
|
||||
#include "core/qinisettings.h"
|
||||
#include "core/preferences.h"
|
||||
#include "core/bittorrent/sessionstatus.h"
|
||||
#include "core/bittorrent/session.h"
|
||||
#include "base/qinisettings.h"
|
||||
#include "base/bittorrent/sessionstatus.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "statistics.h"
|
||||
|
||||
static const qint64 SAVE_INTERVAL = 15 * 60 * 1000;
|
||||
@@ -76,40 +75,9 @@ void Statistics::save() const
|
||||
|
||||
void Statistics::load()
|
||||
{
|
||||
// Temp code. Versions v3.1.4 and v3.1.5 saved the data in the qbittorrent.ini file.
|
||||
// This code reads the data from there, writes it to the new file, and removes the keys
|
||||
// from the old file. This code should be removed after some time has passed.
|
||||
// e.g. When we reach v3.3.0
|
||||
// Don't forget to remove:
|
||||
// 1. Preferences::getStats()
|
||||
// 2. Preferences::removeStats()
|
||||
// 3. #include "core/preferences.h"
|
||||
Preferences* const pref = Preferences::instance();
|
||||
QIniSettings s("qBittorrent", "qBittorrent-data");
|
||||
QVariantHash v = pref->getStats();
|
||||
|
||||
// Let's test if the qbittorrent.ini holds the key
|
||||
if (!v.isEmpty()) {
|
||||
m_dirty = true;
|
||||
|
||||
// If the user has used qbt > 3.1.5 and then reinstalled/used
|
||||
// qbt < 3.1.6, there will be stats in qbittorrent-data.ini too
|
||||
// so we need to merge those 2.
|
||||
if (s.contains("Stats/AllStats")) {
|
||||
QVariantHash tmp = s.value("Stats/AllStats").toHash();
|
||||
v["AlltimeDL"] = v["AlltimeDL"].toULongLong() + tmp["AlltimeDL"].toULongLong();
|
||||
v["AlltimeUL"] = v["AlltimeUL"].toULongLong() + tmp["AlltimeUL"].toULongLong();
|
||||
}
|
||||
}
|
||||
else {
|
||||
v = s.value("Stats/AllStats").toHash();
|
||||
}
|
||||
QVariantHash v = s.value("Stats/AllStats").toHash();
|
||||
|
||||
m_alltimeDL = v["AlltimeDL"].toULongLong();
|
||||
m_alltimeUL = v["AlltimeUL"].toULongLong();
|
||||
|
||||
if (m_dirty) {
|
||||
save();
|
||||
pref->removeStats();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,26 +32,40 @@
|
||||
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QPointer>
|
||||
#include <QVector>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QNetworkConfigurationManager>
|
||||
|
||||
#include "core/tristatebool.h"
|
||||
#include "core/types.h"
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include "base/tristatebool.h"
|
||||
#include "base/types.h"
|
||||
#include "torrentinfo.h"
|
||||
|
||||
namespace libtorrent
|
||||
{
|
||||
class session;
|
||||
struct torrent_handle;
|
||||
class entry;
|
||||
struct add_torrent_params;
|
||||
struct pe_settings;
|
||||
struct proxy_settings;
|
||||
struct session_settings;
|
||||
struct session_status;
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
struct proxy_settings;
|
||||
#else
|
||||
namespace aux
|
||||
{
|
||||
struct proxy_settings;
|
||||
}
|
||||
|
||||
typedef aux::proxy_settings proxy_settings;
|
||||
#endif
|
||||
|
||||
class alert;
|
||||
struct torrent_alert;
|
||||
struct state_update_alert;
|
||||
@@ -86,6 +100,7 @@ namespace libtorrent
|
||||
struct external_ip_alert;
|
||||
}
|
||||
|
||||
class QThread;
|
||||
class QTimer;
|
||||
class QStringList;
|
||||
class QString;
|
||||
@@ -95,8 +110,20 @@ template<typename T> class QList;
|
||||
class FilterParserThread;
|
||||
class BandwidthScheduler;
|
||||
class Statistics;
|
||||
class ResumeDataSavingManager;
|
||||
class SettingsStorage;
|
||||
|
||||
typedef QPair<QString, QString> QStringPair;
|
||||
enum MaxRatioAction
|
||||
{
|
||||
Pause,
|
||||
Remove
|
||||
};
|
||||
|
||||
enum TorrentExportFolder
|
||||
{
|
||||
Regular,
|
||||
Finished
|
||||
};
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
@@ -112,17 +139,15 @@ namespace BitTorrent
|
||||
struct AddTorrentParams
|
||||
{
|
||||
QString name;
|
||||
QString label;
|
||||
QString category;
|
||||
QString savePath;
|
||||
bool disableTempPath; // e.g. for imported torrents
|
||||
bool sequential;
|
||||
bool disableTempPath = false; // e.g. for imported torrents
|
||||
bool sequential = false;
|
||||
TriStateBool addForced;
|
||||
TriStateBool addPaused;
|
||||
QVector<int> filePriorities; // used if TorrentInfo is set
|
||||
bool ignoreShareRatio;
|
||||
bool skipChecking;
|
||||
|
||||
AddTorrentParams();
|
||||
bool ignoreShareRatio = false;
|
||||
bool skipChecking = false;
|
||||
};
|
||||
|
||||
struct TorrentStatusReport
|
||||
@@ -152,11 +177,49 @@ namespace BitTorrent
|
||||
bool isPexEnabled() const;
|
||||
bool isQueueingEnabled() const;
|
||||
qreal globalMaxRatio() const;
|
||||
bool isTempPathEnabled() const;
|
||||
bool isAppendExtensionEnabled() const;
|
||||
bool useAppendLabelToSavePath() const;
|
||||
|
||||
QString defaultSavePath() const;
|
||||
void setDefaultSavePath(QString path);
|
||||
QString tempPath() const;
|
||||
void setTempPath(QString path);
|
||||
bool isTempPathEnabled() const;
|
||||
void setTempPathEnabled(bool enabled);
|
||||
|
||||
static bool isValidCategoryName(const QString &name);
|
||||
// returns category itself and all top level categories
|
||||
static QStringList expandCategory(const QString &category);
|
||||
|
||||
QStringList categories() const;
|
||||
QString categorySavePath(const QString &categoryName) const;
|
||||
bool addCategory(const QString &name, const QString &savePath = "");
|
||||
bool editCategory(const QString &name, const QString &savePath);
|
||||
bool removeCategory(const QString &name);
|
||||
bool isSubcategoriesEnabled() const;
|
||||
void setSubcategoriesEnabled(bool value);
|
||||
|
||||
// Advanced Saving Management subsystem (ASM)
|
||||
//
|
||||
// Each torrent can be either in Simple mode or in Advanced mode
|
||||
// In Simple mode torrent has explicit save path
|
||||
// In Advanced Mode torrent has implicit save path (based on Default
|
||||
// save path and Category save path)
|
||||
// In Advanced Mode torrent save path can be changed in following cases:
|
||||
// 1. Default save path changed
|
||||
// 2. Torrent category save path changed
|
||||
// 3. Torrent category changed
|
||||
// (unless otherwise is specified)
|
||||
bool isASMDisabledByDefault() const;
|
||||
void setASMDisabledByDefault(bool value);
|
||||
bool isDisableASMWhenCategoryChanged() const;
|
||||
void setDisableASMWhenCategoryChanged(bool value);
|
||||
bool isDisableASMWhenDefaultSavePathChanged() const;
|
||||
void setDisableASMWhenDefaultSavePathChanged(bool value);
|
||||
bool isDisableASMWhenCategorySavePathChanged() const;
|
||||
void setDisableASMWhenCategorySavePathChanged(bool value);
|
||||
|
||||
bool isAddTorrentPaused() const;
|
||||
void setAddTorrentPaused(bool value);
|
||||
|
||||
TorrentHandle *findTorrent(const InfoHash &hash) const;
|
||||
QHash<InfoHash, TorrentHandle *> torrents() const;
|
||||
@@ -171,6 +234,9 @@ namespace BitTorrent
|
||||
int uploadRateLimit() const;
|
||||
bool isListening() const;
|
||||
|
||||
MaxRatioAction maxRatioAction() const;
|
||||
void setMaxRatioAction(MaxRatioAction act);
|
||||
|
||||
void changeSpeedLimitMode(bool alternative);
|
||||
void setDownloadRateLimit(int rate);
|
||||
void setUploadRateLimit(int rate);
|
||||
@@ -183,7 +249,7 @@ namespace BitTorrent
|
||||
bool addTorrent(QString source, const AddTorrentParams ¶ms = AddTorrentParams());
|
||||
bool addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams ¶ms = AddTorrentParams());
|
||||
bool deleteTorrent(const QString &hash, bool deleteLocalFiles = false);
|
||||
bool loadMetadata(const QString &magnetUri);
|
||||
bool loadMetadata(const MagnetUri &magnetUri);
|
||||
bool cancelLoadMetadata(const InfoHash &hash);
|
||||
|
||||
void recursiveTorrentDownload(const InfoHash &hash);
|
||||
@@ -195,7 +261,8 @@ namespace BitTorrent
|
||||
// TorrentHandle interface
|
||||
void handleTorrentRatioLimitChanged(TorrentHandle *const torrent);
|
||||
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
|
||||
void handleTorrentLabelChanged(TorrentHandle *const torrent, const QString &oldLabel);
|
||||
void handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory);
|
||||
void handleTorrentSavingModeChanged(TorrentHandle *const torrent);
|
||||
void handleTorrentMetadataReceived(TorrentHandle *const torrent);
|
||||
void handleTorrentPaused(TorrentHandle *const torrent);
|
||||
void handleTorrentResumed(TorrentHandle *const torrent);
|
||||
@@ -223,7 +290,8 @@ namespace BitTorrent
|
||||
void torrentFinished(BitTorrent::TorrentHandle *const torrent);
|
||||
void torrentFinishedChecking(BitTorrent::TorrentHandle *const torrent);
|
||||
void torrentSavePathChanged(BitTorrent::TorrentHandle *const torrent);
|
||||
void torrentLabelChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldLabel);
|
||||
void torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory);
|
||||
void torrentSavingModeChanged(BitTorrent::TorrentHandle *const torrent);
|
||||
void allTorrentsFinished();
|
||||
void metadataLoaded(const BitTorrent::TorrentInfo &info);
|
||||
void torrentMetadataLoaded(BitTorrent::TorrentHandle *const torrent);
|
||||
@@ -241,6 +309,9 @@ namespace BitTorrent
|
||||
void trackerlessStateChanged(BitTorrent::TorrentHandle *const torrent, bool trackerless);
|
||||
void downloadFromUrlFailed(const QString &url, const QString &reason);
|
||||
void downloadFromUrlFinished(const QString &url);
|
||||
void categoryAdded(const QString &categoryName);
|
||||
void categoryRemoved(const QString &categoryName);
|
||||
void subcategoriesSupportChanged();
|
||||
|
||||
private slots:
|
||||
void configure();
|
||||
@@ -274,8 +345,6 @@ namespace BitTorrent
|
||||
void adjustLimits(libtorrent::session_settings &sessionSettings);
|
||||
const QStringList getListeningIPs();
|
||||
void setListeningPort();
|
||||
void setDefaultSavePath(const QString &path);
|
||||
void setDefaultTempPath(const QString &path = QString());
|
||||
void preAllocateAllFiles(bool b);
|
||||
void setMaxConnectionsPerTorrent(int max);
|
||||
void setMaxUploadsPerTorrent(int max);
|
||||
@@ -283,7 +352,6 @@ namespace BitTorrent
|
||||
void enableDHT(bool enable);
|
||||
void changeSpeedLimitMode_impl(bool alternative);
|
||||
|
||||
void setAppendLabelToSavePath(bool append);
|
||||
void setAppendExtension(bool append);
|
||||
|
||||
void startUpTorrents();
|
||||
@@ -293,7 +361,6 @@ namespace BitTorrent
|
||||
|
||||
void updateRatioTimer();
|
||||
void exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolder folder = TorrentExportFolder::Regular);
|
||||
void exportTorrentFiles(QString path);
|
||||
void saveTorrentResumeData(TorrentHandle *const torrent);
|
||||
|
||||
void handleAlert(libtorrent::alert *a);
|
||||
@@ -314,13 +381,14 @@ namespace BitTorrent
|
||||
void handleListenFailedAlert(libtorrent::listen_failed_alert *p);
|
||||
void handleExternalIPAlert(libtorrent::external_ip_alert *p);
|
||||
|
||||
void createTorrentHandle(const libtorrent::torrent_handle &nativeHandle);
|
||||
|
||||
void saveResumeData();
|
||||
bool writeResumeDataFile(TorrentHandle *const torrent, const libtorrent::entry &data);
|
||||
|
||||
void dispatchAlerts(std::auto_ptr<libtorrent::alert> alertPtr);
|
||||
void getPendingAlerts(QVector<libtorrent::alert *> &out, ulong time = 0);
|
||||
|
||||
AddTorrentData addDataFromParams(const AddTorrentParams ¶ms);
|
||||
SettingsStorage *m_settings;
|
||||
|
||||
// BitTorrent
|
||||
libtorrent::session *m_nativeSession;
|
||||
@@ -335,10 +403,9 @@ namespace BitTorrent
|
||||
qreal m_globalMaxRatio;
|
||||
int m_numResumeData;
|
||||
int m_extraLimit;
|
||||
bool m_appendLabelToSavePath;
|
||||
bool m_appendExtension;
|
||||
uint m_refreshInterval;
|
||||
MaxRatioAction m_highRatioAction;
|
||||
MaxRatioAction m_maxRatioAction;
|
||||
QList<BitTorrent::TrackerEntry> m_additionalTrackers;
|
||||
QString m_defaultSavePath;
|
||||
QString m_tempPath;
|
||||
@@ -356,12 +423,16 @@ namespace BitTorrent
|
||||
QPointer<BandwidthScheduler> m_bwScheduler;
|
||||
// Tracker
|
||||
QPointer<Tracker> m_tracker;
|
||||
// fastresume data writing thread
|
||||
QThread *m_ioThread;
|
||||
ResumeDataSavingManager *m_resumeDataSavingManager;
|
||||
|
||||
QHash<InfoHash, TorrentInfo> m_loadedMetadata;
|
||||
QHash<InfoHash, TorrentHandle *> m_torrents;
|
||||
QHash<InfoHash, AddTorrentData> m_addingTorrents;
|
||||
QHash<QString, AddTorrentParams> m_downloadedTorrents;
|
||||
TorrentStatusReport m_torrentStatusReport;
|
||||
QStringMap m_categories;
|
||||
|
||||
QMutex m_alertsMutex;
|
||||
QWaitCondition m_alertsWaitCondition;
|
||||
@@ -43,9 +43,9 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "core/utils/fs.h"
|
||||
#include "core/utils/misc.h"
|
||||
#include "core/utils/string.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "torrentcreatorthread.h"
|
||||
|
||||
namespace libt = libtorrent;
|
||||
@@ -55,11 +55,14 @@ using namespace BitTorrent;
|
||||
// name starts with a .
|
||||
bool fileFilter(const std::string &f)
|
||||
{
|
||||
return (libt::filename(f)[0] != '.');
|
||||
return !Utils::Fs::fileName(Utils::String::fromStdString(f)).startsWith('.');
|
||||
}
|
||||
|
||||
TorrentCreatorThread::TorrentCreatorThread(QObject *parent)
|
||||
: QThread(parent)
|
||||
, m_private(false)
|
||||
, m_pieceSize(0)
|
||||
, m_abort(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -40,17 +40,21 @@
|
||||
#include <libtorrent/alert_types.hpp>
|
||||
#include <libtorrent/create_torrent.hpp>
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <libtorrent/time.hpp>
|
||||
#endif
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Windows.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "core/logger.h"
|
||||
#include "core/preferences.h"
|
||||
#include "core/utils/string.h"
|
||||
#include "core/utils/fs.h"
|
||||
#include "core/utils/misc.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "session.h"
|
||||
#include "peerinfo.h"
|
||||
#include "trackerentry.h"
|
||||
@@ -61,10 +65,31 @@ static const char QB_EXT[] = ".!qB";
|
||||
namespace libt = libtorrent;
|
||||
using namespace BitTorrent;
|
||||
|
||||
// TrackerInfo
|
||||
// AddTorrentData
|
||||
|
||||
TrackerInfo::TrackerInfo()
|
||||
: numPeers(0)
|
||||
AddTorrentData::AddTorrentData()
|
||||
: resumed(false)
|
||||
, disableTempPath(false)
|
||||
, sequential(false)
|
||||
, hasSeedStatus(false)
|
||||
, skipChecking(false)
|
||||
, ratioLimit(TorrentHandle::USE_GLOBAL_RATIO)
|
||||
{
|
||||
}
|
||||
|
||||
AddTorrentData::AddTorrentData(const AddTorrentParams ¶ms)
|
||||
: resumed(false)
|
||||
, name(params.name)
|
||||
, category(params.category)
|
||||
, savePath(params.savePath)
|
||||
, disableTempPath(params.disableTempPath)
|
||||
, sequential(params.sequential)
|
||||
, hasSeedStatus(params.skipChecking) // do not react on 'torrent_finished_alert' when skipping
|
||||
, skipChecking(params.skipChecking)
|
||||
, addForced(params.addForced)
|
||||
, addPaused(params.addPaused)
|
||||
, filePriorities(params.filePriorities)
|
||||
, ratioLimit(params.ignoreShareRatio ? TorrentHandle::NO_RATIO_LIMIT : TorrentHandle::USE_GLOBAL_RATIO)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -176,7 +201,8 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
|
||||
, m_renameCount(0)
|
||||
, m_name(data.name)
|
||||
, m_savePath(Utils::Fs::toNativePath(data.savePath))
|
||||
, m_label(data.label)
|
||||
, m_category(data.category)
|
||||
, m_useASM(data.savePath.isEmpty())
|
||||
, m_hasSeedStatus(data.hasSeedStatus)
|
||||
, m_ratioLimit(data.ratioLimit)
|
||||
, m_tempPathDisabled(data.disableTempPath)
|
||||
@@ -184,7 +210,12 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
|
||||
, m_pauseAfterRecheck(false)
|
||||
, m_needSaveResumeData(false)
|
||||
{
|
||||
initialize();
|
||||
if (m_useASM)
|
||||
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
|
||||
|
||||
updateStatus();
|
||||
m_hash = InfoHash(m_nativeStatus.info_hash);
|
||||
adjustActualSavePath();
|
||||
|
||||
if (!data.resumed) {
|
||||
setSequentialDownload(data.sequential);
|
||||
@@ -301,6 +332,22 @@ QString TorrentHandle::contentPath(bool actual) const
|
||||
return rootPath(actual);
|
||||
}
|
||||
|
||||
bool TorrentHandle::isASMEnabled() const
|
||||
{
|
||||
return m_useASM;
|
||||
}
|
||||
|
||||
void TorrentHandle::setASMEnabled(bool enabled)
|
||||
{
|
||||
if (m_useASM == enabled) return;
|
||||
|
||||
m_useASM = enabled;
|
||||
m_session->handleTorrentSavingModeChanged(this);
|
||||
|
||||
if (m_useASM)
|
||||
move_impl(m_session->categorySavePath(m_category));
|
||||
}
|
||||
|
||||
QString TorrentHandle::nativeActualSavePath() const
|
||||
{
|
||||
return Utils::String::fromStdString(m_nativeStatus.save_path);
|
||||
@@ -433,7 +480,7 @@ bool TorrentHandle::connectPeer(const PeerAddress &peerAddress)
|
||||
libt::address addr = libt::address::from_string(Utils::String::toStdString(peerAddress.ip.toString()), ec);
|
||||
if (ec) return false;
|
||||
|
||||
libt::asio::ip::tcp::endpoint ep(addr, peerAddress.port);
|
||||
boost::asio::ip::tcp::endpoint ep(addr, peerAddress.port);
|
||||
SAFE_CALL_BOOL(connect_peer, ep);
|
||||
}
|
||||
|
||||
@@ -478,9 +525,22 @@ qreal TorrentHandle::progress() const
|
||||
return progress;
|
||||
}
|
||||
|
||||
QString TorrentHandle::label() const
|
||||
QString TorrentHandle::category() const
|
||||
{
|
||||
return m_label;
|
||||
return m_category;
|
||||
}
|
||||
|
||||
bool TorrentHandle::belongsToCategory(const QString &category) const
|
||||
{
|
||||
if (m_category.isEmpty()) return category.isEmpty();
|
||||
if (!Session::isValidCategoryName(category)) return false;
|
||||
|
||||
if (m_category == category) return true;
|
||||
|
||||
if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + "/"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QDateTime TorrentHandle::addedTime() const
|
||||
@@ -847,7 +907,7 @@ qulonglong TorrentHandle::eta() const
|
||||
|
||||
QVector<qreal> TorrentHandle::filesProgress() const
|
||||
{
|
||||
std::vector<libt::size_type> fp;
|
||||
std::vector<boost::int64_t> fp;
|
||||
QVector<qreal> result;
|
||||
SAFE_CALL(file_progress, fp, libt::torrent_handle::piece_granularity);
|
||||
|
||||
@@ -960,7 +1020,7 @@ QList<PeerInfo> TorrentHandle::peers() const
|
||||
SAFE_CALL(get_peer_info, nativePeers);
|
||||
|
||||
foreach (const libt::peer_info &peer, nativePeers)
|
||||
peers << peer;
|
||||
peers << PeerInfo(this, peer);
|
||||
|
||||
return peers;
|
||||
}
|
||||
@@ -1022,9 +1082,9 @@ qreal TorrentHandle::maxRatio(bool *usesGlobalRatio) const
|
||||
|
||||
qreal TorrentHandle::realRatio() const
|
||||
{
|
||||
libt::size_type upload = m_nativeStatus.all_time_upload;
|
||||
boost::int64_t upload = m_nativeStatus.all_time_upload;
|
||||
// special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
|
||||
libt::size_type download = (m_nativeStatus.all_time_download < m_nativeStatus.total_done * 0.01) ? m_nativeStatus.total_done : m_nativeStatus.all_time_download;
|
||||
boost::int64_t download = (m_nativeStatus.all_time_download < m_nativeStatus.total_done * 0.01) ? m_nativeStatus.total_done : m_nativeStatus.all_time_download;
|
||||
|
||||
if (download == 0)
|
||||
return (upload == 0) ? 0.0 : MAX_RATIO;
|
||||
@@ -1066,7 +1126,11 @@ int TorrentHandle::connectionsLimit() const
|
||||
|
||||
qlonglong TorrentHandle::nextAnnounce() const
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
return m_nativeStatus.next_announce.total_seconds();
|
||||
#else
|
||||
return libt::duration_cast<libt::seconds>(m_nativeStatus.next_announce).count();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TorrentHandle::setName(const QString &name)
|
||||
@@ -1077,17 +1141,47 @@ void TorrentHandle::setName(const QString &name)
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::setLabel(const QString &label)
|
||||
bool TorrentHandle::setCategory(const QString &category)
|
||||
{
|
||||
if (m_label != label) {
|
||||
QString oldLabel = m_label;
|
||||
m_label = label;
|
||||
if (m_category != category) {
|
||||
if (!category.isEmpty()) {
|
||||
if (!Session::isValidCategoryName(category)) return false;
|
||||
if (!m_session->categories().contains(category))
|
||||
if (!m_session->addCategory(category))
|
||||
return false;
|
||||
}
|
||||
|
||||
QString oldCategory = m_category;
|
||||
m_category = category;
|
||||
m_needSaveResumeData = true;
|
||||
m_session->handleTorrentLabelChanged(this, oldLabel);
|
||||
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
||||
|
||||
if (m_useASM) {
|
||||
if (!m_session->isDisableASMWhenCategoryChanged())
|
||||
move_impl(m_session->categorySavePath(m_category));
|
||||
else
|
||||
setASMEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TorrentHandle::move(QString path)
|
||||
{
|
||||
m_useASM = false;
|
||||
m_session->handleTorrentSavingModeChanged(this);
|
||||
|
||||
path = Utils::Fs::fromNativePath(path.trimmed());
|
||||
if (path.isEmpty())
|
||||
path = m_session->defaultSavePath();
|
||||
if (!path.endsWith('/'))
|
||||
path += '/';
|
||||
|
||||
move_impl(path);
|
||||
}
|
||||
|
||||
void TorrentHandle::move_impl(QString path)
|
||||
{
|
||||
path = Utils::Fs::toNativePath(path);
|
||||
if (path == savePath()) return;
|
||||
@@ -1143,6 +1237,8 @@ void TorrentHandle::setFirstLastPiecePriority(bool b)
|
||||
|
||||
std::vector<int> fp;
|
||||
SAFE_GET(fp, file_priorities);
|
||||
std::vector<int> pp;
|
||||
SAFE_GET(pp, piece_priorities);
|
||||
|
||||
// Download first and last pieces first for all media files in the torrent
|
||||
int nbfiles = static_cast<int>(fp.size());
|
||||
@@ -1151,14 +1247,22 @@ void TorrentHandle::setFirstLastPiecePriority(bool b)
|
||||
const QString ext = Utils::Fs::fileExtension(path);
|
||||
if (Utils::Misc::isPreviewable(ext) && (fp[index] > 0)) {
|
||||
qDebug() << "File" << path << "is previewable, toggle downloading of first/last pieces first";
|
||||
|
||||
// Determine the priority to set
|
||||
int prio = b ? 7 : fp[index];
|
||||
|
||||
QPair<int, int> extremities = fileExtremityPieces(index);
|
||||
SAFE_CALL(piece_priority, extremities.first, prio);
|
||||
SAFE_CALL(piece_priority, extremities.second, prio);
|
||||
|
||||
// worst case: AVI index = 1% of total file size (at the end of the file)
|
||||
int nNumPieces = ceil(fileSize(index) * 0.01 / pieceLength());
|
||||
for (int i = 0; i < nNumPieces; ++i) {
|
||||
pp[extremities.first + i] = prio;
|
||||
pp[extremities.second - i] = prio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_CALL(prioritize_pieces, pp);
|
||||
}
|
||||
|
||||
void TorrentHandle::toggleFirstLastPiecePriority()
|
||||
@@ -1364,10 +1468,13 @@ void TorrentHandle::handleTorrentCheckedAlert(libtorrent::torrent_checked_alert
|
||||
qDebug("%s have just finished checking", qPrintable(hash()));
|
||||
|
||||
updateStatus();
|
||||
adjustActualSavePath();
|
||||
|
||||
if (progress() < 1.0 && wantedSize() > 0)
|
||||
if ((progress() < 1.0) && (wantedSize() > 0))
|
||||
m_hasSeedStatus = false;
|
||||
else if (progress() == 1.0)
|
||||
m_hasSeedStatus = true;
|
||||
|
||||
adjustActualSavePath();
|
||||
|
||||
if (m_pauseAfterRecheck) {
|
||||
m_pauseAfterRecheck = false;
|
||||
@@ -1423,12 +1530,13 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert
|
||||
resumeData["qBt-paused"] = isPaused();
|
||||
resumeData["qBt-forced"] = isForced();
|
||||
}
|
||||
resumeData["qBt-savePath"] = Utils::String::toStdString(m_savePath);
|
||||
resumeData["qBt-savePath"] = m_useASM ? "" : Utils::String::toStdString(m_savePath);
|
||||
resumeData["qBt-ratioLimit"] = Utils::String::toStdString(QString::number(m_ratioLimit));
|
||||
resumeData["qBt-label"] = Utils::String::toStdString(m_label);
|
||||
resumeData["qBt-category"] = Utils::String::toStdString(m_category);
|
||||
resumeData["qBt-name"] = Utils::String::toStdString(m_name);
|
||||
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
|
||||
resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
|
||||
resumeData["qBt-queuePosition"] = queuePosition();
|
||||
|
||||
m_session->handleTorrentResumeDataReady(this, resumeData);
|
||||
}
|
||||
@@ -1485,12 +1593,6 @@ void TorrentHandle::handleFileRenamedAlert(libtorrent::file_renamed_alert *p)
|
||||
|
||||
updateStatus();
|
||||
|
||||
if (filesCount() == 1) {
|
||||
// Single-file torrent
|
||||
// Renaming a file corresponds to changing the save path
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
|
||||
--m_renameCount;
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
@@ -1551,6 +1653,12 @@ void TorrentHandle::handleTempPathChanged()
|
||||
adjustActualSavePath();
|
||||
}
|
||||
|
||||
void TorrentHandle::handleCategorySavePathChanged()
|
||||
{
|
||||
if (m_useASM)
|
||||
move_impl(m_session->categorySavePath(m_category));
|
||||
}
|
||||
|
||||
void TorrentHandle::handleAppendExtensionToggled()
|
||||
{
|
||||
if (!hasMetadata()) return;
|
||||
@@ -1676,15 +1784,11 @@ libtorrent::torrent_handle TorrentHandle::nativeHandle() const
|
||||
void TorrentHandle::updateTorrentInfo()
|
||||
{
|
||||
if (!hasMetadata()) return;
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
m_torrentInfo = TorrentInfo(m_nativeStatus.torrent_file);
|
||||
}
|
||||
|
||||
void TorrentHandle::initialize()
|
||||
{
|
||||
updateStatus();
|
||||
m_hash = InfoHash(m_nativeStatus.info_hash);
|
||||
adjustActualSavePath();
|
||||
#else
|
||||
m_torrentInfo = TorrentInfo(m_nativeStatus.torrent_file.lock());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TorrentHandle::isMoveInProgress() const
|
||||
@@ -1694,7 +1798,7 @@ bool TorrentHandle::isMoveInProgress() const
|
||||
|
||||
bool TorrentHandle::useTempPath() const
|
||||
{
|
||||
return !m_tempPathDisabled && m_session->isTempPathEnabled() && !isSeed();
|
||||
return !m_tempPathDisabled && m_session->isTempPathEnabled() && !(isSeed() || m_hasSeedStatus);
|
||||
}
|
||||
|
||||
void TorrentHandle::updateStatus()
|
||||
@@ -38,9 +38,14 @@
|
||||
#include <QHash>
|
||||
|
||||
#include <libtorrent/torrent_handle.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <libtorrent/torrent_status.hpp>
|
||||
#endif
|
||||
|
||||
#include <boost/function.hpp>
|
||||
|
||||
#include "core/tristatebool.h"
|
||||
#include "base/tristatebool.h"
|
||||
#include "private/speedmonitor.h"
|
||||
#include "infohash.h"
|
||||
#include "torrentinfo.h"
|
||||
@@ -85,7 +90,7 @@ namespace BitTorrent
|
||||
bool resumed;
|
||||
// for both new and resumed torrents
|
||||
QString name;
|
||||
QString label;
|
||||
QString category;
|
||||
QString savePath;
|
||||
bool disableTempPath;
|
||||
bool sequential;
|
||||
@@ -97,14 +102,15 @@ namespace BitTorrent
|
||||
QVector<int> filePriorities;
|
||||
// for resumed torrents
|
||||
qreal ratioLimit;
|
||||
|
||||
AddTorrentData();
|
||||
AddTorrentData(const AddTorrentParams ¶ms);
|
||||
};
|
||||
|
||||
struct TrackerInfo
|
||||
{
|
||||
QString lastMessage;
|
||||
quint32 numPeers;
|
||||
|
||||
TrackerInfo();
|
||||
quint32 numPeers = 0;
|
||||
};
|
||||
|
||||
class TorrentState
|
||||
@@ -221,11 +227,16 @@ namespace BitTorrent
|
||||
QString rootPath(bool actual = false) const;
|
||||
QString contentPath(bool actual = false) const;
|
||||
|
||||
bool isASMEnabled() const;
|
||||
void setASMEnabled(bool enabled);
|
||||
QString category() const;
|
||||
bool belongsToCategory(const QString &category) const;
|
||||
bool setCategory(const QString &category);
|
||||
|
||||
int filesCount() const;
|
||||
int piecesCount() const;
|
||||
int piecesHave() const;
|
||||
qreal progress() const;
|
||||
QString label() const;
|
||||
QDateTime addedTime() const;
|
||||
qreal ratioLimit() const;
|
||||
|
||||
@@ -301,7 +312,6 @@ namespace BitTorrent
|
||||
qlonglong nextAnnounce() const;
|
||||
|
||||
void setName(const QString &name);
|
||||
void setLabel(const QString &label);
|
||||
void setSequentialDownload(bool b);
|
||||
void toggleSequentialDownload();
|
||||
void setFirstLastPiecePriority(bool b);
|
||||
@@ -338,13 +348,13 @@ namespace BitTorrent
|
||||
void handleAlert(libtorrent::alert *a);
|
||||
void handleStateUpdate(const libtorrent::torrent_status &nativeStatus);
|
||||
void handleTempPathChanged();
|
||||
void handleCategorySavePathChanged();
|
||||
void handleAppendExtensionToggled();
|
||||
void saveResumeData();
|
||||
|
||||
private:
|
||||
typedef boost::function<void ()> EventTrigger;
|
||||
|
||||
void initialize();
|
||||
void updateStatus();
|
||||
void updateStatus(const libtorrent::torrent_status &nativeStatus);
|
||||
void updateState();
|
||||
@@ -374,6 +384,7 @@ namespace BitTorrent
|
||||
|
||||
void adjustActualSavePath();
|
||||
void adjustActualSavePath_impl();
|
||||
void move_impl(QString path);
|
||||
void moveStorage(const QString &newPath);
|
||||
void appendExtensionsToIncompleteFiles();
|
||||
void removeExtensionsFromIncompleteFiles();
|
||||
@@ -400,10 +411,12 @@ namespace BitTorrent
|
||||
QQueue<EventTrigger> m_moveFinishedTriggers;
|
||||
int m_renameCount;
|
||||
|
||||
bool m_useASM;
|
||||
|
||||
// Persistent data
|
||||
QString m_name;
|
||||
QString m_savePath;
|
||||
QString m_label;
|
||||
QString m_category;
|
||||
bool m_hasSeedStatus;
|
||||
qreal m_ratioLimit;
|
||||
bool m_tempPathDisabled;
|
||||
@@ -33,9 +33,9 @@
|
||||
|
||||
#include <libtorrent/error_code.hpp>
|
||||
|
||||
#include "core/utils/misc.h"
|
||||
#include "core/utils/fs.h"
|
||||
#include "core/utils/string.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "infohash.h"
|
||||
#include "trackerentry.h"
|
||||
#include "torrentinfo.h"
|
||||
@@ -43,9 +43,9 @@
|
||||
namespace libt = libtorrent;
|
||||
using namespace BitTorrent;
|
||||
|
||||
TorrentInfo::TorrentInfo(boost::intrusive_ptr<const libt::torrent_info> nativeInfo)
|
||||
: m_nativeInfo(const_cast<libt::torrent_info *>(nativeInfo.get()))
|
||||
TorrentInfo::TorrentInfo(NativeConstPtr nativeInfo)
|
||||
{
|
||||
m_nativeInfo = boost::const_pointer_cast<libt::torrent_info>(nativeInfo);
|
||||
}
|
||||
|
||||
TorrentInfo::TorrentInfo(const TorrentInfo &other)
|
||||
@@ -63,7 +63,7 @@ TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString &error)
|
||||
{
|
||||
error.clear();
|
||||
libt::error_code ec;
|
||||
TorrentInfo info(new libt::torrent_info(Utils::String::toStdString(Utils::Fs::toNativePath(path)), ec));
|
||||
TorrentInfo info(NativePtr(new libt::torrent_info(Utils::String::toStdString(Utils::Fs::toNativePath(path)), ec)));
|
||||
if (ec) {
|
||||
error = QString::fromUtf8(ec.message().c_str());
|
||||
qDebug("Cannot load .torrent file: %s", qPrintable(error));
|
||||
@@ -211,13 +211,27 @@ QByteArray TorrentInfo::metadata() const
|
||||
return QByteArray(m_nativeInfo->metadata().get(), m_nativeInfo->metadata_size());
|
||||
}
|
||||
|
||||
QStringList TorrentInfo::filesForPiece(int pieceIndex) const
|
||||
{
|
||||
if (pieceIndex < 0)
|
||||
return QStringList();
|
||||
|
||||
std::vector<libtorrent::file_slice> files(
|
||||
nativeInfo()->map_block(pieceIndex, 0, nativeInfo()->piece_size(pieceIndex)));
|
||||
QStringList res;
|
||||
for (const libtorrent::file_slice& s: files) {
|
||||
res.append(filePath(s.file_index));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void TorrentInfo::renameFile(uint index, const QString &newPath)
|
||||
{
|
||||
if (!isValid()) return;
|
||||
m_nativeInfo->rename_file(index, Utils::String::toStdString(newPath));
|
||||
nativeInfo()->rename_file(index, Utils::String::toStdString(newPath));
|
||||
}
|
||||
|
||||
boost::intrusive_ptr<libtorrent::torrent_info> TorrentInfo::nativeInfo() const
|
||||
TorrentInfo::NativePtr TorrentInfo::nativeInfo() const
|
||||
{
|
||||
return m_nativeInfo;
|
||||
}
|
||||
@@ -30,7 +30,9 @@
|
||||
#define BITTORRENT_TORRENTINFO_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
class QString;
|
||||
class QUrl;
|
||||
@@ -47,7 +49,15 @@ namespace BitTorrent
|
||||
class TorrentInfo
|
||||
{
|
||||
public:
|
||||
explicit TorrentInfo(boost::intrusive_ptr<const libtorrent::torrent_info> nativeInfo = boost::intrusive_ptr<const libtorrent::torrent_info>());
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
typedef boost::intrusive_ptr<const libtorrent::torrent_info> NativeConstPtr;
|
||||
typedef boost::intrusive_ptr<libtorrent::torrent_info> NativePtr;
|
||||
#else
|
||||
typedef boost::shared_ptr<const libtorrent::torrent_info> NativeConstPtr;
|
||||
typedef boost::shared_ptr<libtorrent::torrent_info> NativePtr;
|
||||
#endif
|
||||
|
||||
explicit TorrentInfo(NativeConstPtr nativeInfo = NativeConstPtr());
|
||||
TorrentInfo(const TorrentInfo &other);
|
||||
|
||||
static TorrentInfo loadFromFile(const QString &path, QString &error);
|
||||
@@ -75,12 +85,14 @@ namespace BitTorrent
|
||||
QList<TrackerEntry> trackers() const;
|
||||
QList<QUrl> urlSeeds() const;
|
||||
QByteArray metadata() const;
|
||||
QStringList filesForPiece(int pieceIndex) const;
|
||||
|
||||
void renameFile(uint index, const QString &newPath);
|
||||
boost::intrusive_ptr<libtorrent::torrent_info> nativeInfo() const;
|
||||
|
||||
NativePtr nativeInfo() const;
|
||||
|
||||
private:
|
||||
boost::intrusive_ptr<libtorrent::torrent_info> m_nativeInfo;
|
||||
NativePtr m_nativeInfo;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
|
||||
#include "core/preferences.h"
|
||||
#include "core/http/server.h"
|
||||
#include "core/utils/string.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/http/server.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "tracker.h"
|
||||
|
||||
// static limits
|
||||
@@ -33,9 +33,9 @@
|
||||
#define BITTORRENT_TRACKER_H
|
||||
|
||||
#include <QHash>
|
||||
#include "core/http/types.h"
|
||||
#include "core/http/responsebuilder.h"
|
||||
#include "core/http/irequesthandler.h"
|
||||
#include "base/http/types.h"
|
||||
#include "base/http/responsebuilder.h"
|
||||
#include "base/http/irequesthandler.h"
|
||||
|
||||
namespace libtorrent
|
||||
{
|
||||
@@ -28,8 +28,8 @@
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/utils/misc.h"
|
||||
#include "core/utils/string.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "trackerentry.h"
|
||||
|
||||
using namespace BitTorrent;
|
||||
@@ -30,6 +30,10 @@
|
||||
#define BITTORRENT_TRACKERENTRY_H
|
||||
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <libtorrent/announce_entry.hpp>
|
||||
#endif
|
||||
|
||||
class QString;
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "core/preferences.h"
|
||||
#include "core/bittorrent/torrentinfo.h"
|
||||
#include "core/bittorrent/magneturi.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/bittorrent/torrentinfo.h"
|
||||
#include "base/bittorrent/magneturi.h"
|
||||
#include "filesystemwatcher.h"
|
||||
|
||||
#ifndef CIFS_MAGIC_NUMBER
|
||||
@@ -195,11 +195,7 @@ void FileSystemWatcher::addTorrentsFromDir(const QDir &dir, QStringList &torrent
|
||||
foreach (const QString &file, files) {
|
||||
const QString fileAbsPath = dir.absoluteFilePath(file);
|
||||
if (fileAbsPath.endsWith(".magnet")) {
|
||||
QFile f(fileAbsPath);
|
||||
if (f.open(QIODevice::ReadOnly)
|
||||
&& !BitTorrent::MagnetUri(QString::fromLocal8Bit(f.readAll())).isValid()) {
|
||||
torrents << fileAbsPath;
|
||||
}
|
||||
torrents << fileAbsPath;
|
||||
}
|
||||
else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid()) {
|
||||
torrents << fileAbsPath;
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
#include <QUrlQuery>
|
||||
#endif
|
||||
#include <QDir>
|
||||
@@ -81,6 +81,11 @@ RequestParser::ErrorCode RequestParser::parseHttpRequest(const QByteArray& data,
|
||||
// Parse HTTP request message
|
||||
if (m_request.headers.contains("content-length")) {
|
||||
int content_length = m_request.headers["content-length"].toInt();
|
||||
if (content_length < 0) {
|
||||
qWarning() << Q_FUNC_INFO << "bad request: content-length negative";
|
||||
return BadRequest;
|
||||
}
|
||||
|
||||
if (content_length > static_cast<int>(m_maxContentLength)) {
|
||||
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
||||
return BadRequest;
|
||||
@@ -92,7 +97,7 @@ RequestParser::ErrorCode RequestParser::parseHttpRequest(const QByteArray& data,
|
||||
return IncompleteRequest;
|
||||
}
|
||||
|
||||
if (!parseContent(content)) {
|
||||
if ((content_length > 0) && !parseContent(content)) {
|
||||
qWarning() << Q_FUNC_INFO << "message parsing error";
|
||||
return BadRequest;
|
||||
}
|
||||
@@ -117,7 +122,7 @@ bool RequestParser::parseStartingLine(const QString &line)
|
||||
m_request.path = url.path(); // Path
|
||||
|
||||
// Parse GET parameters
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifndef QBT_USES_QT5
|
||||
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
||||
#else
|
||||
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems());
|
||||
@@ -216,7 +221,7 @@ bool RequestParser::parseContent(const QByteArray& data)
|
||||
// Parse url-encoded POST data
|
||||
if (m_request.headers["content-type"].startsWith("application/x-www-form-urlencoded")) {
|
||||
QUrl url;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifndef QBT_USES_QT5
|
||||
url.setEncodedQuery(data);
|
||||
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
||||
#else
|
||||
@@ -319,7 +324,7 @@ bool RequestParser::parseFormData(const QByteArray& data)
|
||||
ufile.type = disposition["content-type"];
|
||||
ufile.data = data.mid(header_end + EOH.length());
|
||||
|
||||
m_request.files[disposition["name"]] = ufile;
|
||||
m_request.files.append(ufile);
|
||||
}
|
||||
else {
|
||||
m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length()));
|
||||
@@ -29,7 +29,7 @@
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "core/utils/gzip.h"
|
||||
#include "base/utils/gzip.h"
|
||||
#include "responsegenerator.h"
|
||||
|
||||
using namespace Http;
|
||||
@@ -52,9 +52,9 @@ Server::~Server()
|
||||
}
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
void Server::enableHttps(const QSslCertificate &certificate, const QSslKey &key)
|
||||
void Server::enableHttps(const QList<QSslCertificate> &certificates, const QSslKey &key)
|
||||
{
|
||||
m_certificate = certificate;
|
||||
m_certificates = certificates;
|
||||
m_key = key;
|
||||
m_https = true;
|
||||
}
|
||||
@@ -62,12 +62,12 @@ void Server::enableHttps(const QSslCertificate &certificate, const QSslKey &key)
|
||||
void Server::disableHttps()
|
||||
{
|
||||
m_https = false;
|
||||
m_certificate.clear();
|
||||
m_certificates.clear();
|
||||
m_key.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
void Server::incomingConnection(qintptr socketDescriptor)
|
||||
#else
|
||||
void Server::incomingConnection(int socketDescriptor)
|
||||
@@ -84,9 +84,13 @@ void Server::incomingConnection(int socketDescriptor)
|
||||
if (serverSocket->setSocketDescriptor(socketDescriptor)) {
|
||||
#ifndef QT_NO_OPENSSL
|
||||
if (m_https) {
|
||||
static_cast<QSslSocket*>(serverSocket)->setProtocol(QSsl::AnyProtocol);
|
||||
static_cast<QSslSocket*>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
||||
static_cast<QSslSocket*>(serverSocket)->setPrivateKey(m_key);
|
||||
static_cast<QSslSocket*>(serverSocket)->setLocalCertificate(m_certificate);
|
||||
#ifdef QBT_USES_QT5
|
||||
static_cast<QSslSocket*>(serverSocket)->setLocalCertificateChain(m_certificates);
|
||||
#else
|
||||
static_cast<QSslSocket*>(serverSocket)->setLocalCertificate(m_certificates.first());
|
||||
#endif
|
||||
static_cast<QSslSocket*>(serverSocket)->startServerEncryption();
|
||||
}
|
||||
#endif
|
||||
@@ -54,12 +54,12 @@ namespace Http
|
||||
~Server();
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
void enableHttps(const QSslCertificate &certificate, const QSslKey &key);
|
||||
void enableHttps(const QList<QSslCertificate> &certificates, const QSslKey &key);
|
||||
void disableHttps();
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
void incomingConnection(qintptr socketDescriptor);
|
||||
#else
|
||||
void incomingConnection(int socketDescriptor);
|
||||
@@ -69,7 +69,7 @@ namespace Http
|
||||
IRequestHandler *m_requestHandler;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
bool m_https;
|
||||
QSslCertificate m_certificate;
|
||||
QList<QSslCertificate> m_certificates;
|
||||
QSslKey m_key;
|
||||
#endif
|
||||
};
|
||||
@@ -32,8 +32,9 @@
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QHostAddress>
|
||||
#include <QVector>
|
||||
|
||||
typedef QMap<QString, QString> QStringMap;
|
||||
#include "base/types.h"
|
||||
|
||||
namespace Http
|
||||
{
|
||||
@@ -70,7 +71,7 @@ namespace Http
|
||||
QStringMap headers;
|
||||
QStringMap gets;
|
||||
QStringMap posts;
|
||||
QMap<QString, UploadedFile> files;
|
||||
QVector<UploadedFile> files;
|
||||
};
|
||||
|
||||
struct ResponseStatus
|
||||
@@ -2,31 +2,6 @@
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
namespace Log
|
||||
{
|
||||
Msg::Msg() {}
|
||||
|
||||
Msg::Msg(int id, MsgType type, const QString &message)
|
||||
: id(id)
|
||||
, timestamp(QDateTime::currentMSecsSinceEpoch())
|
||||
, type(type)
|
||||
, message(message)
|
||||
{
|
||||
}
|
||||
|
||||
Peer::Peer() {}
|
||||
|
||||
Peer::Peer(int id, const QString &ip, bool blocked, const QString &reason)
|
||||
: id(id)
|
||||
, timestamp(QDateTime::currentMSecsSinceEpoch())
|
||||
, ip(ip)
|
||||
, blocked(blocked)
|
||||
, reason(reason)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Logger* Logger::m_instance = 0;
|
||||
|
||||
Logger::Logger()
|
||||
@@ -61,7 +36,7 @@ void Logger::addMessage(const QString &message, const Log::MsgType &type)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
Log::Msg temp(msgCounter++, type, message);
|
||||
Log::Msg temp = { msgCounter++, QDateTime::currentMSecsSinceEpoch(), type, message };
|
||||
m_messages.push_back(temp);
|
||||
|
||||
if (m_messages.size() >= MAX_LOG_MESSAGES)
|
||||
@@ -74,7 +49,7 @@ void Logger::addPeer(const QString &ip, bool blocked, const QString &reason)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
Log::Peer temp(peerCounter++, ip, blocked, reason);
|
||||
Log::Peer temp = { peerCounter++, QDateTime::currentMSecsSinceEpoch(), ip, blocked, reason };
|
||||
m_peers.push_back(temp);
|
||||
|
||||
if (m_peers.size() >= MAX_LOG_MESSAGES)
|
||||
@@ -6,22 +6,22 @@
|
||||
#include <QReadWriteLock>
|
||||
#include <QObject>
|
||||
|
||||
const int MAX_LOG_MESSAGES = 1000;
|
||||
const int MAX_LOG_MESSAGES = 20000;
|
||||
|
||||
namespace Log
|
||||
{
|
||||
enum MsgType
|
||||
{
|
||||
NORMAL,
|
||||
INFO,
|
||||
WARNING,
|
||||
CRITICAL //ERROR is defined by libtorrent and results in compiler error
|
||||
ALL = -1,
|
||||
NORMAL = 0x1,
|
||||
INFO = 0x2,
|
||||
WARNING = 0x4,
|
||||
CRITICAL = 0x8 //ERROR is defined by libtorrent and results in compiler error
|
||||
};
|
||||
Q_DECLARE_FLAGS(MsgTypes, MsgType)
|
||||
|
||||
struct Msg
|
||||
{
|
||||
Msg();
|
||||
Msg(int id, MsgType type, const QString &message);
|
||||
int id;
|
||||
qint64 timestamp;
|
||||
MsgType type;
|
||||
@@ -30,8 +30,6 @@ namespace Log
|
||||
|
||||
struct Peer
|
||||
{
|
||||
Peer(int id, const QString &ip, bool blocked, const QString &reason);
|
||||
Peer();
|
||||
int id;
|
||||
qint64 timestamp;
|
||||
QString ip;
|
||||
@@ -40,6 +38,8 @@ namespace Log
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Log::MsgTypes)
|
||||
|
||||
class Logger : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -28,15 +28,16 @@
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QDebug>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
#include <QUrlQuery>
|
||||
#endif
|
||||
|
||||
#include "core/logger.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/net/downloadhandler.h"
|
||||
#include "dnsupdater.h"
|
||||
|
||||
using namespace Net;
|
||||
@@ -76,65 +77,62 @@ DNSUpdater::~DNSUpdater()
|
||||
void DNSUpdater::checkPublicIP()
|
||||
{
|
||||
Q_ASSERT(m_state == OK);
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply *)), SLOT(ipRequestFinished(QNetworkReply *)));
|
||||
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
||||
"http://checkip.dyndns.org", false, 0, false,
|
||||
QString("qBittorrent/%1").arg(VERSION));
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(ipRequestFinished(QString, QByteArray)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(ipRequestFailed(QString, QString)));
|
||||
|
||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl("http://checkip.dyndns.org"));
|
||||
request.setRawHeader("User-Agent", "qBittorrent/" VERSION);
|
||||
manager->get(request);
|
||||
}
|
||||
|
||||
void DNSUpdater::ipRequestFinished(QNetworkReply *reply)
|
||||
void DNSUpdater::ipRequestFinished(const QString &url, const QByteArray &data)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
if (reply->error()) {
|
||||
// Error
|
||||
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
|
||||
}
|
||||
else {
|
||||
// Parse response
|
||||
QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>");
|
||||
QString ret = reply->readAll();
|
||||
if (ipregex.indexIn(ret) >= 0) {
|
||||
QString ip_str = ipregex.cap(1);
|
||||
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ip_str;
|
||||
QHostAddress new_ip(ip_str);
|
||||
if (!new_ip.isNull()) {
|
||||
if (m_lastIP != new_ip) {
|
||||
qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS...";
|
||||
qDebug() << m_lastIP.toString() << "->" << new_ip.toString();
|
||||
m_lastIP = new_ip;
|
||||
updateDNSService();
|
||||
}
|
||||
}
|
||||
else {
|
||||
qWarning() << Q_FUNC_INFO << "Failed to construct a QHostAddress from the IP string";
|
||||
Q_UNUSED(url);
|
||||
|
||||
// Parse response
|
||||
QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>");
|
||||
if (ipregex.indexIn(data) >= 0) {
|
||||
QString ipStr = ipregex.cap(1);
|
||||
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
|
||||
QHostAddress newIp(ipStr);
|
||||
if (!newIp.isNull()) {
|
||||
if (m_lastIP != newIp) {
|
||||
qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS...";
|
||||
qDebug() << m_lastIP.toString() << "->" << newIp.toString();
|
||||
m_lastIP = newIp;
|
||||
updateDNSService();
|
||||
}
|
||||
}
|
||||
else {
|
||||
qWarning() << Q_FUNC_INFO << "Regular expression failed to capture the IP address";
|
||||
qWarning() << Q_FUNC_INFO << "Failed to construct a QHostAddress from the IP string";
|
||||
}
|
||||
}
|
||||
// Clean up
|
||||
reply->deleteLater();
|
||||
sender()->deleteLater();
|
||||
else {
|
||||
qWarning() << Q_FUNC_INFO << "Regular expression failed to capture the IP address";
|
||||
}
|
||||
}
|
||||
|
||||
void DNSUpdater::ipRequestFailed(const QString &url, const QString &error)
|
||||
{
|
||||
Q_UNUSED(url);
|
||||
qWarning() << "IP request failed:" << error;
|
||||
}
|
||||
|
||||
void DNSUpdater::updateDNSService()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
// Prepare request
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply *)), SLOT(ipUpdateFinished(QNetworkReply *)));
|
||||
|
||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||
QNetworkRequest request;
|
||||
request.setUrl(getUpdateUrl());
|
||||
request.setRawHeader("User-Agent", "qBittorrent/" VERSION);
|
||||
manager->get(request);
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
||||
getUpdateUrl(), false, 0, false,
|
||||
QString("qBittorrent/%1").arg(VERSION));
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(ipUpdateFinished(QString, QByteArray)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(ipUpdateFailed(QString, QString)));
|
||||
}
|
||||
|
||||
QUrl DNSUpdater::getUpdateUrl() const
|
||||
QString DNSUpdater::getUpdateUrl() const
|
||||
{
|
||||
QUrl url;
|
||||
#ifdef QT_NO_OPENSSL
|
||||
@@ -160,7 +158,7 @@ QUrl DNSUpdater::getUpdateUrl() const
|
||||
}
|
||||
url.setPath("/nic/update");
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifndef QBT_USES_QT5
|
||||
url.addQueryItem("hostname", m_domain);
|
||||
url.addQueryItem("myip", m_lastIP.toString());
|
||||
#else
|
||||
@@ -172,22 +170,20 @@ QUrl DNSUpdater::getUpdateUrl() const
|
||||
Q_ASSERT(url.isValid());
|
||||
|
||||
qDebug() << Q_FUNC_INFO << url.toString();
|
||||
return url;
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
void DNSUpdater::ipUpdateFinished(QNetworkReply *reply)
|
||||
void DNSUpdater::ipUpdateFinished(const QString &url, const QByteArray &data)
|
||||
{
|
||||
if (reply->error()) {
|
||||
// Error
|
||||
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
|
||||
}
|
||||
else {
|
||||
// Parse reply
|
||||
processIPUpdateReply(reply->readAll());
|
||||
}
|
||||
// Clean up
|
||||
reply->deleteLater();
|
||||
sender()->deleteLater();
|
||||
Q_UNUSED(url);
|
||||
// Parse reply
|
||||
processIPUpdateReply(data);
|
||||
}
|
||||
|
||||
void DNSUpdater::ipUpdateFailed(const QString &url, const QString &error)
|
||||
{
|
||||
Q_UNUSED(url);
|
||||
qWarning() << "IP update failed:" << error;
|
||||
}
|
||||
|
||||
void DNSUpdater::processIPUpdateReply(const QString &reply)
|
||||
@@ -196,16 +192,19 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
|
||||
qDebug() << Q_FUNC_INFO << reply;
|
||||
QString code = reply.split(" ").first();
|
||||
qDebug() << Q_FUNC_INFO << "Code:" << code;
|
||||
if (code == "good" || code == "nochg") {
|
||||
|
||||
if ((code == "good") || (code == "nochg")) {
|
||||
logger->addMessage(tr("Your dynamic DNS was successfully updated."), Log::INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((code == "911") || (code == "dnserr")) {
|
||||
logger->addMessage(tr("Dynamic DNS error: The service is temporarily unavailable, it will be retried in 30 minutes."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
// It will retry in 30 minutes because the timer was not stopped
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything bellow is an error, stop updating until the user updates something
|
||||
m_ipCheckTimer.stop();
|
||||
m_lastIP.clear();
|
||||
@@ -214,23 +213,27 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == "badauth") {
|
||||
logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL);
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == "badagent") {
|
||||
logger->addMessage(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please report a bug at http://bugs.qbittorrent.org."),
|
||||
Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == "!donator") {
|
||||
logger->addMessage(tr("Dynamic DNS error: %1 was returned by the service, please report a bug at http://bugs.qbittorrent.org.").arg("!donator"),
|
||||
Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == "abuse") {
|
||||
logger->addMessage(tr("Dynamic DNS error: Your username was blocked due to abuse."), Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
@@ -297,7 +300,7 @@ QUrl DNSUpdater::getRegistrationUrl(int service)
|
||||
case DNS::DYNDNS:
|
||||
return QUrl("https://www.dyndns.com/account/services/hosts/add.html");
|
||||
case DNS::NOIP:
|
||||
return QUrl("http://www.no-ip.com/services/managed_dns/free_dynamic_dns.html");
|
||||
return QUrl("https://www.noip.com/remote-access");
|
||||
default:
|
||||
Q_ASSERT(0);
|
||||
}
|
||||
@@ -33,15 +33,15 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkReply>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
#include "core/preferences.h"
|
||||
|
||||
#include "base/preferences.h"
|
||||
|
||||
namespace Net
|
||||
{
|
||||
{
|
||||
// Based on http://www.dyndns.com/developers/specs/
|
||||
class DNSUpdater : public QObject
|
||||
class DNSUpdater: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -56,15 +56,25 @@ namespace Net
|
||||
|
||||
private slots:
|
||||
void checkPublicIP();
|
||||
void ipRequestFinished(QNetworkReply *reply);
|
||||
void ipRequestFinished(const QString &url, const QByteArray &data);
|
||||
void ipRequestFailed(const QString &url, const QString &error);
|
||||
void updateDNSService();
|
||||
void ipUpdateFinished(QNetworkReply *reply);
|
||||
void ipUpdateFinished(const QString &url, const QByteArray &data);
|
||||
void ipUpdateFailed(const QString &url, const QString &error);
|
||||
|
||||
private:
|
||||
QUrl getUpdateUrl() const;
|
||||
enum State
|
||||
{
|
||||
OK,
|
||||
INVALID_CREDS,
|
||||
FATAL
|
||||
};
|
||||
|
||||
static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min
|
||||
|
||||
QString getUpdateUrl() const;
|
||||
void processIPUpdateReply(const QString &reply);
|
||||
|
||||
private:
|
||||
QHostAddress m_lastIP;
|
||||
QDateTime m_lastIPCheckTime;
|
||||
QTimer m_ipCheckTimer;
|
||||
@@ -74,16 +84,6 @@ namespace Net
|
||||
QString m_domain;
|
||||
QString m_username;
|
||||
QString m_password;
|
||||
|
||||
private:
|
||||
static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min
|
||||
|
||||
enum State
|
||||
{
|
||||
OK,
|
||||
INVALID_CREDS,
|
||||
FATAL
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
#include <QUrl>
|
||||
#include <QDebug>
|
||||
|
||||
#include "core/utils/fs.h"
|
||||
#include "core/utils/gzip.h"
|
||||
#include "core/utils/misc.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/gzip.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "downloadmanager.h"
|
||||
#include "downloadhandler.h"
|
||||
|
||||
@@ -27,19 +27,100 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkProxy>
|
||||
#include <QNetworkCookieJar>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkCookie>
|
||||
#include <QNetworkCookieJar>
|
||||
#include <QSslError>
|
||||
#include <QUrl>
|
||||
#include <QDebug>
|
||||
|
||||
#include "core/preferences.h"
|
||||
#include "base/preferences.h"
|
||||
#include "downloadhandler.h"
|
||||
#include "downloadmanager.h"
|
||||
|
||||
// Spoof Firefox 38 user agent to avoid web server banning
|
||||
const char DEFAULT_USER_AGENT[] = "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0";
|
||||
|
||||
namespace
|
||||
{
|
||||
class NetworkCookieJar: public QNetworkCookieJar
|
||||
{
|
||||
public:
|
||||
explicit NetworkCookieJar(QObject *parent = 0)
|
||||
: QNetworkCookieJar(parent)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = Preferences::instance()->getNetworkCookies();
|
||||
foreach (const QNetworkCookie &cookie, Preferences::instance()->getNetworkCookies()) {
|
||||
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
|
||||
setAllCookies(cookies);
|
||||
}
|
||||
|
||||
~NetworkCookieJar()
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = allCookies();
|
||||
foreach (const QNetworkCookie &cookie, allCookies()) {
|
||||
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
|
||||
Preferences::instance()->setNetworkCookies(cookies);
|
||||
}
|
||||
|
||||
#ifndef QBT_USES_QT5
|
||||
virtual bool deleteCookie(const QNetworkCookie &cookie)
|
||||
{
|
||||
auto myCookies = allCookies();
|
||||
|
||||
QList<QNetworkCookie>::Iterator it;
|
||||
for (it = myCookies.begin(); it != myCookies.end(); ++it) {
|
||||
if ((it->name() == cookie.name())
|
||||
&& (it->domain() == cookie.domain())
|
||||
&& (it->path() == cookie.path())) {
|
||||
myCookies.erase(it);
|
||||
setAllCookies(myCookies);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
QList<QNetworkCookie> cookiesForUrl(const QUrl &url) const override
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = QNetworkCookieJar::cookiesForUrl(url);
|
||||
foreach (const QNetworkCookie &cookie, QNetworkCookieJar::cookiesForUrl(url)) {
|
||||
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url) override
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = cookieList;
|
||||
foreach (const QNetworkCookie &cookie, cookieList) {
|
||||
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
|
||||
return QNetworkCookieJar::setCookiesFromUrl(cookies, url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
using namespace Net;
|
||||
|
||||
DownloadManager *DownloadManager::m_instance = 0;
|
||||
@@ -50,10 +131,7 @@ DownloadManager::DownloadManager(QObject *parent)
|
||||
#ifndef QT_NO_OPENSSL
|
||||
connect(&m_networkManager, SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)), this, SLOT(ignoreSslErrors(QNetworkReply *, QList<QSslError>)));
|
||||
#endif
|
||||
}
|
||||
|
||||
DownloadManager::~DownloadManager()
|
||||
{
|
||||
m_networkManager.setCookieJar(new NetworkCookieJar(this));
|
||||
}
|
||||
|
||||
void DownloadManager::initInstance()
|
||||
@@ -75,7 +153,7 @@ DownloadManager *DownloadManager::instance()
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
DownloadHandler *DownloadManager::downloadUrl(const QString &url, bool saveToFile, qint64 limit, bool handleRedirectToMagnet)
|
||||
DownloadHandler *DownloadManager::downloadUrl(const QString &url, bool saveToFile, qint64 limit, bool handleRedirectToMagnet, const QString &userAgent)
|
||||
{
|
||||
// Update proxy settings
|
||||
applyProxySettings();
|
||||
@@ -85,29 +163,36 @@ DownloadHandler *DownloadManager::downloadUrl(const QString &url, bool saveToFil
|
||||
const QUrl qurl = QUrl::fromEncoded(url.toUtf8());
|
||||
QNetworkRequest request(qurl);
|
||||
|
||||
// Spoof Firefox 38 user agent to avoid web server banning
|
||||
request.setRawHeader("User-Agent", "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0");
|
||||
if (userAgent.isEmpty())
|
||||
request.setRawHeader("User-Agent", DEFAULT_USER_AGENT);
|
||||
else
|
||||
request.setRawHeader("User-Agent", userAgent.toUtf8());
|
||||
|
||||
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
|
||||
request.setRawHeader("Referer", request.url().toEncoded().data());
|
||||
|
||||
qDebug("Downloading %s...", request.url().toEncoded().data());
|
||||
qDebug() << "Cookies:" << m_networkManager.cookieJar()->cookiesForUrl(request.url());
|
||||
// accept gzip
|
||||
request.setRawHeader("Accept-Encoding", "gzip");
|
||||
return new DownloadHandler(m_networkManager.get(request), this, saveToFile, limit, handleRedirectToMagnet);
|
||||
}
|
||||
|
||||
QList<QNetworkCookie> DownloadManager::cookiesForUrl(const QString &url) const
|
||||
QList<QNetworkCookie> DownloadManager::cookiesForUrl(const QUrl &url) const
|
||||
{
|
||||
return m_networkManager.cookieJar()->cookiesForUrl(url);
|
||||
}
|
||||
|
||||
bool DownloadManager::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
|
||||
{
|
||||
qDebug("Setting %d cookies for url: %s", cookieList.size(), qPrintable(url.toString()));
|
||||
return m_networkManager.cookieJar()->setCookiesFromUrl(cookieList, url);
|
||||
}
|
||||
|
||||
bool DownloadManager::deleteCookie(const QNetworkCookie &cookie)
|
||||
{
|
||||
return static_cast<NetworkCookieJar *>(m_networkManager.cookieJar())->deleteCookie(cookie);
|
||||
}
|
||||
|
||||
void DownloadManager::applyProxySettings()
|
||||
{
|
||||
QNetworkProxy proxy;
|
||||
@@ -33,12 +33,10 @@
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QNetworkReply;
|
||||
class QNetworkCookie;
|
||||
class QSslError;
|
||||
class QUrl;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Net
|
||||
{
|
||||
@@ -53,9 +51,10 @@ namespace Net
|
||||
static void freeInstance();
|
||||
static DownloadManager *instance();
|
||||
|
||||
DownloadHandler *downloadUrl(const QString &url, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false);
|
||||
QList<QNetworkCookie> cookiesForUrl(const QString &url) const;
|
||||
DownloadHandler *downloadUrl(const QString &url, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false, const QString &userAgent = "");
|
||||
QList<QNetworkCookie> cookiesForUrl(const QUrl &url) const;
|
||||
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url);
|
||||
bool deleteCookie(const QNetworkCookie &cookie);
|
||||
|
||||
private slots:
|
||||
#ifndef QT_NO_OPENSSL
|
||||
@@ -63,8 +62,7 @@ namespace Net
|
||||
#endif
|
||||
|
||||
private:
|
||||
DownloadManager(QObject *parent = 0);
|
||||
~DownloadManager();
|
||||
explicit DownloadManager(QObject *parent = 0);
|
||||
|
||||
void applyProxySettings();
|
||||
|
||||
@@ -33,16 +33,16 @@
|
||||
#include <QHostAddress>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/logger.h"
|
||||
#include "core/preferences.h"
|
||||
#include "core/utils/fs.h"
|
||||
#include "core/utils/gzip.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/gzip.h"
|
||||
#include "downloadmanager.h"
|
||||
#include "downloadhandler.h"
|
||||
#include "private/geoipdatabase.h"
|
||||
#include "geoipmanager.h"
|
||||
|
||||
static const char DATABASE_URL[] = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz";
|
||||
static const char DATABASE_URL[] = "https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz";
|
||||
static const char GEOIP_FOLDER[] = "GeoIP";
|
||||
static const char GEOIP_FILENAME[] = "GeoLite2-Country.mmdb";
|
||||
static const int CACHE_SIZE = 1000;
|
||||
@@ -131,16 +131,15 @@ QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
|
||||
return QString();
|
||||
}
|
||||
|
||||
// http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm
|
||||
QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
{
|
||||
static QHash<QString, QString> countries;
|
||||
static bool initialized = false;
|
||||
|
||||
// ISO 3166-1 alpha-2 codes
|
||||
// http://www.iso.org/iso/home/standards/country_codes/country_names_and_code_elements_txt-temp.htm
|
||||
if (!initialized) {
|
||||
countries[QString()] = tr("N/A");
|
||||
countries["AP"] = tr("Asia/Pacific Region");
|
||||
countries["EU"] = tr("Europe");
|
||||
// Officially assigned
|
||||
countries["AD"] = tr("Andorra");
|
||||
countries["AE"] = tr("United Arab Emirates");
|
||||
countries["AF"] = tr("Afghanistan");
|
||||
@@ -148,7 +147,6 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["AI"] = tr("Anguilla");
|
||||
countries["AL"] = tr("Albania");
|
||||
countries["AM"] = tr("Armenia");
|
||||
countries["AN"] = tr("Netherlands Antilles");
|
||||
countries["AO"] = tr("Angola");
|
||||
countries["AQ"] = tr("Antarctica");
|
||||
countries["AR"] = tr("Argentina");
|
||||
@@ -156,6 +154,7 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["AT"] = tr("Austria");
|
||||
countries["AU"] = tr("Australia");
|
||||
countries["AW"] = tr("Aruba");
|
||||
countries["AX"] = tr("Aland Islands");
|
||||
countries["AZ"] = tr("Azerbaijan");
|
||||
countries["BA"] = tr("Bosnia and Herzegovina");
|
||||
countries["BB"] = tr("Barbados");
|
||||
@@ -166,9 +165,11 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["BH"] = tr("Bahrain");
|
||||
countries["BI"] = tr("Burundi");
|
||||
countries["BJ"] = tr("Benin");
|
||||
countries["BL"] = tr("Saint Barthelemy");
|
||||
countries["BM"] = tr("Bermuda");
|
||||
countries["BN"] = tr("Brunei Darussalam");
|
||||
countries["BO"] = tr("Bolivia");
|
||||
countries["BO"] = tr("Bolivia, Plurinational State of");
|
||||
countries["BQ"] = tr("Bonaire, Sint Eustatius and Saba");
|
||||
countries["BR"] = tr("Brazil");
|
||||
countries["BS"] = tr("Bahamas");
|
||||
countries["BT"] = tr("Bhutan");
|
||||
@@ -182,7 +183,7 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["CF"] = tr("Central African Republic");
|
||||
countries["CG"] = tr("Congo");
|
||||
countries["CH"] = tr("Switzerland");
|
||||
countries["CI"] = tr("Cote D'Ivoire");
|
||||
countries["CI"] = tr("Cote d'Ivoire");
|
||||
countries["CK"] = tr("Cook Islands");
|
||||
countries["CL"] = tr("Chile");
|
||||
countries["CM"] = tr("Cameroon");
|
||||
@@ -191,6 +192,7 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["CR"] = tr("Costa Rica");
|
||||
countries["CU"] = tr("Cuba");
|
||||
countries["CV"] = tr("Cape Verde");
|
||||
countries["CW"] = tr("Curacao");
|
||||
countries["CX"] = tr("Christmas Island");
|
||||
countries["CY"] = tr("Cyprus");
|
||||
countries["CZ"] = tr("Czech Republic");
|
||||
@@ -213,12 +215,12 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["FM"] = tr("Micronesia, Federated States of");
|
||||
countries["FO"] = tr("Faroe Islands");
|
||||
countries["FR"] = tr("France");
|
||||
countries["FX"] = tr("France, Metropolitan");
|
||||
countries["GA"] = tr("Gabon");
|
||||
countries["GB"] = tr("United Kingdom");
|
||||
countries["GD"] = tr("Grenada");
|
||||
countries["GE"] = tr("Georgia");
|
||||
countries["GF"] = tr("French Guiana");
|
||||
countries["GG"] = tr("Guernsey");
|
||||
countries["GH"] = tr("Ghana");
|
||||
countries["GI"] = tr("Gibraltar");
|
||||
countries["GL"] = tr("Greenland");
|
||||
@@ -241,12 +243,14 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["ID"] = tr("Indonesia");
|
||||
countries["IE"] = tr("Ireland");
|
||||
countries["IL"] = tr("Israel");
|
||||
countries["IM"] = tr("Isle of Man");
|
||||
countries["IN"] = tr("India");
|
||||
countries["IO"] = tr("British Indian Ocean Territory");
|
||||
countries["IQ"] = tr("Iraq");
|
||||
countries["IR"] = tr("Iran, Islamic Republic of");
|
||||
countries["IS"] = tr("Iceland");
|
||||
countries["IT"] = tr("Italy");
|
||||
countries["JE"] = tr("Jersey");
|
||||
countries["JM"] = tr("Jamaica");
|
||||
countries["JO"] = tr("Jordan");
|
||||
countries["JP"] = tr("Japan");
|
||||
@@ -271,17 +275,19 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["LT"] = tr("Lithuania");
|
||||
countries["LU"] = tr("Luxembourg");
|
||||
countries["LV"] = tr("Latvia");
|
||||
countries["LY"] = tr("Libyan Arab Jamahiriya");
|
||||
countries["LY"] = tr("Libya");
|
||||
countries["MA"] = tr("Morocco");
|
||||
countries["MC"] = tr("Monaco");
|
||||
countries["MD"] = tr("Moldova, Republic of");
|
||||
countries["ME"] = tr("Montenegro");
|
||||
countries["MF"] = tr("Saint Martin (French part)");
|
||||
countries["MG"] = tr("Madagascar");
|
||||
countries["MH"] = tr("Marshall Islands");
|
||||
countries["MK"] = tr("Macedonia");
|
||||
countries["MK"] = tr("Macedonia, The Former Yugoslav Republic of");
|
||||
countries["ML"] = tr("Mali");
|
||||
countries["MM"] = tr("Myanmar");
|
||||
countries["MN"] = tr("Mongolia");
|
||||
countries["MO"] = tr("Macau");
|
||||
countries["MO"] = tr("Macao");
|
||||
countries["MP"] = tr("Northern Mariana Islands");
|
||||
countries["MQ"] = tr("Martinique");
|
||||
countries["MR"] = tr("Mauritania");
|
||||
@@ -314,15 +320,16 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["PK"] = tr("Pakistan");
|
||||
countries["PL"] = tr("Poland");
|
||||
countries["PM"] = tr("Saint Pierre and Miquelon");
|
||||
countries["PN"] = tr("Pitcairn Islands");
|
||||
countries["PN"] = tr("Pitcairn");
|
||||
countries["PR"] = tr("Puerto Rico");
|
||||
countries["PS"] = tr("Palestinian Territory");
|
||||
countries["PS"] = tr("Palestine, State of");
|
||||
countries["PT"] = tr("Portugal");
|
||||
countries["PW"] = tr("Palau");
|
||||
countries["PY"] = tr("Paraguay");
|
||||
countries["QA"] = tr("Qatar");
|
||||
countries["RE"] = tr("Reunion");
|
||||
countries["RO"] = tr("Romania");
|
||||
countries["RS"] = tr("Serbia");
|
||||
countries["RU"] = tr("Russian Federation");
|
||||
countries["RW"] = tr("Rwanda");
|
||||
countries["SA"] = tr("Saudi Arabia");
|
||||
@@ -331,7 +338,7 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["SD"] = tr("Sudan");
|
||||
countries["SE"] = tr("Sweden");
|
||||
countries["SG"] = tr("Singapore");
|
||||
countries["SH"] = tr("Saint Helena");
|
||||
countries["SH"] = tr("Saint Helena, Ascension and Tristan da Cunha");
|
||||
countries["SI"] = tr("Slovenia");
|
||||
countries["SJ"] = tr("Svalbard and Jan Mayen");
|
||||
countries["SK"] = tr("Slovakia");
|
||||
@@ -340,8 +347,10 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["SN"] = tr("Senegal");
|
||||
countries["SO"] = tr("Somalia");
|
||||
countries["SR"] = tr("Suriname");
|
||||
countries["SS"] = tr("South Sudan");
|
||||
countries["ST"] = tr("Sao Tome and Principe");
|
||||
countries["SV"] = tr("El Salvador");
|
||||
countries["SX"] = tr("Sint Maarten (Dutch part)");
|
||||
countries["SY"] = tr("Syrian Arab Republic");
|
||||
countries["SZ"] = tr("Swaziland");
|
||||
countries["TC"] = tr("Turks and Caicos Islands");
|
||||
@@ -351,10 +360,10 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["TH"] = tr("Thailand");
|
||||
countries["TJ"] = tr("Tajikistan");
|
||||
countries["TK"] = tr("Tokelau");
|
||||
countries["TL"] = tr("Timor-Leste");
|
||||
countries["TM"] = tr("Turkmenistan");
|
||||
countries["TN"] = tr("Tunisia");
|
||||
countries["TO"] = tr("Tonga");
|
||||
countries["TL"] = tr("Timor-Leste");
|
||||
countries["TR"] = tr("Turkey");
|
||||
countries["TT"] = tr("Trinidad and Tobago");
|
||||
countries["TV"] = tr("Tuvalu");
|
||||
@@ -368,30 +377,20 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
countries["UZ"] = tr("Uzbekistan");
|
||||
countries["VA"] = tr("Holy See (Vatican City State)");
|
||||
countries["VC"] = tr("Saint Vincent and the Grenadines");
|
||||
countries["VE"] = tr("Venezuela");
|
||||
countries["VE"] = tr("Venezuela, Bolivarian Republic of");
|
||||
countries["VG"] = tr("Virgin Islands, British");
|
||||
countries["VI"] = tr("Virgin Islands, U.S.");
|
||||
countries["VN"] = tr("Vietnam");
|
||||
countries["VN"] = tr("Viet Nam");
|
||||
countries["VU"] = tr("Vanuatu");
|
||||
countries["WF"] = tr("Wallis and Futuna");
|
||||
countries["WS"] = tr("Samoa");
|
||||
countries["YE"] = tr("Yemen");
|
||||
countries["YT"] = tr("Mayotte");
|
||||
countries["RS"] = tr("Serbia");
|
||||
countries["ZA"] = tr("South Africa");
|
||||
countries["ZM"] = tr("Zambia");
|
||||
countries["ME"] = tr("Montenegro");
|
||||
countries["ZW"] = tr("Zimbabwe");
|
||||
countries["A1"] = tr("Anonymous Proxy");
|
||||
countries["A2"] = tr("Satellite Provider");
|
||||
countries["O1"] = tr("Other");
|
||||
countries["AX"] = tr("Aland Islands");
|
||||
countries["GG"] = tr("Guernsey");
|
||||
countries["IM"] = tr("Isle of Man");
|
||||
countries["JE"] = tr("Jersey");
|
||||
countries["BL"] = tr("Saint Barthelemy");
|
||||
countries["MF"] = tr("Saint Martin");
|
||||
|
||||
countries[QString()] = tr("N/A");
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
|
||||
#include <libtorrent/session.hpp>
|
||||
|
||||
#include "core/logger.h"
|
||||
#include "core/preferences.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "portforwarder.h"
|
||||
|
||||
namespace libt = libtorrent;
|
||||
@@ -33,7 +33,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
|
||||
#include "core/types.h"
|
||||
#include "base/types.h"
|
||||
#include "geoipdatabase.h"
|
||||
|
||||
namespace
|
||||
@@ -66,7 +66,7 @@ namespace
|
||||
Float = 15
|
||||
};
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifndef QBT_USES_QT5
|
||||
Q_IPV6ADDR createMappedAddress(quint32 ip4);
|
||||
#endif
|
||||
}
|
||||
@@ -166,7 +166,7 @@ QDateTime GeoIPDatabase::buildEpoch() const
|
||||
|
||||
QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
|
||||
{
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifndef QBT_USES_QT5
|
||||
Q_IPV6ADDR addr = hostAddr.protocol() == QAbstractSocket::IPv4Protocol
|
||||
? createMappedAddress(hostAddr.toIPv4Address())
|
||||
: hostAddr.toIPv6Address();
|
||||
@@ -499,7 +499,7 @@ QVariant GeoIPDatabase::readArrayValue(quint32 &offset, quint32 count) const
|
||||
|
||||
namespace
|
||||
{
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifndef QBT_USES_QT5
|
||||
Q_IPV6ADDR createMappedAddress(quint32 ip4)
|
||||
{
|
||||
Q_IPV6ADDR ip6;
|
||||
@@ -33,8 +33,8 @@
|
||||
*/
|
||||
|
||||
#include "smtp.h"
|
||||
#include "core/preferences.h"
|
||||
#include "core/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/logger.h"
|
||||
|
||||
#include <QTextStream>
|
||||
#ifndef QT_NO_OPENSSL
|
||||
@@ -58,9 +58,8 @@ namespace
|
||||
QByteArray hmacMD5(QByteArray key, const QByteArray &msg)
|
||||
{
|
||||
const int blockSize = 64; // HMAC-MD5 block size
|
||||
if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with MD5 compression
|
||||
if (key.length() > blockSize) // if key is longer than block size (64), reduce key length with MD5 compression
|
||||
key = QCryptographicHash::hash(key, QCryptographicHash::Md5);
|
||||
}
|
||||
|
||||
QByteArray innerPadding(blockSize, char(0x36)); // initialize inner padding with char "6"
|
||||
QByteArray outerPadding(blockSize, char(0x5c)); // initialize outer padding with char "\"
|
||||
@@ -96,6 +95,7 @@ Smtp::Smtp(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_state(Init)
|
||||
, m_useSsl(false)
|
||||
, m_authType(AuthPlain)
|
||||
{
|
||||
#ifndef QT_NO_OPENSSL
|
||||
m_socket = new QSslSocket(this);
|
||||
@@ -122,18 +122,17 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
|
||||
{
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
QTextCodec* latin1 = QTextCodec::codecForName("latin1");
|
||||
m_message = "";
|
||||
m_message += encodeMimeHeader("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1);
|
||||
m_message += encodeMimeHeader("From", from, latin1);
|
||||
m_message += encodeMimeHeader("Subject", subject, latin1);
|
||||
m_message += encodeMimeHeader("To", to, latin1);
|
||||
m_message += "MIME-Version: 1.0\r\n";
|
||||
m_message += "Content-Type: text/plain; charset=UTF-8\r\n";
|
||||
m_message += "Content-Transfer-Encoding: base64\r\n";
|
||||
m_message += "\r\n";
|
||||
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
|
||||
+ encodeMimeHeader("From", from, latin1)
|
||||
+ encodeMimeHeader("Subject", subject, latin1)
|
||||
+ encodeMimeHeader("To", to, latin1)
|
||||
+ "MIME-Version: 1.0\r\n"
|
||||
+ "Content-Type: text/plain; charset=UTF-8\r\n"
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
+ "\r\n";
|
||||
// Encode the body in base64
|
||||
QString crlf_body = body;
|
||||
QByteArray b = crlf_body.replace("\n","\r\n").toUtf8().toBase64();
|
||||
QByteArray b = crlf_body.replace("\n", "\r\n").toUtf8().toBase64();
|
||||
int ct = b.length();
|
||||
for (int i = 0; i < ct; i += 78)
|
||||
m_message += b.mid(i, 78);
|
||||
@@ -153,10 +152,10 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
|
||||
}
|
||||
else {
|
||||
#endif
|
||||
m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
|
||||
m_useSsl = false;
|
||||
m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
|
||||
m_useSsl = false;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -185,7 +184,7 @@ void Smtp::readyRead()
|
||||
ehlo();
|
||||
}
|
||||
else {
|
||||
logError("Connection failed, unrecognized reply: "+line);
|
||||
logError("Connection failed, unrecognized reply: " + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
@@ -222,7 +221,7 @@ void Smtp::readyRead()
|
||||
}
|
||||
else {
|
||||
// Authentication failed!
|
||||
logError("Authentication failed, msg: "+line);
|
||||
logError("Authentication failed, msg: " + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
@@ -233,7 +232,7 @@ void Smtp::readyRead()
|
||||
m_state = Data;
|
||||
}
|
||||
else {
|
||||
logError("<mail from> was rejected by server, msg: "+line);
|
||||
logError("<mail from> was rejected by server, msg: " + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
@@ -244,7 +243,7 @@ void Smtp::readyRead()
|
||||
m_state = Body;
|
||||
}
|
||||
else {
|
||||
logError("<Rcpt to> was rejected by server, msg: "+line);
|
||||
logError("<Rcpt to> was rejected by server, msg: " + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
@@ -255,7 +254,7 @@ void Smtp::readyRead()
|
||||
m_state = Quit;
|
||||
}
|
||||
else {
|
||||
logError("<data> was rejected by server, msg: "+line);
|
||||
logError("<data> was rejected by server, msg: " + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
@@ -267,7 +266,7 @@ void Smtp::readyRead()
|
||||
m_state = Close;
|
||||
}
|
||||
else {
|
||||
logError("Message was rejected by the server, error: "+line);
|
||||
logError("Message was rejected by the server, error: " + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
@@ -308,9 +307,9 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, QTex
|
||||
line += "=?utf-8?b?";
|
||||
for (int i = 0; i < ct; i += 4) {
|
||||
/*if (line.length() > 72) {
|
||||
rv += line + "?\n\r";
|
||||
line = " =?utf-8?b?";
|
||||
}*/
|
||||
rv += line + "?\n\r";
|
||||
line = " =?utf-8?b?";
|
||||
}*/
|
||||
line = line + base64.mid(i, 4);
|
||||
}
|
||||
line += "?="; // end encoded-word atom
|
||||
@@ -346,7 +345,7 @@ void Smtp::parseEhloResponse(const QByteArray &code, bool continued, const QStri
|
||||
else {
|
||||
// Both EHLO and HELO failed, chances are this is NOT
|
||||
// a SMTP server
|
||||
logError("Both EHLO and HELO failed, msg: "+line);
|
||||
logError("Both EHLO and HELO failed, msg: " + line);
|
||||
m_state = Close;
|
||||
}
|
||||
return;
|
||||
@@ -361,7 +360,7 @@ void Smtp::parseEhloResponse(const QByteArray &code, bool continued, const QStri
|
||||
else {
|
||||
// greeting followed by extensions
|
||||
m_state = EhloGreetReceived;
|
||||
qDebug () << "EHLO greet received";
|
||||
qDebug() << "EHLO greet received";
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -418,7 +417,7 @@ void Smtp::authenticate()
|
||||
// Skip authentication
|
||||
logError("The SMTP server does not seem to support any of the authentications modes "
|
||||
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
|
||||
"knowing it is likely to fail... Server Auth Modes: "+auth.join("|"));
|
||||
"knowing it is likely to fail... Server Auth Modes: " + auth.join("|"));
|
||||
m_state = Authenticated;
|
||||
// At this point the server will not send any response
|
||||
// So fill the buffer with a fake one to pass the tests
|
||||
@@ -449,7 +448,7 @@ void Smtp::authCramMD5(const QByteArray& challenge)
|
||||
}
|
||||
else {
|
||||
QByteArray response = m_username.toLatin1() + ' '
|
||||
+ hmacMD5(m_password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
|
||||
+ hmacMD5(m_password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
|
||||
m_socket->write(response.toBase64() + "\r\n");
|
||||
m_socket->flush();
|
||||
m_state = AuthSent;
|
||||
@@ -469,7 +468,7 @@ void Smtp::authPlain()
|
||||
auth += m_password.toLatin1();
|
||||
qDebug() << "password: " << m_password.toLatin1();
|
||||
// Send it
|
||||
m_socket->write("auth plain "+ auth.toBase64() + "\r\n");
|
||||
m_socket->write("auth plain " + auth.toBase64() + "\r\n");
|
||||
m_socket->flush();
|
||||
m_state = AuthSent;
|
||||
}
|
||||
@@ -500,3 +499,29 @@ void Smtp::logError(const QString &msg)
|
||||
qDebug() << "Email Notification Error:" << msg;
|
||||
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
|
||||
}
|
||||
|
||||
QString Smtp::getCurrentDateTime() const
|
||||
{
|
||||
// return date & time in the format specified in RFC 2822, section 3.3
|
||||
const QDateTime nowDateTime = QDateTime::currentDateTime();
|
||||
const QDate nowDate = nowDateTime.date();
|
||||
const QLocale eng(QLocale::English);
|
||||
|
||||
QString timeStr = nowDateTime.time().toString("HH:mm:ss");
|
||||
QString weekDayStr = eng.dayName(nowDate.dayOfWeek(), QLocale::ShortFormat);
|
||||
QString dayStr = QString::number(nowDate.day());
|
||||
QString monthStr = eng.monthName(nowDate.month(), QLocale::ShortFormat);
|
||||
QString yearStr = QString::number(nowDate.year());
|
||||
|
||||
QDateTime tmp = nowDateTime;
|
||||
tmp.setTimeSpec(Qt::UTC);
|
||||
int timeOffsetHour = nowDateTime.secsTo(tmp) / 3600;
|
||||
int timeOffsetMin = nowDateTime.secsTo(tmp) / 60 - (60 * timeOffsetHour);
|
||||
int timeOffset = timeOffsetHour * 100 + timeOffsetMin;
|
||||
char buf[6] = {0};
|
||||
std::snprintf(buf, sizeof(buf), "%+05d", timeOffset);
|
||||
QString timeOffsetStr = buf;
|
||||
|
||||
QString ret = weekDayStr + ", " + dayStr + " " + monthStr + " " + yearStr + " " + timeStr + " " + timeOffsetStr;
|
||||
return ret;
|
||||
}
|
||||
@@ -102,6 +102,7 @@ namespace Net
|
||||
void authPlain();
|
||||
void authLogin();
|
||||
void logError(const QString &msg);
|
||||
QString getCurrentDateTime() const;
|
||||
|
||||
QByteArray m_message;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
@@ -30,15 +30,10 @@
|
||||
* Contact : hammered999@gmail.com
|
||||
*/
|
||||
|
||||
#include "preferences.h"
|
||||
#include "qinisettings.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QPair>
|
||||
#include <QDir>
|
||||
#include <QReadLocker>
|
||||
#include <QWriteLocker>
|
||||
#include <QSettings>
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#include <QApplication>
|
||||
@@ -47,79 +42,27 @@
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <ShlObj.h>
|
||||
#include <shlobj.h>
|
||||
#include <winreg.h>
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include "core/utils/fs.h"
|
||||
#include "core/utils/misc.h"
|
||||
#ifdef Q_OS_MAC
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "utils/fs.h"
|
||||
#include "utils/misc.h"
|
||||
#include "settingsstorage.h"
|
||||
#include "logger.h"
|
||||
#include "preferences.h"
|
||||
|
||||
Preferences* Preferences::m_instance = 0;
|
||||
|
||||
Preferences::Preferences()
|
||||
: m_randomPort(rand() % 64512 + 1024)
|
||||
, dirty(false)
|
||||
, lock(QReadWriteLock::Recursive)
|
||||
{
|
||||
qRegisterMetaTypeStreamOperators<MaxRatioAction>("MaxRatioAction");
|
||||
|
||||
QIniSettings *settings = new QIniSettings;
|
||||
#ifndef Q_OS_MAC
|
||||
QIniSettings *settings_new = new QIniSettings("qBittorrent", "qBittorrent_new");
|
||||
QStringList keys = settings_new->allKeys();
|
||||
bool use_new = false;
|
||||
|
||||
// This means that the PC closed either due to power outage
|
||||
// or because the disk was full. In any case the settings weren't transfered
|
||||
// in their final position. So assume that qbittorrent_new.ini/qbittorrent_new.conf
|
||||
// contains the most recent settings.
|
||||
if (!keys.isEmpty()) {
|
||||
Logger::instance()->addMessage(tr("Detected unclean program exit. Using fallback file to restore settings."), Log::WARNING);
|
||||
use_new = true;
|
||||
dirty = true;
|
||||
}
|
||||
else {
|
||||
keys = settings->allKeys();
|
||||
}
|
||||
#else
|
||||
QStringList keys = settings->allKeys();
|
||||
#endif
|
||||
|
||||
// Copy everything into memory. This means even keys inserted in the file manually
|
||||
// or that we don't touch directly in this code(eg disabled by ifdef). This ensures
|
||||
// that they will be copied over when save our settings to disk.
|
||||
for (QStringList::const_iterator i = keys.begin(), e = keys.end(); i != e; ++i) {
|
||||
#ifndef Q_OS_MAC
|
||||
if (!use_new)
|
||||
m_data[*i] = settings->value(*i);
|
||||
else
|
||||
m_data[*i] = settings_new->value(*i);
|
||||
#else
|
||||
m_data[*i] = settings->value(*i);
|
||||
#endif
|
||||
}
|
||||
|
||||
//Ensures sync to disk before we attempt to manipulate the files from save().
|
||||
delete settings;
|
||||
#ifndef Q_OS_MAC
|
||||
QString new_path = settings_new->fileName();
|
||||
delete settings_new;
|
||||
Utils::Fs::forceRemove(new_path);
|
||||
|
||||
if (use_new)
|
||||
save();
|
||||
#endif
|
||||
|
||||
timer.setSingleShot(true);
|
||||
timer.setInterval(5 * 1000);
|
||||
connect(&timer, SIGNAL(timeout()), SLOT(save()));
|
||||
}
|
||||
|
||||
Preferences::~Preferences()
|
||||
{
|
||||
save();
|
||||
}
|
||||
|
||||
Preferences *Preferences::instance()
|
||||
@@ -141,71 +84,14 @@ void Preferences::freeInstance()
|
||||
}
|
||||
}
|
||||
|
||||
bool Preferences::save()
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
if (!dirty) return false;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
// QSettings delete the file before writing it out. This can result in problems
|
||||
// if the disk is full or a power outage occurs. Those events might occur
|
||||
// between deleting the file and recreating it. This is a safety measure.
|
||||
// Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds
|
||||
// replace qBittorrent_new.ini/qBittorrent.conf with it.
|
||||
QIniSettings *settings = new QIniSettings("qBittorrent", "qBittorrent_new");
|
||||
#else
|
||||
QIniSettings *settings = new QIniSettings;
|
||||
#endif
|
||||
|
||||
for (QHash<QString, QVariant>::const_iterator i = m_data.begin(), e = m_data.end(); i != e; ++i)
|
||||
settings->setValue(i.key(), i.value());
|
||||
|
||||
dirty = false;
|
||||
locker.unlock();
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
settings->sync(); // Important to get error status
|
||||
QString new_path = settings->fileName();
|
||||
QSettings::Status status = settings->status();
|
||||
|
||||
if (status != QSettings::NoError) {
|
||||
if (status == QSettings::AccessError)
|
||||
Logger::instance()->addMessage(tr("An access error occurred while trying to write the configuration file."), Log::CRITICAL);
|
||||
else
|
||||
Logger::instance()->addMessage(tr("A format error occurred while trying to write the configuration file."), Log::CRITICAL);
|
||||
|
||||
delete settings;
|
||||
Utils::Fs::forceRemove(new_path);
|
||||
return false;
|
||||
}
|
||||
delete settings;
|
||||
QString final_path = new_path;
|
||||
int index = final_path.lastIndexOf("_new", -1, Qt::CaseInsensitive);
|
||||
final_path.remove(index, 4);
|
||||
Utils::Fs::forceRemove(final_path);
|
||||
QFile::rename(new_path, final_path);
|
||||
#else
|
||||
delete settings;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const QVariant Preferences::value(const QString &key, const QVariant &defaultValue) const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
return m_data.value(key, defaultValue);
|
||||
return SettingsStorage::instance()->loadValue(key, defaultValue);
|
||||
}
|
||||
|
||||
void Preferences::setValue(const QString &key, const QVariant &value)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
if (m_data.value(key) == value)
|
||||
return;
|
||||
dirty = true;
|
||||
timer.start();
|
||||
m_data.insert(key, value);
|
||||
SettingsStorage::instance()->storeValue(key, value);
|
||||
}
|
||||
|
||||
// General options
|
||||
@@ -269,6 +155,26 @@ void Preferences::setAlternatingRowColors(bool b)
|
||||
setValue("Preferences/General/AlternatingRowColors", b);
|
||||
}
|
||||
|
||||
bool Preferences::getHideZeroValues() const
|
||||
{
|
||||
return value("Preferences/General/HideZeroValues", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setHideZeroValues(bool b)
|
||||
{
|
||||
setValue("Preferences/General/HideZeroValues", b);
|
||||
}
|
||||
|
||||
int Preferences::getHideZeroComboValues() const
|
||||
{
|
||||
return value("Preferences/General/HideZeroComboValues", 0).toInt();
|
||||
}
|
||||
|
||||
void Preferences::setHideZeroComboValues(int n)
|
||||
{
|
||||
setValue("Preferences/General/HideZeroComboValues", n);
|
||||
}
|
||||
|
||||
bool Preferences::useRandomPort() const
|
||||
{
|
||||
return value("Preferences/General/UseRandomPort", false).toBool();
|
||||
@@ -331,7 +237,7 @@ void Preferences::setStartMinimized(bool b)
|
||||
|
||||
bool Preferences::isSplashScreenDisabled() const
|
||||
{
|
||||
return value("Preferences/General/NoSplashScreen", false).toBool();
|
||||
return value("Preferences/General/NoSplashScreen", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setSplashScreenDisabled(bool b)
|
||||
@@ -371,40 +277,6 @@ void Preferences::setWinStartup(bool b)
|
||||
#endif
|
||||
|
||||
// Downloads
|
||||
QString Preferences::getSavePath() const
|
||||
{
|
||||
QString save_path = value("Preferences/Downloads/SavePath").toString();
|
||||
if (!save_path.isEmpty())
|
||||
return Utils::Fs::fromNativePath(save_path);
|
||||
return Utils::Fs::QDesktopServicesDownloadLocation();
|
||||
}
|
||||
|
||||
void Preferences::setSavePath(const QString &save_path)
|
||||
{
|
||||
setValue("Preferences/Downloads/SavePath", Utils::Fs::fromNativePath(save_path));
|
||||
}
|
||||
|
||||
bool Preferences::isTempPathEnabled() const
|
||||
{
|
||||
return value("Preferences/Downloads/TempPathEnabled", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setTempPathEnabled(bool enabled)
|
||||
{
|
||||
setValue("Preferences/Downloads/TempPathEnabled", enabled);
|
||||
}
|
||||
|
||||
QString Preferences::getTempPath() const
|
||||
{
|
||||
const QString temp = QDir(getSavePath()).absoluteFilePath("temp");
|
||||
return Utils::Fs::fromNativePath(value("Preferences/Downloads/TempPath", temp).toString());
|
||||
}
|
||||
|
||||
void Preferences::setTempPath(const QString &path)
|
||||
{
|
||||
setValue("Preferences/Downloads/TempPath", Utils::Fs::fromNativePath(path));
|
||||
}
|
||||
|
||||
bool Preferences::useIncompleteFilesExtension() const
|
||||
{
|
||||
return value("Preferences/Downloads/UseIncompleteExtension", false).toBool();
|
||||
@@ -415,16 +287,6 @@ void Preferences::useIncompleteFilesExtension(bool enabled)
|
||||
setValue("Preferences/Downloads/UseIncompleteExtension", enabled);
|
||||
}
|
||||
|
||||
bool Preferences::appendTorrentLabel() const
|
||||
{
|
||||
return value("Preferences/Downloads/AppendLabel", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setAppendTorrentLabel(bool b)
|
||||
{
|
||||
setValue("Preferences/Downloads/AppendLabel", b);
|
||||
}
|
||||
|
||||
QString Preferences::lastLocationPath() const
|
||||
{
|
||||
return Utils::Fs::fromNativePath(value("Preferences/Downloads/LastLocationPath").toString());
|
||||
@@ -445,76 +307,15 @@ void Preferences::preAllocateAllFiles(bool enabled)
|
||||
return setValue("Preferences/Downloads/PreAllocation", enabled);
|
||||
}
|
||||
|
||||
bool Preferences::useAdditionDialog() const
|
||||
QVariantHash Preferences::getScanDirs() const
|
||||
{
|
||||
return value("Preferences/Downloads/NewAdditionDialog", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::useAdditionDialog(bool b)
|
||||
{
|
||||
setValue("Preferences/Downloads/NewAdditionDialog", b);
|
||||
}
|
||||
|
||||
bool Preferences::additionDialogFront() const
|
||||
{
|
||||
return value("Preferences/Downloads/NewAdditionDialogFront", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::additionDialogFront(bool b)
|
||||
{
|
||||
setValue("Preferences/Downloads/NewAdditionDialogFront", b);
|
||||
}
|
||||
|
||||
bool Preferences::addTorrentsInPause() const
|
||||
{
|
||||
return value("Preferences/Downloads/StartInPause", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::addTorrentsInPause(bool b)
|
||||
{
|
||||
setValue("Preferences/Downloads/StartInPause", b);
|
||||
}
|
||||
|
||||
QStringList Preferences::getScanDirs() const
|
||||
{
|
||||
QStringList originalList = value("Preferences/Downloads/ScanDirs").toStringList();
|
||||
if (originalList.isEmpty())
|
||||
return originalList;
|
||||
|
||||
QStringList newList;
|
||||
foreach (const QString& s, originalList)
|
||||
newList << Utils::Fs::fromNativePath(s);
|
||||
return newList;
|
||||
return value("Preferences/Downloads/ScanDirsV2").toHash();
|
||||
}
|
||||
|
||||
// This must be called somewhere with data from the model
|
||||
void Preferences::setScanDirs(const QStringList &dirs)
|
||||
void Preferences::setScanDirs(const QVariantHash &dirs)
|
||||
{
|
||||
QStringList newList;
|
||||
if (!dirs.isEmpty())
|
||||
foreach (const QString& s, dirs)
|
||||
newList << Utils::Fs::fromNativePath(s);
|
||||
setValue("Preferences/Downloads/ScanDirs", newList);
|
||||
}
|
||||
|
||||
QList<bool> Preferences::getDownloadInScanDirs() const
|
||||
{
|
||||
return Utils::Misc::boolListfromStringList(value("Preferences/Downloads/DownloadInScanDirs").toStringList());
|
||||
}
|
||||
|
||||
void Preferences::setDownloadInScanDirs(const QList<bool> &list)
|
||||
{
|
||||
setValue("Preferences/Downloads/ScanDirsDownloadPaths", Utils::Misc::toStringList(list));
|
||||
}
|
||||
|
||||
void Preferences::setScanDirsDownloadPaths(const QStringList &downloadpaths)
|
||||
{
|
||||
setValue("Preferences/Downloads/ScanDirsDownloadPaths", downloadpaths);
|
||||
}
|
||||
|
||||
QStringList Preferences::getScanDirsDownloadPaths() const
|
||||
{
|
||||
return value("Preferences/Downloads/DownloadPaths").toStringList();
|
||||
setValue("Preferences/Downloads/ScanDirsV2", dirs);
|
||||
}
|
||||
|
||||
QString Preferences::getScanDirsLastPath() const
|
||||
@@ -650,7 +451,6 @@ void Preferences::setActionOnDblClOnTorrentFn(int act)
|
||||
// Connection options
|
||||
int Preferences::getSessionPort() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
if (useRandomPort())
|
||||
return m_randomPort;
|
||||
return value("Preferences/Connection/PortRangeMin", 8999).toInt();
|
||||
@@ -996,7 +796,7 @@ void Preferences::setTrackersList(const QString &val)
|
||||
|
||||
qreal Preferences::getGlobalMaxRatio() const
|
||||
{
|
||||
return value("Preferences/Bittorrent/MaxRatio", -1).toDouble();
|
||||
return value("Preferences/Bittorrent/MaxRatio", -1).toReal();
|
||||
}
|
||||
|
||||
void Preferences::setGlobalMaxRatio(qreal ratio)
|
||||
@@ -1004,16 +804,6 @@ void Preferences::setGlobalMaxRatio(qreal ratio)
|
||||
setValue("Preferences/Bittorrent/MaxRatio", ratio);
|
||||
}
|
||||
|
||||
MaxRatioAction Preferences::getMaxRatioAction() const
|
||||
{
|
||||
return value("Preferences/Bittorrent/MaxRatioAction", QVariant::fromValue(MaxRatioAction::Pause)).value<MaxRatioAction>();
|
||||
}
|
||||
|
||||
void Preferences::setMaxRatioAction(MaxRatioAction act)
|
||||
{
|
||||
setValue("Preferences/Bittorrent/MaxRatioAction", QVariant::fromValue(act));
|
||||
}
|
||||
|
||||
// IP Filter
|
||||
bool Preferences::isFilteringEnabled() const
|
||||
{
|
||||
@@ -1027,12 +817,12 @@ void Preferences::setFilteringEnabled(bool enabled)
|
||||
|
||||
bool Preferences::isFilteringTrackerEnabled() const
|
||||
{
|
||||
return value("Preferences/IPFilter/FilterTracker", false).toBool();
|
||||
return value("Preferences/IPFilter/FilterTracker", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setFilteringTrackerEnabled(bool enabled)
|
||||
{
|
||||
setValue("Preferences/IPFilter/FilterTracker", enabled);
|
||||
setValue("Preferences/IPFilter/FilterTracker", enabled);
|
||||
}
|
||||
|
||||
QString Preferences::getFilter() const
|
||||
@@ -1070,17 +860,6 @@ void Preferences::setSearchEnabled(bool enabled)
|
||||
setValue("Preferences/Search/SearchEnabled", enabled);
|
||||
}
|
||||
|
||||
// Execution Log
|
||||
bool Preferences::isExecutionLogEnabled() const
|
||||
{
|
||||
return value("Preferences/ExecutionLog/enabled", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setExecutionLogEnabled(bool b)
|
||||
{
|
||||
setValue("Preferences/ExecutionLog/enabled", b);
|
||||
}
|
||||
|
||||
// Queueing system
|
||||
bool Preferences::isQueueingSystemEnabled() const
|
||||
{
|
||||
@@ -1337,12 +1116,12 @@ void Preferences::setAutoRunEnabled(bool enabled)
|
||||
|
||||
QString Preferences::getAutoRunProgram() const
|
||||
{
|
||||
return Utils::Fs::fromNativePath(value("AutoRun/program").toString());
|
||||
return value("AutoRun/program").toString();
|
||||
}
|
||||
|
||||
void Preferences::setAutoRunProgram(const QString &program)
|
||||
{
|
||||
setValue("AutoRun/program", Utils::Fs::fromNativePath(program));
|
||||
setValue("AutoRun/program", program);
|
||||
}
|
||||
|
||||
bool Preferences::shutdownWhenDownloadsComplete() const
|
||||
@@ -1385,6 +1164,16 @@ void Preferences::setShutdownqBTWhenDownloadsComplete(bool shutdown)
|
||||
setValue("Preferences/Downloads/AutoShutDownqBTOnCompletion", shutdown);
|
||||
}
|
||||
|
||||
bool Preferences::dontConfirmAutoExit() const
|
||||
{
|
||||
return value("ShutdownConfirmDlg/DontConfirmAutoExit", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setDontConfirmAutoExit(bool dontConfirmAutoExit)
|
||||
{
|
||||
setValue("ShutdownConfirmDlg/DontConfirmAutoExit", dontConfirmAutoExit);
|
||||
}
|
||||
|
||||
uint Preferences::diskCacheSize() const
|
||||
{
|
||||
uint size = value("Preferences/Downloads/DiskWriteCacheSize", 0).toUInt();
|
||||
@@ -1627,51 +1416,6 @@ void Preferences::useSystemIconTheme(bool enabled)
|
||||
}
|
||||
#endif
|
||||
|
||||
QStringList Preferences::getTorrentLabels() const
|
||||
{
|
||||
return value("TransferListFilters/customLabels").toStringList();
|
||||
}
|
||||
|
||||
void Preferences::setTorrentLabels(const QStringList& labels)
|
||||
{
|
||||
setValue("TransferListFilters/customLabels", labels);
|
||||
}
|
||||
|
||||
void Preferences::addTorrentLabelExternal(const QString &label)
|
||||
{
|
||||
addTorrentLabel(label);
|
||||
QString toEmit = label;
|
||||
emit externalLabelAdded(toEmit);
|
||||
}
|
||||
|
||||
void Preferences::addTorrentLabel(const QString& label)
|
||||
{
|
||||
QStringList labels = value("TransferListFilters/customLabels").toStringList();
|
||||
if (labels.contains(label))
|
||||
return;
|
||||
labels << label;
|
||||
setValue("TransferListFilters/customLabels", labels);
|
||||
}
|
||||
|
||||
void Preferences::removeTorrentLabel(const QString& label)
|
||||
{
|
||||
QStringList labels = value("TransferListFilters/customLabels").toStringList();
|
||||
if (!labels.contains(label))
|
||||
return;
|
||||
labels.removeOne(label);
|
||||
setValue("TransferListFilters/customLabels", labels);
|
||||
}
|
||||
|
||||
QString Preferences::getDefaultLabel() const
|
||||
{
|
||||
return value("Preferences/Downloads/DefaultLabel").toString();
|
||||
}
|
||||
|
||||
void Preferences::setDefaultLabel(const QString &defaultLabel)
|
||||
{
|
||||
setValue("Preferences/Downloads/DefaultLabel", defaultLabel);
|
||||
}
|
||||
|
||||
bool Preferences::recursiveDownloadDisabled() const
|
||||
{
|
||||
return value("Preferences/Advanced/DisableRecursiveDownload", false).toBool();
|
||||
@@ -1910,6 +1654,62 @@ void Preferences::setMagnetLinkAssoc(bool set)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
namespace
|
||||
{
|
||||
CFStringRef torrentExtension = CFSTR("torrent");
|
||||
CFStringRef magnetUrlScheme = CFSTR("magnet");
|
||||
}
|
||||
|
||||
bool Preferences::isTorrentFileAssocSet()
|
||||
{
|
||||
bool isSet = false;
|
||||
CFStringRef torrentId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
|
||||
if (torrentId != NULL) {
|
||||
CFStringRef defaultHandlerId = LSCopyDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer);
|
||||
if (defaultHandlerId != NULL) {
|
||||
CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
|
||||
isSet = CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
|
||||
CFRelease(defaultHandlerId);
|
||||
}
|
||||
CFRelease(torrentId);
|
||||
}
|
||||
return isSet;
|
||||
}
|
||||
|
||||
bool Preferences::isMagnetLinkAssocSet()
|
||||
{
|
||||
bool isSet = false;
|
||||
CFStringRef defaultHandlerId = LSCopyDefaultHandlerForURLScheme(magnetUrlScheme);
|
||||
if (defaultHandlerId != NULL) {
|
||||
CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
|
||||
isSet = CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
|
||||
CFRelease(defaultHandlerId);
|
||||
}
|
||||
return isSet;
|
||||
}
|
||||
|
||||
void Preferences::setTorrentFileAssoc()
|
||||
{
|
||||
if (isTorrentFileAssocSet())
|
||||
return;
|
||||
CFStringRef torrentId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
|
||||
if (torrentId != NULL) {
|
||||
CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
|
||||
LSSetDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer, myBundleId);
|
||||
CFRelease(torrentId);
|
||||
}
|
||||
}
|
||||
|
||||
void Preferences::setMagnetLinkAssoc()
|
||||
{
|
||||
if (isMagnetLinkAssocSet())
|
||||
return;
|
||||
CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
|
||||
LSSetDefaultHandlerForURLScheme(magnetUrlScheme, myBundleId);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Preferences::isTrackerEnabled() const
|
||||
{
|
||||
return value("Preferences/Advanced/trackerEnabled", false).toBool();
|
||||
@@ -1974,63 +1774,6 @@ void Preferences::setTrayIconStyle(TrayIcon::Style style)
|
||||
|
||||
// Stuff that don't appear in the Options GUI but are saved
|
||||
// in the same file.
|
||||
QByteArray Preferences::getAddNewTorrentDialogState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
return value("AddNewTorrentDialog/qt5/treeHeaderState").toByteArray();
|
||||
#else
|
||||
return value("AddNewTorrentDialog/treeHeaderState").toByteArray();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Preferences::setAddNewTorrentDialogState(const QByteArray &state)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
setValue("AddNewTorrentDialog/qt5/treeHeaderState", state);
|
||||
#else
|
||||
setValue("AddNewTorrentDialog/treeHeaderState", state);
|
||||
#endif
|
||||
}
|
||||
|
||||
int Preferences::getAddNewTorrentDialogPos() const
|
||||
{
|
||||
return value("AddNewTorrentDialog/y", -1).toInt();
|
||||
}
|
||||
|
||||
void Preferences::setAddNewTorrentDialogPos(const int &pos)
|
||||
{
|
||||
setValue("AddNewTorrentDialog/y", pos);
|
||||
}
|
||||
|
||||
int Preferences::getAddNewTorrentDialogWidth() const
|
||||
{
|
||||
return value("AddNewTorrentDialog/width", -1).toInt();
|
||||
}
|
||||
|
||||
void Preferences::setAddNewTorrentDialogWidth(const int &width)
|
||||
{
|
||||
setValue("AddNewTorrentDialog/width", width);
|
||||
}
|
||||
|
||||
bool Preferences::getAddNewTorrentDialogExpanded() const
|
||||
{
|
||||
return value("AddNewTorrentDialog/expanded", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setAddNewTorrentDialogExpanded(const bool expanded)
|
||||
{
|
||||
setValue("AddNewTorrentDialog/expanded", expanded);
|
||||
}
|
||||
|
||||
QStringList Preferences::getAddNewTorrentDialogPathHistory() const
|
||||
{
|
||||
return value("TorrentAdditionDlg/save_path_history").toStringList();
|
||||
}
|
||||
|
||||
void Preferences::setAddNewTorrentDialogPathHistory(const QStringList &history)
|
||||
{
|
||||
setValue("TorrentAdditionDlg/save_path_history", history);
|
||||
}
|
||||
|
||||
QDateTime Preferences::getDNSLastUpd() const
|
||||
{
|
||||
@@ -2074,7 +1817,7 @@ void Preferences::setMainGeometry(const QByteArray &geometry)
|
||||
|
||||
QByteArray Preferences::getMainVSplitterState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("MainWindow/qt5/vsplitterState").toByteArray();
|
||||
#else
|
||||
return value("MainWindow/vsplitterState").toByteArray();
|
||||
@@ -2083,7 +1826,7 @@ QByteArray Preferences::getMainVSplitterState() const
|
||||
|
||||
void Preferences::setMainVSplitterState(const QByteArray &state)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("MainWindow/qt5/vsplitterState", state);
|
||||
#else
|
||||
setValue("MainWindow/vsplitterState", state);
|
||||
@@ -2134,7 +1877,7 @@ void Preferences::setPrefHSplitterSizes(const QStringList &sizes)
|
||||
|
||||
QByteArray Preferences::getPeerListState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("TorrentProperties/Peers/qt5/PeerListState").toByteArray();
|
||||
#else
|
||||
return value("TorrentProperties/Peers/PeerListState").toByteArray();
|
||||
@@ -2143,7 +1886,7 @@ QByteArray Preferences::getPeerListState() const
|
||||
|
||||
void Preferences::setPeerListState(const QByteArray &state)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("TorrentProperties/Peers/qt5/PeerListState", state);
|
||||
#else
|
||||
setValue("TorrentProperties/Peers/PeerListState", state);
|
||||
@@ -2162,7 +1905,7 @@ void Preferences::setPropSplitterSizes(const QString &sizes)
|
||||
|
||||
QByteArray Preferences::getPropFileListState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("TorrentProperties/qt5/FilesListState").toByteArray();
|
||||
#else
|
||||
return value("TorrentProperties/FilesListState").toByteArray();
|
||||
@@ -2171,7 +1914,7 @@ QByteArray Preferences::getPropFileListState() const
|
||||
|
||||
void Preferences::setPropFileListState(const QByteArray &state)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("TorrentProperties/qt5/FilesListState", state);
|
||||
#else
|
||||
setValue("TorrentProperties/FilesListState", state);
|
||||
@@ -2200,7 +1943,7 @@ void Preferences::setPropVisible(const bool visible)
|
||||
|
||||
QByteArray Preferences::getPropTrackerListState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("TorrentProperties/Trackers/qt5/TrackerListState").toByteArray();
|
||||
#else
|
||||
return value("TorrentProperties/Trackers/TrackerListState").toByteArray();
|
||||
@@ -2209,7 +1952,7 @@ QByteArray Preferences::getPropTrackerListState() const
|
||||
|
||||
void Preferences::setPropTrackerListState(const QByteArray &state)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("TorrentProperties/Trackers/qt5/TrackerListState", state);
|
||||
#else
|
||||
setValue("TorrentProperties/Trackers/TrackerListState", state);
|
||||
@@ -2228,7 +1971,7 @@ void Preferences::setRssGeometry(const QByteArray &geometry)
|
||||
|
||||
QByteArray Preferences::getRssHSplitterSizes() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("RssFeedDownloader/qt5/hsplitterSizes").toByteArray();
|
||||
#else
|
||||
return value("RssFeedDownloader/hsplitterSizes").toByteArray();
|
||||
@@ -2237,7 +1980,7 @@ QByteArray Preferences::getRssHSplitterSizes() const
|
||||
|
||||
void Preferences::setRssHSplitterSizes(const QByteArray &sizes)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("RssFeedDownloader/qt5/hsplitterSizes", sizes);
|
||||
#else
|
||||
setValue("RssFeedDownloader/hsplitterSizes", sizes);
|
||||
@@ -2256,7 +1999,7 @@ void Preferences::setRssOpenFolders(const QStringList &folders)
|
||||
|
||||
QByteArray Preferences::getRssHSplitterState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("Rss/qt5/splitter_h").toByteArray();
|
||||
#else
|
||||
return value("Rss/splitter_h").toByteArray();
|
||||
@@ -2265,7 +2008,7 @@ QByteArray Preferences::getRssHSplitterState() const
|
||||
|
||||
void Preferences::setRssHSplitterState(const QByteArray &state)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("Rss/qt5/splitter_h", state);
|
||||
#else
|
||||
setValue("Rss/splitter_h", state);
|
||||
@@ -2274,7 +2017,7 @@ void Preferences::setRssHSplitterState(const QByteArray &state)
|
||||
|
||||
QByteArray Preferences::getRssVSplitterState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("Rss/qt5/splitter_v").toByteArray();
|
||||
#else
|
||||
return value("Rss/splitter_v").toByteArray();
|
||||
@@ -2283,7 +2026,7 @@ QByteArray Preferences::getRssVSplitterState() const
|
||||
|
||||
void Preferences::setRssVSplitterState(const QByteArray &state)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("Rss/qt5/splitter_v", state);
|
||||
#else
|
||||
setValue("Rss/splitter_v", state);
|
||||
@@ -2390,14 +2133,14 @@ void Preferences::setStatusFilterState(const bool checked)
|
||||
setValue("TransferListFilters/statusFilterState", checked);
|
||||
}
|
||||
|
||||
bool Preferences::getLabelFilterState() const
|
||||
bool Preferences::getCategoryFilterState() const
|
||||
{
|
||||
return value("TransferListFilters/labelFilterState", true).toBool();
|
||||
return value("TransferListFilters/CategoryFilterState", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setLabelFilterState(const bool checked)
|
||||
void Preferences::setCategoryFilterState(const bool checked)
|
||||
{
|
||||
setValue("TransferListFilters/labelFilterState", checked);
|
||||
setValue("TransferListFilters/CategoryFilterState", checked);
|
||||
}
|
||||
|
||||
bool Preferences::getTrackerFilterState() const
|
||||
@@ -2422,7 +2165,7 @@ void Preferences::setTransSelFilter(const int &index)
|
||||
|
||||
QByteArray Preferences::getTransHeaderState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("TransferList/qt5/HeaderState").toByteArray();
|
||||
#else
|
||||
return value("TransferList/HeaderState").toByteArray();
|
||||
@@ -2431,29 +2174,13 @@ QByteArray Preferences::getTransHeaderState() const
|
||||
|
||||
void Preferences::setTransHeaderState(const QByteArray &state)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("TransferList/qt5/HeaderState", state);
|
||||
#else
|
||||
setValue("TransferList/HeaderState", state);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Temp code.
|
||||
// See TorrentStatistics::loadStats() for details.
|
||||
QVariantHash Preferences::getStats() const
|
||||
{
|
||||
return value("Stats/AllStats").toHash();
|
||||
}
|
||||
|
||||
void Preferences::removeStats()
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
dirty = true;
|
||||
if (!timer.isActive())
|
||||
timer.start();
|
||||
m_data.remove("Stats/AllStats");
|
||||
}
|
||||
|
||||
//From old RssSettings class
|
||||
bool Preferences::isRSSEnabled() const
|
||||
{
|
||||
@@ -2525,45 +2252,32 @@ void Preferences::setToolbarTextPosition(const int position)
|
||||
setValue("Toolbar/textPosition", position);
|
||||
}
|
||||
|
||||
QList<QByteArray> Preferences::getHostNameCookies(const QString &host_name) const
|
||||
{
|
||||
QMap<QString, QVariant> hosts_table = value("Rss/hosts_cookies").toMap();
|
||||
if (!hosts_table.contains(host_name)) return QList<QByteArray>();
|
||||
QByteArray raw_cookies = hosts_table.value(host_name).toByteArray();
|
||||
return raw_cookies.split(':');
|
||||
}
|
||||
|
||||
QList<QNetworkCookie> Preferences::getHostNameQNetworkCookies(const QString& host_name) const
|
||||
QList<QNetworkCookie> Preferences::getNetworkCookies() const
|
||||
{
|
||||
QList<QNetworkCookie> cookies;
|
||||
const QList<QByteArray> raw_cookies = getHostNameCookies(host_name);
|
||||
foreach (const QByteArray& raw_cookie, raw_cookies) {
|
||||
QList<QByteArray> cookie_parts = raw_cookie.split('=');
|
||||
if (cookie_parts.size() == 2) {
|
||||
qDebug("Loading cookie: %s = %s", cookie_parts.first().constData(), cookie_parts.last().constData());
|
||||
cookies << QNetworkCookie(cookie_parts.first(), cookie_parts.last());
|
||||
}
|
||||
}
|
||||
QStringList rawCookies = value("Network/Cookies").toStringList();
|
||||
foreach (const QString &rawCookie, rawCookies)
|
||||
cookies << QNetworkCookie::parseCookies(rawCookie.toUtf8());
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
void Preferences::setHostNameCookies(const QString &host_name, const QList<QByteArray> &cookies)
|
||||
void Preferences::setNetworkCookies(const QList<QNetworkCookie> &cookies)
|
||||
{
|
||||
QMap<QString, QVariant> hosts_table = value("Rss/hosts_cookies").toMap();
|
||||
QByteArray raw_cookies = "";
|
||||
foreach (const QByteArray& cookie, cookies)
|
||||
raw_cookies += cookie + ":";
|
||||
if (raw_cookies.endsWith(":"))
|
||||
raw_cookies.chop(1);
|
||||
hosts_table.insert(host_name, raw_cookies);
|
||||
setValue("Rss/hosts_cookies", hosts_table);
|
||||
QStringList rawCookies;
|
||||
foreach (const QNetworkCookie &cookie, cookies)
|
||||
rawCookies << cookie.toRawForm();
|
||||
|
||||
setValue("Network/Cookies", rawCookies);
|
||||
}
|
||||
|
||||
int Preferences::getSpeedWidgetPeriod() const {
|
||||
int Preferences::getSpeedWidgetPeriod() const
|
||||
{
|
||||
return value("SpeedWidget/period", 1).toInt();
|
||||
}
|
||||
|
||||
void Preferences::setSpeedWidgetPeriod(const int period) {
|
||||
void Preferences::setSpeedWidgetPeriod(const int period)
|
||||
{
|
||||
setValue("SpeedWidget/period", period);
|
||||
}
|
||||
|
||||
@@ -2578,8 +2292,43 @@ void Preferences::setSpeedWidgetGraphEnable(int id, const bool enable)
|
||||
setValue("SpeedWidget/graph_enable_" + QString::number(id), enable);
|
||||
}
|
||||
|
||||
void Preferences::upgrade()
|
||||
{
|
||||
// Move RSS cookies to global storage
|
||||
QList<QNetworkCookie> cookies = getNetworkCookies();
|
||||
QVariantMap hostsTable = value("Rss/hosts_cookies").toMap();
|
||||
foreach (const QString &key, hostsTable.keys()) {
|
||||
QVariant value = hostsTable[key];
|
||||
QList<QByteArray> rawCookies = value.toByteArray().split(':');
|
||||
foreach (const QByteArray &rawCookie, rawCookies) {
|
||||
foreach (QNetworkCookie cookie, QNetworkCookie::parseCookies(rawCookie)) {
|
||||
cookie.setDomain(key);
|
||||
cookie.setPath("/");
|
||||
cookie.setExpirationDate(QDateTime::currentDateTime().addYears(10));
|
||||
cookies << cookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNetworkCookies(cookies);
|
||||
|
||||
QStringList labels = value("TransferListFilters/customLabels").toStringList();
|
||||
if (!labels.isEmpty()) {
|
||||
QVariantMap categories = value("BitTorrent/Session/Categories").toMap();
|
||||
foreach (const QString &label, labels) {
|
||||
if (!categories.contains(label))
|
||||
categories[label] = "";
|
||||
}
|
||||
setValue("BitTorrent/Session/Categories", categories);
|
||||
SettingsStorage::instance()->removeValue("TransferListFilters/customLabels");
|
||||
}
|
||||
|
||||
SettingsStorage::instance()->removeValue("Rss/hosts_cookies");
|
||||
SettingsStorage::instance()->removeValue("Preferences/Downloads/AppendLabel");
|
||||
}
|
||||
|
||||
void Preferences::apply()
|
||||
{
|
||||
if (save())
|
||||
if (SettingsStorage::instance()->save())
|
||||
emit changed();
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
#include <QNetworkCookie>
|
||||
#include <QVariant>
|
||||
|
||||
#include "core/types.h"
|
||||
#include "types.h"
|
||||
|
||||
enum scheduler_days
|
||||
{
|
||||
@@ -89,30 +89,23 @@ namespace DNS
|
||||
};
|
||||
}
|
||||
|
||||
class SettingsStorage;
|
||||
|
||||
class Preferences: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Preferences)
|
||||
|
||||
private:
|
||||
Preferences();
|
||||
~Preferences();
|
||||
|
||||
static Preferences* m_instance;
|
||||
QHash<QString, QVariant> m_data;
|
||||
int m_randomPort;
|
||||
bool dirty;
|
||||
QTimer timer;
|
||||
mutable QReadWriteLock lock;
|
||||
const QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
void setValue(const QString &key, const QVariant &value);
|
||||
|
||||
private slots:
|
||||
bool save();
|
||||
static Preferences* m_instance;
|
||||
int m_randomPort;
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
void externalLabelAdded(QString&);
|
||||
|
||||
public:
|
||||
static void initInstance();
|
||||
@@ -132,6 +125,10 @@ public:
|
||||
void showSpeedInTitleBar(bool show);
|
||||
bool useAlternatingRowColors() const;
|
||||
void setAlternatingRowColors(bool b);
|
||||
bool getHideZeroValues() const;
|
||||
void setHideZeroValues(bool b);
|
||||
int getHideZeroComboValues() const;
|
||||
void setHideZeroComboValues(int n);
|
||||
bool useRandomPort() const;
|
||||
void setRandomPort(bool b);
|
||||
bool systrayIntegration() const;
|
||||
@@ -154,35 +151,15 @@ public:
|
||||
#endif
|
||||
|
||||
// Downloads
|
||||
QString getSavePath() const;
|
||||
void setSavePath(const QString &save_path);
|
||||
bool isTempPathEnabled() const;
|
||||
void setTempPathEnabled(bool enabled);
|
||||
QString getTempPath() const;
|
||||
void setTempPath(const QString &path);
|
||||
QString getDefaultLabel() const;
|
||||
void setDefaultLabel(const QString &defaultLabel);
|
||||
bool useIncompleteFilesExtension() const;
|
||||
void useIncompleteFilesExtension(bool enabled);
|
||||
bool appendTorrentLabel() const;
|
||||
void setAppendTorrentLabel(bool b);
|
||||
QString lastLocationPath() const;
|
||||
void setLastLocationPath(const QString &path);
|
||||
bool preAllocateAllFiles() const;
|
||||
void preAllocateAllFiles(bool enabled);
|
||||
bool useAdditionDialog() const;
|
||||
void useAdditionDialog(bool b);
|
||||
bool additionDialogFront() const;
|
||||
void additionDialogFront(bool b);
|
||||
bool addTorrentsInPause() const;
|
||||
void addTorrentsInPause(bool b);
|
||||
QStringList getScanDirs() const;
|
||||
void setScanDirs(const QStringList &dirs);
|
||||
QList<bool> getDownloadInScanDirs() const;
|
||||
void setDownloadInScanDirs(const QList<bool> &list);
|
||||
QVariantHash getScanDirs() const;
|
||||
void setScanDirs(const QVariantHash &dirs);
|
||||
QString getScanDirsLastPath() const;
|
||||
void setScanDirsDownloadPaths(const QStringList &downloadpaths);
|
||||
QStringList getScanDirsDownloadPaths() const;
|
||||
void setScanDirsLastPath(const QString &path);
|
||||
bool isTorrentExportEnabled() const;
|
||||
QString getTorrentExportDir() const;
|
||||
@@ -281,8 +258,6 @@ public:
|
||||
void setTrackersList(const QString &val);
|
||||
qreal getGlobalMaxRatio() const;
|
||||
void setGlobalMaxRatio(qreal ratio);
|
||||
MaxRatioAction getMaxRatioAction() const;
|
||||
void setMaxRatioAction(MaxRatioAction act);
|
||||
|
||||
// IP Filter
|
||||
bool isFilteringEnabled() const;
|
||||
@@ -298,10 +273,6 @@ public:
|
||||
bool isSearchEnabled() const;
|
||||
void setSearchEnabled(bool enabled);
|
||||
|
||||
// Execution Log
|
||||
bool isExecutionLogEnabled() const;
|
||||
void setExecutionLogEnabled(bool b);
|
||||
|
||||
// Queueing system
|
||||
bool isQueueingSystemEnabled() const;
|
||||
void setQueueingSystemEnabled(bool enabled);
|
||||
@@ -360,6 +331,8 @@ public:
|
||||
void setHibernateWhenDownloadsComplete(bool hibernate);
|
||||
bool shutdownqBTWhenDownloadsComplete() const;
|
||||
void setShutdownqBTWhenDownloadsComplete(bool shutdown);
|
||||
bool dontConfirmAutoExit() const;
|
||||
void setDontConfirmAutoExit(bool dontConfirmAutoExit);
|
||||
uint diskCacheSize() const;
|
||||
void setDiskCacheSize(uint size);
|
||||
uint diskCacheTTL() const;
|
||||
@@ -406,11 +379,6 @@ public:
|
||||
bool useSystemIconTheme() const;
|
||||
void useSystemIconTheme(bool enabled);
|
||||
#endif
|
||||
QStringList getTorrentLabels() const;
|
||||
void setTorrentLabels(const QStringList& labels);
|
||||
void addTorrentLabelExternal(const QString &label);
|
||||
void addTorrentLabel(const QString& label);
|
||||
void removeTorrentLabel(const QString& label);
|
||||
bool recursiveDownloadDisabled() const;
|
||||
void disableRecursiveDownload(bool disable = true);
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -421,6 +389,12 @@ public:
|
||||
static bool isMagnetLinkAssocSet();
|
||||
static void setTorrentFileAssoc(bool set);
|
||||
static void setMagnetLinkAssoc(bool set);
|
||||
#endif
|
||||
#ifdef Q_OS_MAC
|
||||
static bool isTorrentFileAssocSet();
|
||||
static bool isMagnetLinkAssocSet();
|
||||
static void setTorrentFileAssoc();
|
||||
static void setMagnetLinkAssoc();
|
||||
#endif
|
||||
bool isTrackerEnabled() const;
|
||||
void setTrackerEnabled(bool enabled);
|
||||
@@ -437,19 +411,8 @@ public:
|
||||
TrayIcon::Style trayIconStyle() const;
|
||||
void setTrayIconStyle(TrayIcon::Style style);
|
||||
|
||||
|
||||
// Stuff that don't appear in the Options GUI but are saved
|
||||
// in the same file.
|
||||
QByteArray getAddNewTorrentDialogState() const;
|
||||
void setAddNewTorrentDialogState(const QByteArray &state);
|
||||
int getAddNewTorrentDialogPos() const;
|
||||
void setAddNewTorrentDialogPos(const int &pos);
|
||||
int getAddNewTorrentDialogWidth() const;
|
||||
void setAddNewTorrentDialogWidth(const int &width);
|
||||
bool getAddNewTorrentDialogExpanded() const;
|
||||
void setAddNewTorrentDialogExpanded(const bool expanded);
|
||||
QStringList getAddNewTorrentDialogPathHistory() const;
|
||||
void setAddNewTorrentDialogPathHistory(const QStringList &history);
|
||||
QDateTime getDNSLastUpd() const;
|
||||
void setDNSLastUpd(const QDateTime &date);
|
||||
QString getDNSLastIP() const;
|
||||
@@ -511,7 +474,7 @@ public:
|
||||
QByteArray getTorImportGeometry() const;
|
||||
void setTorImportGeometry(const QByteArray &geometry);
|
||||
bool getStatusFilterState() const;
|
||||
bool getLabelFilterState() const;
|
||||
bool getCategoryFilterState() const;
|
||||
bool getTrackerFilterState() const;
|
||||
int getTransSelFilter() const;
|
||||
void setTransSelFilter(const int &index);
|
||||
@@ -520,11 +483,6 @@ public:
|
||||
int getToolbarTextPosition() const;
|
||||
void setToolbarTextPosition(const int position);
|
||||
|
||||
// Temp code.
|
||||
// See TorrentStatistics::loadStats() for details.
|
||||
QVariantHash getStats() const;
|
||||
void removeStats();
|
||||
|
||||
//From old RssSettings class
|
||||
bool isRSSEnabled() const;
|
||||
void setRSSEnabled(const bool enabled);
|
||||
@@ -538,9 +496,10 @@ public:
|
||||
void setRssFeedsUrls(const QStringList &rssFeeds);
|
||||
QStringList getRssFeedsAliases() const;
|
||||
void setRssFeedsAliases(const QStringList &rssAliases);
|
||||
QList<QByteArray> getHostNameCookies(const QString &host_name) const;
|
||||
QList<QNetworkCookie> getHostNameQNetworkCookies(const QString& host_name) const;
|
||||
void setHostNameCookies(const QString &host_name, const QList<QByteArray> &cookies);
|
||||
|
||||
// Network
|
||||
QList<QNetworkCookie> getNetworkCookies() const;
|
||||
void setNetworkCookies(const QList<QNetworkCookie> &cookies);
|
||||
|
||||
// SpeedWidget
|
||||
int getSpeedWidgetPeriod() const;
|
||||
@@ -548,9 +507,11 @@ public:
|
||||
bool getSpeedWidgetGraphEnable(int id) const;
|
||||
void setSpeedWidgetGraphEnable(int id, const bool enable);
|
||||
|
||||
void upgrade();
|
||||
|
||||
public slots:
|
||||
void setStatusFilterState(bool checked);
|
||||
void setLabelFilterState(bool checked);
|
||||
void setCategoryFilterState(bool checked);
|
||||
void setTrackerFilterState(bool checked);
|
||||
|
||||
void apply();
|
||||
467
src/base/rss/private/rssparser.cpp
Normal file
467
src/base/rss/private/rssparser.cpp
Normal file
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 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
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "rssparser.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const char shortDay[][4] = {
|
||||
"Mon", "Tue", "Wed",
|
||||
"Thu", "Fri", "Sat",
|
||||
"Sun"
|
||||
};
|
||||
|
||||
const char longDay[][10] = {
|
||||
"Monday", "Tuesday", "Wednesday",
|
||||
"Thursday", "Friday", "Saturday",
|
||||
"Sunday"
|
||||
};
|
||||
|
||||
const char shortMonth[][4] = {
|
||||
"Jan", "Feb", "Mar", "Apr",
|
||||
"May", "Jun", "Jul", "Aug",
|
||||
"Sep", "Oct", "Nov", "Dec"
|
||||
};
|
||||
|
||||
// Ported to Qt from KDElibs4
|
||||
QDateTime parseDate(const QString &string)
|
||||
{
|
||||
const QString str = string.trimmed();
|
||||
if (str.isEmpty())
|
||||
return QDateTime::currentDateTime();
|
||||
|
||||
int nyear = 6; // indexes within string to values
|
||||
int nmonth = 4;
|
||||
int nday = 2;
|
||||
int nwday = 1;
|
||||
int nhour = 7;
|
||||
int nmin = 8;
|
||||
int nsec = 9;
|
||||
// Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
|
||||
QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$");
|
||||
QStringList parts;
|
||||
if (!str.indexOf(rx)) {
|
||||
// Check that if date has '-' separators, both separators are '-'.
|
||||
parts = rx.capturedTexts();
|
||||
bool h1 = (parts[3] == QLatin1String("-"));
|
||||
bool h2 = (parts[5] == QLatin1String("-"));
|
||||
if (h1 != h2)
|
||||
return QDateTime::currentDateTime();
|
||||
}
|
||||
else {
|
||||
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
|
||||
rx = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$");
|
||||
if (str.indexOf(rx))
|
||||
return QDateTime::currentDateTime();
|
||||
nyear = 7;
|
||||
nmonth = 2;
|
||||
nday = 3;
|
||||
nwday = 1;
|
||||
nhour = 4;
|
||||
nmin = 5;
|
||||
nsec = 6;
|
||||
parts = rx.capturedTexts();
|
||||
}
|
||||
|
||||
bool ok[4];
|
||||
const int day = parts[nday].toInt(&ok[0]);
|
||||
int year = parts[nyear].toInt(&ok[1]);
|
||||
const int hour = parts[nhour].toInt(&ok[2]);
|
||||
const int minute = parts[nmin].toInt(&ok[3]);
|
||||
if (!ok[0] || !ok[1] || !ok[2] || !ok[3])
|
||||
return QDateTime::currentDateTime();
|
||||
|
||||
int second = 0;
|
||||
if (!parts[nsec].isEmpty()) {
|
||||
second = parts[nsec].toInt(&ok[0]);
|
||||
if (!ok[0])
|
||||
return QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
bool leapSecond = (second == 60);
|
||||
if (leapSecond)
|
||||
second = 59; // apparently a leap second - validate below, once time zone is known
|
||||
int month = 0;
|
||||
for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month);
|
||||
int dayOfWeek = -1;
|
||||
if (!parts[nwday].isEmpty()) {
|
||||
// Look up the weekday name
|
||||
while (++dayOfWeek < 7 && (shortDay[dayOfWeek] != parts[nwday]));
|
||||
if (dayOfWeek >= 7)
|
||||
for (dayOfWeek = 0; dayOfWeek < 7 && (longDay[dayOfWeek] != parts[nwday]); ++dayOfWeek);
|
||||
}
|
||||
|
||||
// if (month >= 12 || dayOfWeek >= 7
|
||||
// || (dayOfWeek < 0 && format == RFCDateDay))
|
||||
// return QDateTime;
|
||||
int i = parts[nyear].size();
|
||||
if (i < 4) {
|
||||
// It's an obsolete year specification with less than 4 digits
|
||||
year += (i == 2 && year < 50) ? 2000 : 1900;
|
||||
}
|
||||
|
||||
// Parse the UTC offset part
|
||||
int offset = 0; // set default to '-0000'
|
||||
bool negOffset = false;
|
||||
if (parts.count() > 10) {
|
||||
rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$");
|
||||
if (!parts[10].indexOf(rx)) {
|
||||
// It's a UTC offset ±hhmm
|
||||
parts = rx.capturedTexts();
|
||||
offset = parts[2].toInt(&ok[0]) * 3600;
|
||||
int offsetMin = parts[3].toInt(&ok[1]);
|
||||
if (!ok[0] || !ok[1] || offsetMin > 59)
|
||||
return QDateTime();
|
||||
offset += offsetMin * 60;
|
||||
negOffset = (parts[1] == QLatin1String("-"));
|
||||
if (negOffset)
|
||||
offset = -offset;
|
||||
}
|
||||
else {
|
||||
// Check for an obsolete time zone name
|
||||
QByteArray zone = parts[10].toLatin1();
|
||||
if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') {
|
||||
negOffset = true; // military zone: RFC 2822 treats as '-0000'
|
||||
}
|
||||
else if (zone != "UT" && zone != "GMT") { // treated as '+0000'
|
||||
offset = (zone == "EDT")
|
||||
? -4 * 3600
|
||||
: ((zone == "EST") || (zone == "CDT"))
|
||||
? -5 * 3600
|
||||
: ((zone == "CST") || (zone == "MDT"))
|
||||
? -6 * 3600
|
||||
: (zone == "MST" || zone == "PDT")
|
||||
? -7 * 3600
|
||||
: (zone == "PST")
|
||||
? -8 * 3600
|
||||
: 0;
|
||||
if (!offset) {
|
||||
// Check for any other alphabetic time zone
|
||||
bool nonalpha = false;
|
||||
for (int i = 0, end = zone.size(); (i < end) && !nonalpha; ++i)
|
||||
nonalpha = !isalpha(zone[i]);
|
||||
if (nonalpha)
|
||||
return QDateTime();
|
||||
// TODO: Attempt to recognize the time zone abbreviation?
|
||||
negOffset = true; // unknown time zone: RFC 2822 treats as '-0000'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QDate qdate(year, month + 1, day); // convert date, and check for out-of-range
|
||||
if (!qdate.isValid())
|
||||
return QDateTime::currentDateTime();
|
||||
|
||||
QTime qTime(hour, minute, second);
|
||||
QDateTime result(qdate, qTime, Qt::UTC);
|
||||
if (offset)
|
||||
result = result.addSecs(-offset);
|
||||
if (!result.isValid())
|
||||
return QDateTime::currentDateTime(); // invalid date/time
|
||||
|
||||
if (leapSecond) {
|
||||
// Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
|
||||
// Convert the time to UTC and check that it is 00:00:00.
|
||||
if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours)
|
||||
return QDateTime::currentDateTime(); // the time isn't the last second of the day
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Rss::Private;
|
||||
|
||||
// read and create items from a rss document
|
||||
void Parser::parse(const QByteArray &feedData)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
|
||||
QXmlStreamReader xml(feedData);
|
||||
bool foundChannel = false;
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == "rss") {
|
||||
// Find channels
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == "channel") {
|
||||
parseRSSChannel(xml);
|
||||
foundChannel = true;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
qDebug() << "Skip rss item: " << xml.name();
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (xml.name() == "feed") { // Atom feed
|
||||
parseAtomChannel(xml);
|
||||
foundChannel = true;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
qDebug() << "Skip root item: " << xml.name();
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (xml.hasError())
|
||||
emit finished(xml.errorString());
|
||||
else if (!foundChannel)
|
||||
emit finished(tr("Invalid RSS feed."));
|
||||
else
|
||||
emit finished(QString());
|
||||
}
|
||||
|
||||
void Parser::parseRssArticle(QXmlStreamReader &xml)
|
||||
{
|
||||
QVariantHash article;
|
||||
|
||||
while(!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
|
||||
if(xml.isEndElement() && xml.name() == "item")
|
||||
break;
|
||||
|
||||
if (xml.isStartElement()) {
|
||||
if (xml.name() == "title") {
|
||||
article["title"] = xml.readElementText().trimmed();
|
||||
}
|
||||
else if (xml.name() == "enclosure") {
|
||||
if (xml.attributes().value("type") == "application/x-bittorrent")
|
||||
article["torrent_url"] = xml.attributes().value("url").toString();
|
||||
}
|
||||
else if (xml.name() == "link") {
|
||||
QString link = xml.readElementText().trimmed();
|
||||
if (link.startsWith("magnet:", Qt::CaseInsensitive))
|
||||
article["torrent_url"] = link; // magnet link instead of a news URL
|
||||
else
|
||||
article["news_link"] = link;
|
||||
}
|
||||
else if (xml.name() == "description") {
|
||||
article["description"] = xml.readElementText().trimmed();
|
||||
}
|
||||
else if (xml.name() == "pubDate") {
|
||||
article["date"] = parseDate(xml.readElementText().trimmed());
|
||||
}
|
||||
else if (xml.name() == "author") {
|
||||
article["author"] = xml.readElementText().trimmed();
|
||||
}
|
||||
else if (xml.name() == "guid") {
|
||||
article["id"] = xml.readElementText().trimmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!article.contains("torrent_url") && article.contains("news_link"))
|
||||
article["torrent_url"] = article["news_link"];
|
||||
|
||||
if (!article.contains("id")) {
|
||||
// Item does not have a guid, fall back to some other identifier
|
||||
const QString link = article.value("news_link").toString();
|
||||
if (!link.isEmpty()) {
|
||||
article["id"] = link;
|
||||
}
|
||||
else {
|
||||
const QString title = article.value("title").toString();
|
||||
if (!title.isEmpty()) {
|
||||
article["id"] = title;
|
||||
}
|
||||
else {
|
||||
qWarning() << "Item has no guid, link or title, ignoring it...";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit newArticle(article);
|
||||
}
|
||||
|
||||
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") {
|
||||
QString title = xml.readElementText();
|
||||
emit feedTitle(title);
|
||||
}
|
||||
else if (xml.name() == "lastBuildDate") {
|
||||
QString lastBuildDate = xml.readElementText();
|
||||
if (!lastBuildDate.isEmpty()) {
|
||||
if (m_lastBuildDate == lastBuildDate) {
|
||||
qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
|
||||
return;
|
||||
}
|
||||
m_lastBuildDate = lastBuildDate;
|
||||
}
|
||||
}
|
||||
else if (xml.name() == "item") {
|
||||
parseRssArticle(xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::parseAtomArticle(QXmlStreamReader &xml)
|
||||
{
|
||||
QVariantHash article;
|
||||
bool doubleContent = false;
|
||||
|
||||
while(!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
|
||||
if(xml.isEndElement() && (xml.name() == "entry"))
|
||||
break;
|
||||
|
||||
if (xml.isStartElement()) {
|
||||
if (xml.name() == "title") {
|
||||
article["title"] = xml.readElementText().trimmed();
|
||||
}
|
||||
else if (xml.name() == "link") {
|
||||
QString link = ( xml.attributes().isEmpty() ?
|
||||
xml.readElementText().trimmed() :
|
||||
xml.attributes().value("href").toString() );
|
||||
|
||||
if (link.startsWith("magnet:", Qt::CaseInsensitive))
|
||||
article["torrent_url"] = link; // magnet link instead of a news URL
|
||||
else
|
||||
// Atom feeds can have relative links, work around this and
|
||||
// take the stress of figuring article full URI from UI
|
||||
// Assemble full URI
|
||||
article["news_link"] = ( m_baseUrl.isEmpty() ? link : m_baseUrl + link );
|
||||
|
||||
}
|
||||
else if ((xml.name() == "summary") || (xml.name() == "content")){
|
||||
if (doubleContent) { // Duplicate content -> ignore
|
||||
xml.readNext();
|
||||
|
||||
while ((xml.name() != "summary") && (xml.name() != "content"))
|
||||
xml.readNext();
|
||||
|
||||
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["description"] = feedText.trimmed();
|
||||
|
||||
doubleContent = true;
|
||||
}
|
||||
else if (xml.name() == "updated") {
|
||||
// ATOM uses standard compliant date, don't do fancy stuff
|
||||
QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
|
||||
article["date"] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime());
|
||||
}
|
||||
else if (xml.name() == "author") {
|
||||
xml.readNext();
|
||||
while(xml.name() != "author") {
|
||||
if(xml.name() == "name")
|
||||
article["author"] = xml.readElementText().trimmed();
|
||||
xml.readNext();
|
||||
}
|
||||
}
|
||||
else if (xml.name() == "id") {
|
||||
article["id"] = xml.readElementText().trimmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!article.contains("torrent_url") && article.contains("news_link"))
|
||||
article["torrent_url"] = article["news_link"];
|
||||
|
||||
if (!article.contains("id")) {
|
||||
// Item does not have a guid, fall back to some other identifier
|
||||
const QString link = article.value("news_link").toString();
|
||||
if (!link.isEmpty()) {
|
||||
article["id"] = link;
|
||||
}
|
||||
else {
|
||||
const QString title = article.value("title").toString();
|
||||
if (!title.isEmpty()) {
|
||||
article["id"] = title;
|
||||
}
|
||||
else {
|
||||
qWarning() << "Item has no guid, link or title, ignoring it...";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit newArticle(article);
|
||||
}
|
||||
|
||||
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") {
|
||||
QString title = xml.readElementText();
|
||||
emit feedTitle(title);
|
||||
}
|
||||
else if (xml.name() == "updated") {
|
||||
QString lastBuildDate = xml.readElementText();
|
||||
if (!lastBuildDate.isEmpty()) {
|
||||
if (m_lastBuildDate == lastBuildDate) {
|
||||
qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
|
||||
return;
|
||||
}
|
||||
m_lastBuildDate = lastBuildDate;
|
||||
}
|
||||
}
|
||||
else if (xml.name() == "entry") {
|
||||
parseAtomArticle(xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/base/rss/private/rssparser.h
Normal file
69
src/base/rss/private/rssparser.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 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
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSPARSER_H
|
||||
#define RSSPARSER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVariantHash>
|
||||
|
||||
class QXmlStreamReader;
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
namespace Private
|
||||
{
|
||||
class Parser: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
void parse(const QByteArray &feedData);
|
||||
|
||||
signals:
|
||||
void newArticle(const QVariantHash &rssArticle);
|
||||
void feedTitle(const QString &title);
|
||||
void finished(const QString &error);
|
||||
|
||||
private:
|
||||
void parseRssArticle(QXmlStreamReader &xml);
|
||||
void parseRSSChannel(QXmlStreamReader &xml);
|
||||
void parseAtomArticle(QXmlStreamReader &xml);
|
||||
void parseAtomChannel(QXmlStreamReader &xml);
|
||||
|
||||
QString m_lastBuildDate; // Optimization
|
||||
QString m_baseUrl;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // RSSPARSER_H
|
||||
143
src/base/rss/rssarticle.cpp
Normal file
143
src/base/rss/rssarticle.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
#include <iostream>
|
||||
|
||||
#include "rssfeed.h"
|
||||
#include "rssarticle.h"
|
||||
|
||||
using namespace Rss;
|
||||
|
||||
// public constructor
|
||||
Article::Article(Feed *parent, const QString &guid)
|
||||
: m_parent(parent)
|
||||
, m_guid(guid)
|
||||
, m_read(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool Article::hasAttachment() const
|
||||
{
|
||||
return !m_torrentUrl.isEmpty();
|
||||
}
|
||||
|
||||
QVariantHash Article::toHash() const
|
||||
{
|
||||
QVariantHash item;
|
||||
item["title"] = m_title;
|
||||
item["id"] = m_guid;
|
||||
item["torrent_url"] = m_torrentUrl;
|
||||
item["news_link"] = m_link;
|
||||
item["description"] = m_description;
|
||||
item["date"] = m_date;
|
||||
item["author"] = m_author;
|
||||
item["read"] = m_read;
|
||||
return item;
|
||||
}
|
||||
|
||||
ArticlePtr Article::fromHash(Feed *parent, const QVariantHash &h)
|
||||
{
|
||||
const QString guid = h.value("id").toString();
|
||||
if (guid.isEmpty())
|
||||
return ArticlePtr();
|
||||
|
||||
ArticlePtr art(new Article(parent, guid));
|
||||
art->m_title = h.value("title", "").toString();
|
||||
art->m_torrentUrl = h.value("torrent_url", "").toString();
|
||||
art->m_link = h.value("news_link", "").toString();
|
||||
art->m_description = h.value("description").toString();
|
||||
art->m_date = h.value("date").toDateTime();
|
||||
art->m_author = h.value("author").toString();
|
||||
art->m_read = h.value("read", false).toBool();
|
||||
|
||||
return art;
|
||||
}
|
||||
|
||||
Feed *Article::parent() const
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
const QString &Article::author() const
|
||||
{
|
||||
return m_author;
|
||||
}
|
||||
|
||||
const QString &Article::torrentUrl() const
|
||||
{
|
||||
return m_torrentUrl;
|
||||
}
|
||||
|
||||
const QString &Article::link() const
|
||||
{
|
||||
return m_link;
|
||||
}
|
||||
|
||||
QString Article::description() const
|
||||
{
|
||||
return m_description.isNull() ? "" : m_description;
|
||||
}
|
||||
|
||||
const QDateTime &Article::date() const
|
||||
{
|
||||
return m_date;
|
||||
}
|
||||
|
||||
bool Article::isRead() const
|
||||
{
|
||||
return m_read;
|
||||
}
|
||||
|
||||
void Article::markAsRead()
|
||||
{
|
||||
if (!m_read) {
|
||||
m_read = true;
|
||||
emit articleWasRead();
|
||||
}
|
||||
}
|
||||
|
||||
const QString &Article::guid() const
|
||||
{
|
||||
return m_guid;
|
||||
}
|
||||
|
||||
const QString &Article::title() const
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
void Article::handleTorrentDownloadSuccess(const QString &url)
|
||||
{
|
||||
if (url == m_torrentUrl)
|
||||
markAsRead();
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -31,58 +32,60 @@
|
||||
#ifndef RSSARTICLE_H
|
||||
#define RSSARTICLE_H
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
#include <QDateTime>
|
||||
#include <QVariantHash>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class RssFeed;
|
||||
class RssArticle;
|
||||
namespace Rss
|
||||
{
|
||||
class Feed;
|
||||
class Article;
|
||||
|
||||
typedef QSharedPointer<RssArticle> RssArticlePtr;
|
||||
typedef QSharedPointer<Article> ArticlePtr;
|
||||
|
||||
// Item of a rss stream, single information
|
||||
class RssArticle : public QObject {
|
||||
Q_OBJECT
|
||||
// Item of a rss stream, single information
|
||||
class Article: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RssArticle(RssFeed* parent, const QString& guid);
|
||||
// Accessors
|
||||
bool hasAttachment() const;
|
||||
const QString& guid() const;
|
||||
RssFeed* parent() const;
|
||||
const QString& title() const;
|
||||
const QString& author() const;
|
||||
const QString& torrentUrl() const;
|
||||
const QString& link() const;
|
||||
QString description() const;
|
||||
const QDateTime& date() const;
|
||||
bool isRead() const;
|
||||
// Setters
|
||||
void markAsRead();
|
||||
// Serialization
|
||||
QVariantHash toHash() const;
|
||||
public:
|
||||
Article(Feed *parent, const QString &guid);
|
||||
|
||||
signals:
|
||||
void articleWasRead();
|
||||
// Accessors
|
||||
bool hasAttachment() const;
|
||||
const QString &guid() const;
|
||||
Feed *parent() const;
|
||||
const QString &title() const;
|
||||
const QString &author() const;
|
||||
const QString &torrentUrl() const;
|
||||
const QString &link() const;
|
||||
QString description() const;
|
||||
const QDateTime &date() const;
|
||||
bool isRead() const;
|
||||
// Setters
|
||||
void markAsRead();
|
||||
|
||||
public slots:
|
||||
void handleTorrentDownloadSuccess(const QString& url);
|
||||
// Serialization
|
||||
QVariantHash toHash() const;
|
||||
static ArticlePtr fromHash(Feed *parent, const QVariantHash &hash);
|
||||
|
||||
friend RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& hash);
|
||||
signals:
|
||||
void articleWasRead();
|
||||
|
||||
private:
|
||||
RssFeed* m_parent;
|
||||
QString m_guid;
|
||||
QString m_title;
|
||||
QString m_torrentUrl;
|
||||
QString m_link;
|
||||
QString m_description;
|
||||
QDateTime m_date;
|
||||
QString m_author;
|
||||
bool m_read;
|
||||
};
|
||||
public slots:
|
||||
void handleTorrentDownloadSuccess(const QString &url);
|
||||
|
||||
RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& hash);
|
||||
private:
|
||||
Feed *m_parent;
|
||||
QString m_guid;
|
||||
QString m_title;
|
||||
QString m_torrentUrl;
|
||||
QString m_link;
|
||||
QString m_description;
|
||||
QDateTime m_date;
|
||||
QString m_author;
|
||||
bool m_read;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RSSARTICLE_H
|
||||
309
src/base/rss/rssdownloadrule.cpp
Normal file
309
src/base/rss/rssdownloadrule.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "rssfeed.h"
|
||||
#include "rssarticle.h"
|
||||
#include "rssdownloadrule.h"
|
||||
|
||||
using namespace Rss;
|
||||
|
||||
DownloadRule::DownloadRule()
|
||||
: m_enabled(false)
|
||||
, m_useRegex(false)
|
||||
, m_apstate(USE_GLOBAL)
|
||||
, m_ignoreDays(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool DownloadRule::matches(const QString &articleTitle) const
|
||||
{
|
||||
foreach (const QString &token, m_mustContain) {
|
||||
if (!token.isEmpty()) {
|
||||
QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
|
||||
if (reg.indexIn(articleTitle) < 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
qDebug("Checking not matching tokens");
|
||||
// Checking not matching
|
||||
foreach (const QString &token, m_mustNotContain) {
|
||||
if (!token.isEmpty()) {
|
||||
QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
|
||||
if (reg.indexIn(articleTitle) > -1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!m_episodeFilter.isEmpty()) {
|
||||
qDebug("Checking episode filter");
|
||||
QRegExp f("(^\\d{1,4})x(.*;$)");
|
||||
int pos = f.indexIn(m_episodeFilter);
|
||||
if (pos < 0)
|
||||
return false;
|
||||
|
||||
QString s = f.cap(1);
|
||||
QStringList eps = f.cap(2).split(";");
|
||||
QString expStr;
|
||||
expStr += "s0?" + s + "[ -_\\.]?" + "e0?";
|
||||
|
||||
foreach (const QString &ep, eps) {
|
||||
if (ep.isEmpty())
|
||||
continue;
|
||||
|
||||
if (ep.indexOf('-') != -1) { // Range detected
|
||||
QString partialPattern = "s0?" + s + "[ -_\\.]?" + "e(0?\\d{1,4})";
|
||||
QRegExp reg(partialPattern, Qt::CaseInsensitive);
|
||||
|
||||
if (ep.endsWith('-')) { // Infinite range
|
||||
int epOurs = ep.left(ep.size() - 1).toInt();
|
||||
|
||||
// Extract partial match from article and compare as digits
|
||||
pos = reg.indexIn(articleTitle);
|
||||
if (pos != -1) {
|
||||
int epTheirs = reg.cap(1).toInt();
|
||||
if (epTheirs >= epOurs)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else { // Normal range
|
||||
QStringList range = ep.split('-');
|
||||
Q_ASSERT(range.size() == 2);
|
||||
if (range.first().toInt() > range.last().toInt())
|
||||
continue; // Ignore this subrule completely
|
||||
|
||||
int epOursFirst = range.first().toInt();
|
||||
int epOursLast = range.last().toInt();
|
||||
|
||||
// Extract partial match from article and compare as digits
|
||||
pos = reg.indexIn(articleTitle);
|
||||
if (pos != -1) {
|
||||
int epTheirs = reg.cap(1).toInt();
|
||||
if (epOursFirst <= epTheirs && epOursLast >= epTheirs)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Single number
|
||||
QRegExp reg(expStr + ep + "\\D", Qt::CaseInsensitive);
|
||||
if (reg.indexIn(articleTitle) != -1)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DownloadRule::setMustContain(const QString &tokens)
|
||||
{
|
||||
if (m_useRegex)
|
||||
m_mustContain = QStringList() << tokens;
|
||||
else
|
||||
m_mustContain = tokens.split(" ");
|
||||
}
|
||||
|
||||
void DownloadRule::setMustNotContain(const QString &tokens)
|
||||
{
|
||||
if (m_useRegex)
|
||||
m_mustNotContain = QStringList() << tokens;
|
||||
else
|
||||
m_mustNotContain = tokens.split("|");
|
||||
}
|
||||
|
||||
QStringList DownloadRule::rssFeeds() const
|
||||
{
|
||||
return m_rssFeeds;
|
||||
}
|
||||
|
||||
void DownloadRule::setRssFeeds(const QStringList &rssFeeds)
|
||||
{
|
||||
m_rssFeeds = rssFeeds;
|
||||
}
|
||||
|
||||
QString DownloadRule::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void DownloadRule::setName(const QString &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
QString DownloadRule::savePath() const
|
||||
{
|
||||
return m_savePath;
|
||||
}
|
||||
|
||||
DownloadRulePtr DownloadRule::fromVariantHash(const QVariantHash &ruleHash)
|
||||
{
|
||||
DownloadRulePtr rule(new DownloadRule);
|
||||
rule->setName(ruleHash.value("name").toString());
|
||||
rule->setUseRegex(ruleHash.value("use_regex", false).toBool());
|
||||
rule->setMustContain(ruleHash.value("must_contain").toString());
|
||||
rule->setMustNotContain(ruleHash.value("must_not_contain").toString());
|
||||
rule->setEpisodeFilter(ruleHash.value("episode_filter").toString());
|
||||
rule->setRssFeeds(ruleHash.value("affected_feeds").toStringList());
|
||||
rule->setEnabled(ruleHash.value("enabled", false).toBool());
|
||||
rule->setSavePath(ruleHash.value("save_path").toString());
|
||||
rule->setCategory(ruleHash.value("category_assigned").toString());
|
||||
rule->setAddPaused(AddPausedState(ruleHash.value("add_paused").toUInt()));
|
||||
rule->setLastMatch(ruleHash.value("last_match").toDateTime());
|
||||
rule->setIgnoreDays(ruleHash.value("ignore_days").toInt());
|
||||
return rule;
|
||||
}
|
||||
|
||||
QVariantHash DownloadRule::toVariantHash() const
|
||||
{
|
||||
QVariantHash hash;
|
||||
hash["name"] = m_name;
|
||||
hash["must_contain"] = m_mustContain.join(" ");
|
||||
hash["must_not_contain"] = m_mustNotContain.join("|");
|
||||
hash["save_path"] = m_savePath;
|
||||
hash["affected_feeds"] = m_rssFeeds;
|
||||
hash["enabled"] = m_enabled;
|
||||
hash["category_assigned"] = m_category;
|
||||
hash["use_regex"] = m_useRegex;
|
||||
hash["add_paused"] = m_apstate;
|
||||
hash["episode_filter"] = m_episodeFilter;
|
||||
hash["last_match"] = m_lastMatch;
|
||||
hash["ignore_days"] = m_ignoreDays;
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool DownloadRule::operator==(const DownloadRule &other) const
|
||||
{
|
||||
return m_name == other.name();
|
||||
}
|
||||
|
||||
void DownloadRule::setSavePath(const QString &savePath)
|
||||
{
|
||||
m_savePath = Utils::Fs::fromNativePath(savePath);
|
||||
}
|
||||
|
||||
DownloadRule::AddPausedState DownloadRule::addPaused() const
|
||||
{
|
||||
return m_apstate;
|
||||
}
|
||||
|
||||
void DownloadRule::setAddPaused(const DownloadRule::AddPausedState &aps)
|
||||
{
|
||||
m_apstate = aps;
|
||||
}
|
||||
|
||||
QString DownloadRule::category() const
|
||||
{
|
||||
return m_category;
|
||||
}
|
||||
|
||||
void DownloadRule::setCategory(const QString &category)
|
||||
{
|
||||
m_category = category;
|
||||
}
|
||||
|
||||
bool DownloadRule::isEnabled() const
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
void DownloadRule::setEnabled(bool enable)
|
||||
{
|
||||
m_enabled = enable;
|
||||
}
|
||||
|
||||
void DownloadRule::setLastMatch(const QDateTime &d)
|
||||
{
|
||||
m_lastMatch = d;
|
||||
}
|
||||
|
||||
QDateTime DownloadRule::lastMatch() const
|
||||
{
|
||||
return m_lastMatch;
|
||||
}
|
||||
|
||||
void DownloadRule::setIgnoreDays(int d)
|
||||
{
|
||||
m_ignoreDays = d;
|
||||
}
|
||||
|
||||
int DownloadRule::ignoreDays() const
|
||||
{
|
||||
return m_ignoreDays;
|
||||
}
|
||||
|
||||
QString DownloadRule::mustContain() const
|
||||
{
|
||||
return m_mustContain.join(" ");
|
||||
}
|
||||
|
||||
QString DownloadRule::mustNotContain() const
|
||||
{
|
||||
return m_mustNotContain.join("|");
|
||||
}
|
||||
|
||||
bool DownloadRule::useRegex() const
|
||||
{
|
||||
return m_useRegex;
|
||||
}
|
||||
|
||||
void DownloadRule::setUseRegex(bool enabled)
|
||||
{
|
||||
m_useRegex = enabled;
|
||||
}
|
||||
|
||||
QString DownloadRule::episodeFilter() const
|
||||
{
|
||||
return m_episodeFilter;
|
||||
}
|
||||
|
||||
void DownloadRule::setEpisodeFilter(const QString &e)
|
||||
{
|
||||
m_episodeFilter = e;
|
||||
}
|
||||
|
||||
QStringList DownloadRule::findMatchingArticles(const FeedPtr &feed) const
|
||||
{
|
||||
QStringList ret;
|
||||
const ArticleHash &feedArticles = feed->articleHash();
|
||||
|
||||
ArticleHash::ConstIterator artIt = feedArticles.begin();
|
||||
ArticleHash::ConstIterator artItend = feedArticles.end();
|
||||
for ( ; artIt != artItend ; ++artIt) {
|
||||
const QString title = artIt.value()->title();
|
||||
if (matches(title))
|
||||
ret << title;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
106
src/base/rss/rssdownloadrule.h
Normal file
106
src/base/rss/rssdownloadrule.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSDOWNLOADRULE_H
|
||||
#define RSSDOWNLOADRULE_H
|
||||
|
||||
#include <QStringList>
|
||||
#include <QVariantHash>
|
||||
#include <QSharedPointer>
|
||||
#include <QDateTime>
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Feed;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
|
||||
class DownloadRule;
|
||||
typedef QSharedPointer<DownloadRule> DownloadRulePtr;
|
||||
|
||||
class DownloadRule
|
||||
{
|
||||
public:
|
||||
enum AddPausedState
|
||||
{
|
||||
USE_GLOBAL = 0,
|
||||
ALWAYS_PAUSED,
|
||||
NEVER_PAUSED
|
||||
};
|
||||
|
||||
DownloadRule();
|
||||
|
||||
static DownloadRulePtr fromVariantHash(const QVariantHash &ruleHash);
|
||||
QVariantHash toVariantHash() const;
|
||||
bool matches(const QString &articleTitle) const;
|
||||
void setMustContain(const QString &tokens);
|
||||
void setMustNotContain(const QString &tokens);
|
||||
QStringList rssFeeds() const;
|
||||
void setRssFeeds(const QStringList &rssFeeds);
|
||||
QString name() const;
|
||||
void setName(const QString &name);
|
||||
QString savePath() const;
|
||||
void setSavePath(const QString &savePath);
|
||||
AddPausedState addPaused() const;
|
||||
void setAddPaused(const AddPausedState &aps);
|
||||
QString category() const;
|
||||
void setCategory(const QString &category);
|
||||
bool isEnabled() const;
|
||||
void setEnabled(bool enable);
|
||||
void setLastMatch(const QDateTime &d);
|
||||
QDateTime lastMatch() const;
|
||||
void setIgnoreDays(int d);
|
||||
int ignoreDays() const;
|
||||
QString mustContain() const;
|
||||
QString mustNotContain() const;
|
||||
bool useRegex() const;
|
||||
void setUseRegex(bool enabled);
|
||||
QString episodeFilter() const;
|
||||
void setEpisodeFilter(const QString &e);
|
||||
QStringList findMatchingArticles(const FeedPtr &feed) const;
|
||||
// Operators
|
||||
bool operator==(const DownloadRule &other) const;
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
QStringList m_mustContain;
|
||||
QStringList m_mustNotContain;
|
||||
QString m_episodeFilter;
|
||||
QString m_savePath;
|
||||
QString m_category;
|
||||
bool m_enabled;
|
||||
QStringList m_rssFeeds;
|
||||
bool m_useRegex;
|
||||
AddPausedState m_apstate;
|
||||
QDateTime m_lastMatch;
|
||||
int m_ignoreDays;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RSSDOWNLOADRULE_H
|
||||
185
src/base/rss/rssdownloadrulelist.cpp
Normal file
185
src/base/rss/rssdownloadrulelist.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QFile>
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
|
||||
#include "base/preferences.h"
|
||||
#include "base/qinisettings.h"
|
||||
#include "rssdownloadrulelist.h"
|
||||
|
||||
using namespace Rss;
|
||||
|
||||
DownloadRuleList::DownloadRuleList()
|
||||
{
|
||||
loadRulesFromStorage();
|
||||
}
|
||||
|
||||
DownloadRulePtr DownloadRuleList::findMatchingRule(const QString &feedUrl, const QString &articleTitle) const
|
||||
{
|
||||
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
|
||||
QStringList ruleNames = m_feedRules.value(feedUrl);
|
||||
foreach (const QString &rule_name, ruleNames) {
|
||||
DownloadRulePtr rule = m_rules[rule_name];
|
||||
if (rule->isEnabled() && rule->matches(articleTitle)) return rule;
|
||||
}
|
||||
return DownloadRulePtr();
|
||||
}
|
||||
|
||||
void DownloadRuleList::replace(DownloadRuleList *other)
|
||||
{
|
||||
m_rules.clear();
|
||||
m_feedRules.clear();
|
||||
foreach (const QString &name, other->ruleNames()) {
|
||||
saveRule(other->getRule(name));
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadRuleList::saveRulesToStorage()
|
||||
{
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
qBTRSS.setValue("download_rules", toVariantHash());
|
||||
}
|
||||
|
||||
void DownloadRuleList::loadRulesFromStorage()
|
||||
{
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
loadRulesFromVariantHash(qBTRSS.value("download_rules").toHash());
|
||||
}
|
||||
|
||||
QVariantHash DownloadRuleList::toVariantHash() const
|
||||
{
|
||||
QVariantHash ret;
|
||||
foreach (const DownloadRulePtr &rule, m_rules.values()) {
|
||||
ret.insert(rule->name(), rule->toVariantHash());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DownloadRuleList::loadRulesFromVariantHash(const QVariantHash &h)
|
||||
{
|
||||
QVariantHash::ConstIterator it = h.begin();
|
||||
QVariantHash::ConstIterator itend = h.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
DownloadRulePtr rule = DownloadRule::fromVariantHash(it.value().toHash());
|
||||
if (rule && !rule->name().isEmpty())
|
||||
saveRule(rule);
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadRuleList::saveRule(const DownloadRulePtr &rule)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << rule->name();
|
||||
Q_ASSERT(rule);
|
||||
if (m_rules.contains(rule->name())) {
|
||||
qDebug("This is an update, removing old rule first");
|
||||
removeRule(rule->name());
|
||||
}
|
||||
m_rules.insert(rule->name(), rule);
|
||||
// Update feedRules hashtable
|
||||
foreach (const QString &feedUrl, rule->rssFeeds()) {
|
||||
m_feedRules[feedUrl].append(rule->name());
|
||||
}
|
||||
qDebug() << Q_FUNC_INFO << "EXIT";
|
||||
}
|
||||
|
||||
void DownloadRuleList::removeRule(const QString &name)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << name;
|
||||
if (!m_rules.contains(name)) return;
|
||||
DownloadRulePtr rule = m_rules.take(name);
|
||||
// Update feedRules hashtable
|
||||
foreach (const QString &feedUrl, rule->rssFeeds()) {
|
||||
m_feedRules[feedUrl].removeOne(rule->name());
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadRuleList::renameRule(const QString &oldName, const QString &newName)
|
||||
{
|
||||
if (!m_rules.contains(oldName)) return;
|
||||
|
||||
DownloadRulePtr rule = m_rules.take(oldName);
|
||||
rule->setName(newName);
|
||||
m_rules.insert(newName, rule);
|
||||
// Update feedRules hashtable
|
||||
foreach (const QString &feedUrl, rule->rssFeeds()) {
|
||||
m_feedRules[feedUrl].replace(m_feedRules[feedUrl].indexOf(oldName), newName);
|
||||
}
|
||||
}
|
||||
|
||||
DownloadRulePtr DownloadRuleList::getRule(const QString &name) const
|
||||
{
|
||||
return m_rules.value(name);
|
||||
}
|
||||
|
||||
QStringList DownloadRuleList::ruleNames() const
|
||||
{
|
||||
return m_rules.keys();
|
||||
}
|
||||
|
||||
bool DownloadRuleList::isEmpty() const
|
||||
{
|
||||
return m_rules.isEmpty();
|
||||
}
|
||||
|
||||
bool DownloadRuleList::serialize(const QString &path)
|
||||
{
|
||||
QFile f(path);
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
QDataStream out(&f);
|
||||
out.setVersion(QDataStream::Qt_4_5);
|
||||
out << toVariantHash();
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DownloadRuleList::unserialize(const QString &path)
|
||||
{
|
||||
QFile f(path);
|
||||
if (f.open(QIODevice::ReadOnly)) {
|
||||
QDataStream in(&f);
|
||||
in.setVersion(QDataStream::Qt_4_5);
|
||||
QVariantHash tmp;
|
||||
in >> tmp;
|
||||
f.close();
|
||||
if (tmp.isEmpty())
|
||||
return false;
|
||||
qDebug("Processing was successful!");
|
||||
loadRulesFromVariantHash(tmp);
|
||||
return true;
|
||||
} else {
|
||||
qDebug("Error: could not open file at %s", qPrintable(path));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -34,36 +34,40 @@
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QVariantHash>
|
||||
|
||||
#include "rssdownloadrule.h"
|
||||
|
||||
class RssDownloadRuleList
|
||||
namespace Rss
|
||||
{
|
||||
Q_DISABLE_COPY(RssDownloadRuleList)
|
||||
class DownloadRuleList
|
||||
{
|
||||
Q_DISABLE_COPY(DownloadRuleList)
|
||||
|
||||
public:
|
||||
RssDownloadRuleList();
|
||||
RssDownloadRulePtr findMatchingRule(const QString &feed_url, const QString &article_title) const;
|
||||
// Operators
|
||||
void saveRule(const RssDownloadRulePtr &rule);
|
||||
void removeRule(const QString &name);
|
||||
void renameRule(const QString &old_name, const QString &new_name);
|
||||
RssDownloadRulePtr getRule(const QString &name) const;
|
||||
inline QStringList ruleNames() const { return m_rules.keys(); }
|
||||
inline bool isEmpty() const { return m_rules.isEmpty(); }
|
||||
void saveRulesToStorage();
|
||||
bool serialize(const QString& path);
|
||||
bool unserialize(const QString& path);
|
||||
void replace(RssDownloadRuleList* other);
|
||||
public:
|
||||
DownloadRuleList();
|
||||
|
||||
private:
|
||||
void loadRulesFromStorage();
|
||||
void loadRulesFromVariantHash(const QVariantHash& l);
|
||||
QVariantHash toVariantHash() const;
|
||||
DownloadRulePtr findMatchingRule(const QString &feedUrl, const QString &articleTitle) const;
|
||||
// Operators
|
||||
void saveRule(const DownloadRulePtr &rule);
|
||||
void removeRule(const QString &name);
|
||||
void renameRule(const QString &oldName, const QString &newName);
|
||||
DownloadRulePtr getRule(const QString &name) const;
|
||||
QStringList ruleNames() const;
|
||||
bool isEmpty() const;
|
||||
void saveRulesToStorage();
|
||||
bool serialize(const QString &path);
|
||||
bool unserialize(const QString &path);
|
||||
void replace(DownloadRuleList *other);
|
||||
|
||||
private:
|
||||
QHash<QString, RssDownloadRulePtr> m_rules;
|
||||
QHash<QString, QStringList> m_feedRules;
|
||||
private:
|
||||
void loadRulesFromStorage();
|
||||
void loadRulesFromVariantHash(const QVariantHash &l);
|
||||
QVariantHash toVariantHash() const;
|
||||
|
||||
};
|
||||
private:
|
||||
QHash<QString, DownloadRulePtr> m_rules;
|
||||
QHash<QString, QStringList> m_feedRules;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RSSDOWNLOADFILTERLIST_H
|
||||
449
src/base/rss/rssfeed.cpp
Normal file
449
src/base/rss/rssfeed.cpp
Normal file
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "base/preferences.h"
|
||||
#include "base/qinisettings.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/magneturi.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/net/downloadhandler.h"
|
||||
#include "private/rssparser.h"
|
||||
#include "rssdownloadrulelist.h"
|
||||
#include "rssarticle.h"
|
||||
#include "rssfolder.h"
|
||||
#include "rssmanager.h"
|
||||
#include "rssfeed.h"
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
bool articleDateRecentThan(const ArticlePtr &left, const ArticlePtr &right)
|
||||
{
|
||||
return left->date() > right->date();
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Rss;
|
||||
|
||||
Feed::Feed(const QString &url, Manager *manager)
|
||||
: m_manager(manager)
|
||||
, m_url (QUrl::fromEncoded(url.toUtf8()).toString())
|
||||
, m_icon(":/icons/oxygen/application-rss+xml.png")
|
||||
, m_unreadCount(0)
|
||||
, m_dirty(false)
|
||||
, m_inErrorState(false)
|
||||
, m_loading(false)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << m_url;
|
||||
m_parser = new Private::Parser;
|
||||
m_parser->moveToThread(m_manager->workingThread());
|
||||
connect(this, SIGNAL(destroyed()), m_parser, SLOT(deleteLater()));
|
||||
// Listen for new RSS downloads
|
||||
connect(m_parser, SIGNAL(feedTitle(QString)), SLOT(handleFeedTitle(QString)));
|
||||
connect(m_parser, SIGNAL(newArticle(QVariantHash)), SLOT(handleNewArticle(QVariantHash)));
|
||||
connect(m_parser, SIGNAL(finished(QString)), SLOT(handleParsingFinished(QString)));
|
||||
|
||||
// Download the RSS Feed icon
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl(), true);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleIconDownloadFinished(QString, QString)));
|
||||
|
||||
// Load old RSS articles
|
||||
loadItemsFromDisk();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
Feed::~Feed()
|
||||
{
|
||||
if (!m_icon.startsWith(":/") && QFile::exists(m_icon))
|
||||
Utils::Fs::forceRemove(m_icon);
|
||||
}
|
||||
|
||||
void Feed::saveItemsToDisk()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << m_url;
|
||||
if (!m_dirty) return;
|
||||
|
||||
m_dirty = false;
|
||||
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QVariantList oldItems;
|
||||
|
||||
ArticleHash::ConstIterator it = m_articles.begin();
|
||||
ArticleHash::ConstIterator itend = m_articles.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
oldItems << it.value()->toHash();
|
||||
}
|
||||
qDebug("Saving %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
|
||||
QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
allOldItems[m_url] = oldItems;
|
||||
qBTRSS.setValue("old_items", allOldItems);
|
||||
}
|
||||
|
||||
void Feed::loadItemsFromDisk()
|
||||
{
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
const QVariantList oldItems = allOldItems.value(m_url, QVariantList()).toList();
|
||||
qDebug("Loading %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
|
||||
|
||||
foreach (const QVariant &var_it, oldItems) {
|
||||
QVariantHash item = var_it.toHash();
|
||||
ArticlePtr rssItem = Article::fromHash(this, item);
|
||||
if (rssItem)
|
||||
addArticle(rssItem);
|
||||
}
|
||||
}
|
||||
|
||||
void Feed::addArticle(const ArticlePtr &article)
|
||||
{
|
||||
int maxArticles = Preferences::instance()->getRSSMaxArticlesPerFeed();
|
||||
|
||||
if (!m_articles.contains(article->guid())) {
|
||||
m_dirty = true;
|
||||
|
||||
// Update unreadCount
|
||||
if (!article->isRead())
|
||||
++m_unreadCount;
|
||||
// Insert in hash table
|
||||
m_articles[article->guid()] = article;
|
||||
if (!article->isRead()) // Optimization
|
||||
connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleRead()), Qt::UniqueConnection);
|
||||
// Insertion sort
|
||||
ArticleList::Iterator lowerBound = qLowerBound(m_articlesByDate.begin(), m_articlesByDate.end(), article, articleDateRecentThan);
|
||||
m_articlesByDate.insert(lowerBound, article);
|
||||
int lbIndex = m_articlesByDate.indexOf(article);
|
||||
if (m_articlesByDate.size() > maxArticles) {
|
||||
ArticlePtr oldestArticle = m_articlesByDate.takeLast();
|
||||
m_articles.remove(oldestArticle->guid());
|
||||
// Update unreadCount
|
||||
if (!oldestArticle->isRead())
|
||||
--m_unreadCount;
|
||||
}
|
||||
|
||||
// Check if article was inserted at the end of the list and will break max_articles limit
|
||||
if (Preferences::instance()->isRssDownloadingEnabled()) {
|
||||
if ((lbIndex < maxArticles) && !article->isRead())
|
||||
downloadArticleTorrentIfMatching(article);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// m_articles.contains(article->guid())
|
||||
// Try to download skipped articles
|
||||
if (Preferences::instance()->isRssDownloadingEnabled()) {
|
||||
ArticlePtr skipped = m_articles.value(article->guid(), ArticlePtr());
|
||||
if (skipped) {
|
||||
if (!skipped->isRead())
|
||||
downloadArticleTorrentIfMatching(skipped);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Feed::refresh()
|
||||
{
|
||||
if (m_loading) {
|
||||
qWarning() << Q_FUNC_INFO << "Feed" << displayName() << "is already being refreshed, ignoring request";
|
||||
return false;
|
||||
}
|
||||
m_loading = true;
|
||||
// Download the RSS again
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), this, SLOT(handleRssDownloadFinished(QString, QByteArray)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleRssDownloadFailed(QString, QString)));
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Feed::id() const
|
||||
{
|
||||
return m_url;
|
||||
}
|
||||
|
||||
void Feed::removeAllSettings()
|
||||
{
|
||||
qDebug() << "Removing all settings / history for feed: " << m_url;
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QVariantHash feedsWDownloader = qBTRSS.value("downloader_on", QVariantHash()).toHash();
|
||||
if (feedsWDownloader.contains(m_url)) {
|
||||
feedsWDownloader.remove(m_url);
|
||||
qBTRSS.setValue("downloader_on", feedsWDownloader);
|
||||
}
|
||||
QVariantHash allFeedsFilters = qBTRSS.value("feed_filters", QVariantHash()).toHash();
|
||||
if (allFeedsFilters.contains(m_url)) {
|
||||
allFeedsFilters.remove(m_url);
|
||||
qBTRSS.setValue("feed_filters", allFeedsFilters);
|
||||
}
|
||||
QVariantHash allOldItems = qBTRSS.value("old_items", QVariantHash()).toHash();
|
||||
if (allOldItems.contains(m_url)) {
|
||||
allOldItems.remove(m_url);
|
||||
qBTRSS.setValue("old_items", allOldItems);
|
||||
}
|
||||
}
|
||||
|
||||
bool Feed::isLoading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
QString Feed::title() const
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
void Feed::rename(const QString &newName)
|
||||
{
|
||||
qDebug() << "Renaming stream to" << newName;
|
||||
m_alias = newName;
|
||||
}
|
||||
|
||||
// Return the alias if the stream has one, the url if it has no alias
|
||||
QString Feed::displayName() const
|
||||
{
|
||||
if (!m_alias.isEmpty())
|
||||
return m_alias;
|
||||
if (!m_title.isEmpty())
|
||||
return m_title;
|
||||
return m_url;
|
||||
}
|
||||
|
||||
QString Feed::url() const
|
||||
{
|
||||
return m_url;
|
||||
}
|
||||
|
||||
QString Feed::iconPath() const
|
||||
{
|
||||
if (m_inErrorState)
|
||||
return QLatin1String(":/icons/oxygen/unavailable.png");
|
||||
|
||||
return m_icon;
|
||||
}
|
||||
|
||||
bool Feed::hasCustomIcon() const
|
||||
{
|
||||
return !m_icon.startsWith(":/");
|
||||
}
|
||||
|
||||
void Feed::setIconPath(const QString &path)
|
||||
{
|
||||
if (!path.isEmpty() && QFile::exists(path))
|
||||
m_icon = path;
|
||||
}
|
||||
|
||||
ArticlePtr Feed::getItem(const QString &guid) const
|
||||
{
|
||||
return m_articles.value(guid);
|
||||
}
|
||||
|
||||
uint Feed::count() const
|
||||
{
|
||||
return m_articles.size();
|
||||
}
|
||||
|
||||
void Feed::markAsRead()
|
||||
{
|
||||
ArticleHash::ConstIterator it = m_articles.begin();
|
||||
ArticleHash::ConstIterator itend = m_articles.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
it.value()->markAsRead();
|
||||
}
|
||||
m_unreadCount = 0;
|
||||
m_manager->forwardFeedInfosChanged(m_url, displayName(), 0);
|
||||
}
|
||||
|
||||
uint Feed::unreadCount() const
|
||||
{
|
||||
return m_unreadCount;
|
||||
}
|
||||
|
||||
ArticleList Feed::articleListByDateDesc() const
|
||||
{
|
||||
return m_articlesByDate;
|
||||
}
|
||||
|
||||
const ArticleHash &Feed::articleHash() const
|
||||
{
|
||||
return m_articles;
|
||||
}
|
||||
|
||||
ArticleList Feed::unreadArticleListByDateDesc() const
|
||||
{
|
||||
ArticleList unreadNews;
|
||||
|
||||
ArticleList::ConstIterator it = m_articlesByDate.begin();
|
||||
ArticleList::ConstIterator itend = m_articlesByDate.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
if (!(*it)->isRead())
|
||||
unreadNews << *it;
|
||||
}
|
||||
return unreadNews;
|
||||
}
|
||||
|
||||
// download the icon from the address
|
||||
QString Feed::iconUrl() const
|
||||
{
|
||||
// XXX: This works for most sites but it is not perfect
|
||||
return QString("http://%1/favicon.ico").arg(QUrl(m_url).host());
|
||||
}
|
||||
|
||||
void Feed::handleIconDownloadFinished(const QString &url, const QString &filePath)
|
||||
{
|
||||
Q_UNUSED(url);
|
||||
m_icon = filePath;
|
||||
qDebug() << Q_FUNC_INFO << "icon path:" << m_icon;
|
||||
m_manager->forwardFeedIconChanged(m_url, m_icon);
|
||||
}
|
||||
|
||||
void Feed::handleRssDownloadFinished(const QString &url, const QByteArray &data)
|
||||
{
|
||||
Q_UNUSED(url);
|
||||
qDebug() << Q_FUNC_INFO << "Successfully downloaded RSS feed at" << m_url;
|
||||
// Parse the download RSS
|
||||
QMetaObject::invokeMethod(m_parser, "parse", Qt::QueuedConnection, Q_ARG(QByteArray, data));
|
||||
}
|
||||
|
||||
void Feed::handleRssDownloadFailed(const QString &url, const QString &error)
|
||||
{
|
||||
Q_UNUSED(url);
|
||||
m_inErrorState = true;
|
||||
m_loading = false;
|
||||
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
|
||||
qWarning() << "Failed to download RSS feed at" << m_url;
|
||||
qWarning() << "Reason:" << error;
|
||||
}
|
||||
|
||||
void Feed::handleFeedTitle(const QString &title)
|
||||
{
|
||||
if (m_title == title) return;
|
||||
|
||||
m_title = title;
|
||||
|
||||
// Notify that we now have something better than a URL to display
|
||||
if (m_alias.isEmpty())
|
||||
m_manager->forwardFeedInfosChanged(m_url, title, m_unreadCount);
|
||||
}
|
||||
|
||||
void Feed::downloadArticleTorrentIfMatching(const ArticlePtr &article)
|
||||
{
|
||||
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
|
||||
DownloadRuleList *rules = m_manager->downloadRules();
|
||||
DownloadRulePtr matchingRule = rules->findMatchingRule(m_url, article->title());
|
||||
if (!matchingRule) return;
|
||||
|
||||
if (matchingRule->ignoreDays() > 0) {
|
||||
QDateTime lastMatch = matchingRule->lastMatch();
|
||||
if (lastMatch.isValid()) {
|
||||
if (QDateTime::currentDateTime() < lastMatch.addDays(matchingRule->ignoreDays())) {
|
||||
article->markAsRead();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matchingRule->setLastMatch(QDateTime::currentDateTime());
|
||||
rules->saveRulesToStorage();
|
||||
// Download the torrent
|
||||
const QString &torrentUrl = article->torrentUrl();
|
||||
if (torrentUrl.isEmpty()) {
|
||||
Logger::instance()->addMessage(tr("Automatic download of '%1' from '%2' RSS feed failed because it doesn't contain a torrent or a magnet link...").arg(article->title()).arg(displayName()), Log::WARNING);
|
||||
article->markAsRead();
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::instance()->addMessage(tr("Automatically downloading '%1' torrent from '%2' RSS feed...").arg(article->title()).arg(displayName()));
|
||||
if (BitTorrent::MagnetUri(torrentUrl).isValid())
|
||||
article->markAsRead();
|
||||
else
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFinished(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString&)), Qt::UniqueConnection);
|
||||
|
||||
BitTorrent::AddTorrentParams params;
|
||||
params.savePath = matchingRule->savePath();
|
||||
params.category = matchingRule->category();
|
||||
if (matchingRule->addPaused() == DownloadRule::ALWAYS_PAUSED)
|
||||
params.addPaused = TriStateBool::True;
|
||||
else if (matchingRule->addPaused() == DownloadRule::NEVER_PAUSED)
|
||||
params.addPaused = TriStateBool::False;
|
||||
BitTorrent::Session::instance()->addTorrent(torrentUrl, params);
|
||||
}
|
||||
|
||||
void Feed::recheckRssItemsForDownload()
|
||||
{
|
||||
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
|
||||
foreach (const ArticlePtr &article, m_articlesByDate) {
|
||||
if (!article->isRead())
|
||||
downloadArticleTorrentIfMatching(article);
|
||||
}
|
||||
}
|
||||
|
||||
void Feed::handleNewArticle(const QVariantHash &articleData)
|
||||
{
|
||||
ArticlePtr article = Article::fromHash(this, articleData);
|
||||
if (article.isNull()) {
|
||||
qDebug() << "Article hash corrupted or guid is uncomputable; feed url: " << m_url;
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(article);
|
||||
addArticle(article);
|
||||
|
||||
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
|
||||
// FIXME: We should forward the information here but this would seriously decrease
|
||||
// performance with current design.
|
||||
//m_manager->forwardFeedContentChanged(m_url);
|
||||
}
|
||||
|
||||
void Feed::handleParsingFinished(const QString &error)
|
||||
{
|
||||
if (!error.isEmpty()) {
|
||||
qWarning() << "Failed to parse RSS feed at" << m_url;
|
||||
qWarning() << "Reason:" << error;
|
||||
}
|
||||
|
||||
m_loading = false;
|
||||
m_inErrorState = !error.isEmpty();
|
||||
|
||||
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
|
||||
// XXX: Would not be needed if we did this in handleNewArticle() instead
|
||||
m_manager->forwardFeedContentChanged(m_url);
|
||||
|
||||
saveItemsToDisk();
|
||||
}
|
||||
|
||||
void Feed::handleArticleRead()
|
||||
{
|
||||
--m_unreadCount;
|
||||
m_dirty = true;
|
||||
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
|
||||
}
|
||||
122
src/base/rss/rssfeed.h
Normal file
122
src/base/rss/rssfeed.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSFEED_H
|
||||
#define RSSFEED_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariantHash>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QNetworkCookie>
|
||||
|
||||
#include "rssfile.h"
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Folder;
|
||||
class Feed;
|
||||
class Manager;
|
||||
class DownloadRuleList;
|
||||
|
||||
typedef QHash<QString, ArticlePtr> ArticleHash;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
typedef QList<FeedPtr> FeedList;
|
||||
|
||||
namespace Private
|
||||
{
|
||||
class Parser;
|
||||
}
|
||||
|
||||
bool articleDateRecentThan(const ArticlePtr &left, const ArticlePtr &right);
|
||||
|
||||
class Feed: public QObject, public File
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Feed(const QString &url, Manager *manager);
|
||||
~Feed();
|
||||
|
||||
bool refresh();
|
||||
QString id() const;
|
||||
void removeAllSettings();
|
||||
void saveItemsToDisk();
|
||||
bool isLoading() const;
|
||||
QString title() const;
|
||||
void rename(const QString &newName);
|
||||
QString displayName() const;
|
||||
QString url() const;
|
||||
QString iconPath() const;
|
||||
bool hasCustomIcon() const;
|
||||
void setIconPath(const QString &pathHierarchy);
|
||||
ArticlePtr getItem(const QString &guid) const;
|
||||
uint count() const;
|
||||
void markAsRead();
|
||||
uint unreadCount() const;
|
||||
ArticleList articleListByDateDesc() const;
|
||||
const ArticleHash &articleHash() const;
|
||||
ArticleList unreadArticleListByDateDesc() const;
|
||||
void recheckRssItemsForDownload();
|
||||
|
||||
private slots:
|
||||
void handleIconDownloadFinished(const QString &url, const QString &filePath);
|
||||
void handleRssDownloadFinished(const QString &url, const QByteArray &data);
|
||||
void handleRssDownloadFailed(const QString &url, const QString &error);
|
||||
void handleFeedTitle(const QString &title);
|
||||
void handleNewArticle(const QVariantHash &article);
|
||||
void handleParsingFinished(const QString &error);
|
||||
void handleArticleRead();
|
||||
|
||||
private:
|
||||
QString iconUrl() const;
|
||||
void loadItemsFromDisk();
|
||||
void addArticle(const ArticlePtr &article);
|
||||
void downloadArticleTorrentIfMatching(const ArticlePtr &article);
|
||||
|
||||
private:
|
||||
Manager *m_manager;
|
||||
Private::Parser *m_parser;
|
||||
ArticleHash m_articles;
|
||||
ArticleList m_articlesByDate; // Articles sorted by date (more recent first)
|
||||
QString m_title;
|
||||
QString m_url;
|
||||
QString m_alias;
|
||||
QString m_icon;
|
||||
uint m_unreadCount;
|
||||
bool m_dirty;
|
||||
bool m_inErrorState;
|
||||
bool m_loading;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RSSFEED_H
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,13 +29,23 @@
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "rssfile.h"
|
||||
#include "rssfolder.h"
|
||||
#include "rssfile.h"
|
||||
|
||||
QStringList RssFile::pathHierarchy() const {
|
||||
QStringList path;
|
||||
if (parent())
|
||||
path << parent()->pathHierarchy();
|
||||
path << id();
|
||||
return path;
|
||||
using namespace Rss;
|
||||
|
||||
File::~File() {}
|
||||
|
||||
Folder *File::parentFolder() const
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
QStringList File::pathHierarchy() const
|
||||
{
|
||||
QStringList path;
|
||||
if (m_parent)
|
||||
path << m_parent->pathHierarchy();
|
||||
path << id();
|
||||
return path;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -31,45 +32,51 @@
|
||||
#ifndef RSSFILE_H
|
||||
#define RSSFILE_H
|
||||
|
||||
#include <QIcon>
|
||||
#include <QList>
|
||||
#include <QStringList>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class RssFolder;
|
||||
class RssFile;
|
||||
class RssArticle;
|
||||
namespace Rss
|
||||
{
|
||||
class Folder;
|
||||
class File;
|
||||
class Article;
|
||||
|
||||
typedef QSharedPointer<RssFile> RssFilePtr;
|
||||
typedef QSharedPointer<RssArticle> RssArticlePtr;
|
||||
typedef QList<RssArticlePtr> RssArticleList;
|
||||
typedef QList<RssFilePtr> RssFileList;
|
||||
typedef QSharedPointer<File> FilePtr;
|
||||
typedef QSharedPointer<Article> ArticlePtr;
|
||||
typedef QList<ArticlePtr> ArticleList;
|
||||
typedef QList<FilePtr> FileList;
|
||||
|
||||
/**
|
||||
* Parent interface for RssFolder and RssFeed.
|
||||
*/
|
||||
class RssFile {
|
||||
public:
|
||||
virtual ~RssFile() {}
|
||||
/**
|
||||
* Parent interface for Rss::Folder and Rss::Feed.
|
||||
*/
|
||||
class File
|
||||
{
|
||||
public:
|
||||
virtual ~File();
|
||||
|
||||
virtual uint unreadCount() const = 0;
|
||||
virtual QString displayName() const = 0;
|
||||
virtual QString id() const = 0;
|
||||
virtual QIcon icon() const = 0;
|
||||
virtual void rename(const QString &new_name) = 0;
|
||||
virtual void markAsRead() = 0;
|
||||
virtual RssFolder* parent() const = 0;
|
||||
virtual void setParent(RssFolder* parent) = 0;
|
||||
virtual bool refresh() = 0;
|
||||
virtual RssArticleList articleListByDateDesc() const = 0;
|
||||
virtual RssArticleList unreadArticleListByDateDesc() const = 0;
|
||||
virtual void removeAllSettings() = 0;
|
||||
virtual void saveItemsToDisk() = 0;
|
||||
virtual void recheckRssItemsForDownload() = 0;
|
||||
QStringList pathHierarchy() const;
|
||||
virtual QString id() const = 0;
|
||||
virtual QString displayName() const = 0;
|
||||
virtual uint unreadCount() const = 0;
|
||||
virtual QString iconPath() const = 0;
|
||||
virtual ArticleList articleListByDateDesc() const = 0;
|
||||
virtual ArticleList unreadArticleListByDateDesc() const = 0;
|
||||
|
||||
protected:
|
||||
uint m_unreadCount;
|
||||
};
|
||||
virtual void rename(const QString &newName) = 0;
|
||||
virtual void markAsRead() = 0;
|
||||
virtual bool refresh() = 0;
|
||||
virtual void removeAllSettings() = 0;
|
||||
virtual void saveItemsToDisk() = 0;
|
||||
virtual void recheckRssItemsForDownload() = 0;
|
||||
|
||||
Folder *parentFolder() const;
|
||||
QStringList pathHierarchy() const;
|
||||
|
||||
protected:
|
||||
friend class Folder;
|
||||
|
||||
Folder *m_parent = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RSSFILE_H
|
||||
253
src/base/rss/rssfolder.cpp
Normal file
253
src/base/rss/rssfolder.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "base/iconprovider.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "rssmanager.h"
|
||||
#include "rssfeed.h"
|
||||
#include "rssarticle.h"
|
||||
#include "rssfolder.h"
|
||||
|
||||
using namespace Rss;
|
||||
|
||||
Folder::Folder(const QString &name)
|
||||
: m_name(name)
|
||||
{
|
||||
}
|
||||
|
||||
uint Folder::unreadCount() const
|
||||
{
|
||||
uint nbUnread = 0;
|
||||
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it)
|
||||
nbUnread += it.value()->unreadCount();
|
||||
|
||||
return nbUnread;
|
||||
}
|
||||
|
||||
void Folder::removeChild(const QString &childId)
|
||||
{
|
||||
if (m_children.contains(childId)) {
|
||||
FilePtr child = m_children.take(childId);
|
||||
child->removeAllSettings();
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh All Children
|
||||
bool Folder::refresh()
|
||||
{
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
bool refreshed = false;
|
||||
for ( ; it != itend; ++it) {
|
||||
if (it.value()->refresh())
|
||||
refreshed = true;
|
||||
}
|
||||
return refreshed;
|
||||
}
|
||||
|
||||
ArticleList Folder::articleListByDateDesc() const
|
||||
{
|
||||
ArticleList news;
|
||||
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
int n = news.size();
|
||||
news << it.value()->articleListByDateDesc();
|
||||
std::inplace_merge(news.begin(), news.begin() + n, news.end(), articleDateRecentThan);
|
||||
}
|
||||
return news;
|
||||
}
|
||||
|
||||
ArticleList Folder::unreadArticleListByDateDesc() const
|
||||
{
|
||||
ArticleList unreadNews;
|
||||
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
int n = unreadNews.size();
|
||||
unreadNews << it.value()->unreadArticleListByDateDesc();
|
||||
std::inplace_merge(unreadNews.begin(), unreadNews.begin() + n, unreadNews.end(), articleDateRecentThan);
|
||||
}
|
||||
return unreadNews;
|
||||
}
|
||||
|
||||
FileList Folder::getContent() const
|
||||
{
|
||||
return m_children.values();
|
||||
}
|
||||
|
||||
uint Folder::getNbFeeds() const
|
||||
{
|
||||
uint nbFeeds = 0;
|
||||
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
if (FolderPtr folder = qSharedPointerDynamicCast<Folder>(it.value()))
|
||||
nbFeeds += folder->getNbFeeds();
|
||||
else
|
||||
++nbFeeds; // Feed
|
||||
}
|
||||
return nbFeeds;
|
||||
}
|
||||
|
||||
QString Folder::displayName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void Folder::rename(const QString &newName)
|
||||
{
|
||||
if (m_name == newName) return;
|
||||
|
||||
Q_ASSERT(!m_parent->hasChild(newName));
|
||||
if (!m_parent->hasChild(newName)) {
|
||||
// Update parent
|
||||
FilePtr folder = m_parent->m_children.take(m_name);
|
||||
m_parent->m_children[newName] = folder;
|
||||
// Actually rename
|
||||
m_name = newName;
|
||||
}
|
||||
}
|
||||
|
||||
void Folder::markAsRead()
|
||||
{
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
it.value()->markAsRead();
|
||||
}
|
||||
}
|
||||
|
||||
FeedList Folder::getAllFeeds() const
|
||||
{
|
||||
FeedList streams;
|
||||
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
if (FeedPtr feed = qSharedPointerDynamicCast<Feed>(it.value()))
|
||||
streams << feed;
|
||||
else if (FolderPtr folder = qSharedPointerDynamicCast<Folder>(it.value()))
|
||||
streams << folder->getAllFeeds();
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
|
||||
QHash<QString, FeedPtr> Folder::getAllFeedsAsHash() const
|
||||
{
|
||||
QHash<QString, FeedPtr> ret;
|
||||
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
if (FeedPtr feed = qSharedPointerDynamicCast<Feed>(it.value())) {
|
||||
qDebug() << Q_FUNC_INFO << feed->url();
|
||||
ret[feed->url()] = feed;
|
||||
}
|
||||
else if (FolderPtr folder = qSharedPointerDynamicCast<Folder>(it.value())) {
|
||||
ret.unite(folder->getAllFeedsAsHash());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Folder::addFile(const FilePtr &item)
|
||||
{
|
||||
Q_ASSERT(!m_children.contains(item->id()));
|
||||
if (!m_children.contains(item->id())) {
|
||||
m_children[item->id()] = item;
|
||||
// Update parent
|
||||
item->m_parent = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Folder::removeAllItems()
|
||||
{
|
||||
m_children.clear();
|
||||
}
|
||||
|
||||
FilePtr Folder::child(const QString &childId)
|
||||
{
|
||||
return m_children.value(childId);
|
||||
}
|
||||
|
||||
void Folder::removeAllSettings()
|
||||
{
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it)
|
||||
it.value()->removeAllSettings();
|
||||
}
|
||||
|
||||
void Folder::saveItemsToDisk()
|
||||
{
|
||||
foreach (const FilePtr &child, m_children.values())
|
||||
child->saveItemsToDisk();
|
||||
}
|
||||
|
||||
QString Folder::id() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString Folder::iconPath() const
|
||||
{
|
||||
return IconProvider::instance()->getIconPath("inode-directory");
|
||||
}
|
||||
|
||||
bool Folder::hasChild(const QString &childId)
|
||||
{
|
||||
return m_children.contains(childId);
|
||||
}
|
||||
|
||||
FilePtr Folder::takeChild(const QString &childId)
|
||||
{
|
||||
return m_children.take(childId);
|
||||
}
|
||||
|
||||
void Folder::recheckRssItemsForDownload()
|
||||
{
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it)
|
||||
it.value()->recheckRssItemsForDownload();
|
||||
}
|
||||
86
src/base/rss/rssfolder.h
Normal file
86
src/base/rss/rssfolder.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSFOLDER_H
|
||||
#define RSSFOLDER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "rssfile.h"
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Folder;
|
||||
class Feed;
|
||||
class Manager;
|
||||
|
||||
typedef QHash<QString, FilePtr> FileHash;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
typedef QSharedPointer<Folder> FolderPtr;
|
||||
typedef QList<FeedPtr> FeedList;
|
||||
|
||||
class Folder: public File
|
||||
{
|
||||
public:
|
||||
explicit Folder(const QString &name = QString());
|
||||
|
||||
uint unreadCount() const;
|
||||
uint getNbFeeds() const;
|
||||
FileList getContent() const;
|
||||
FeedList getAllFeeds() const;
|
||||
QHash<QString, FeedPtr> getAllFeedsAsHash() const;
|
||||
QString displayName() const;
|
||||
QString id() const;
|
||||
QString iconPath() const;
|
||||
bool hasChild(const QString &childId);
|
||||
ArticleList articleListByDateDesc() const;
|
||||
ArticleList unreadArticleListByDateDesc() const;
|
||||
|
||||
void rename(const QString &newName);
|
||||
void markAsRead();
|
||||
bool refresh();
|
||||
void removeAllSettings();
|
||||
void saveItemsToDisk();
|
||||
void recheckRssItemsForDownload();
|
||||
void removeAllItems();
|
||||
FilePtr child(const QString &childId);
|
||||
FilePtr takeChild(const QString &childId);
|
||||
bool addFile(const FilePtr &item);
|
||||
void removeChild(const QString &childId);
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
FileHash m_children;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RSSFOLDER_H
|
||||
190
src/base/rss/rssmanager.cpp
Normal file
190
src/base/rss/rssmanager.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "rssfolder.h"
|
||||
#include "rssfeed.h"
|
||||
#include "rssarticle.h"
|
||||
#include "rssdownloadrulelist.h"
|
||||
#include "rssmanager.h"
|
||||
|
||||
static const int MSECS_PER_MIN = 60000;
|
||||
|
||||
using namespace Rss;
|
||||
using namespace Rss::Private;
|
||||
|
||||
Manager::Manager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_downloadRules(new DownloadRuleList)
|
||||
, m_rootFolder(new Folder)
|
||||
, m_workingThread(new QThread(this))
|
||||
{
|
||||
m_workingThread->start();
|
||||
connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(refresh()));
|
||||
m_refreshInterval = Preferences::instance()->getRSSRefreshInterval();
|
||||
m_refreshTimer.start(m_refreshInterval * MSECS_PER_MIN);
|
||||
}
|
||||
|
||||
Manager::~Manager()
|
||||
{
|
||||
qDebug("Deleting RSSManager...");
|
||||
m_workingThread->quit();
|
||||
m_workingThread->wait();
|
||||
delete m_downloadRules;
|
||||
m_rootFolder->saveItemsToDisk();
|
||||
saveStreamList();
|
||||
m_rootFolder.clear();
|
||||
qDebug("RSSManager deleted");
|
||||
}
|
||||
|
||||
void Manager::updateRefreshInterval(uint val)
|
||||
{
|
||||
if (m_refreshInterval != val) {
|
||||
m_refreshInterval = val;
|
||||
m_refreshTimer.start(m_refreshInterval*60000);
|
||||
qDebug("New RSS refresh interval is now every %dmin", m_refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::loadStreamList()
|
||||
{
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
const QStringList streamsUrl = pref->getRssFeedsUrls();
|
||||
const QStringList aliases = pref->getRssFeedsAliases();
|
||||
if (streamsUrl.size() != aliases.size()) {
|
||||
Logger::instance()->addMessage("Corrupted RSS list, not loading it.", Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
uint i = 0;
|
||||
qDebug() << Q_FUNC_INFO << streamsUrl;
|
||||
foreach (QString s, streamsUrl) {
|
||||
QStringList path = s.split("\\", QString::SkipEmptyParts);
|
||||
if (path.empty()) continue;
|
||||
|
||||
const QString feedUrl = path.takeLast();
|
||||
qDebug() << "Feed URL:" << feedUrl;
|
||||
// Create feed path (if it does not exists)
|
||||
FolderPtr feedParent = m_rootFolder;
|
||||
foreach (const QString &folderName, path) {
|
||||
if (!feedParent->hasChild(folderName)) {
|
||||
qDebug() << "Adding parent folder:" << folderName;
|
||||
FolderPtr folder(new Folder(folderName));
|
||||
feedParent->addFile(folder);
|
||||
feedParent = folder;
|
||||
}
|
||||
else {
|
||||
feedParent = qSharedPointerDynamicCast<Folder>(feedParent->child(folderName));
|
||||
}
|
||||
}
|
||||
// Create feed
|
||||
qDebug() << "Adding feed to parent folder";
|
||||
FeedPtr stream(new Feed(feedUrl, this));
|
||||
feedParent->addFile(stream);
|
||||
const QString &alias = aliases[i];
|
||||
if (!alias.isEmpty())
|
||||
stream->rename(alias);
|
||||
++i;
|
||||
}
|
||||
qDebug("NB RSS streams loaded: %d", streamsUrl.size());
|
||||
}
|
||||
|
||||
void Manager::forwardFeedContentChanged(const QString &url)
|
||||
{
|
||||
emit feedContentChanged(url);
|
||||
}
|
||||
|
||||
void Manager::forwardFeedInfosChanged(const QString &url, const QString &displayName, uint unreadCount)
|
||||
{
|
||||
emit feedInfosChanged(url, displayName, unreadCount);
|
||||
}
|
||||
|
||||
void Manager::forwardFeedIconChanged(const QString &url, const QString &iconPath)
|
||||
{
|
||||
emit feedIconChanged(url, iconPath);
|
||||
}
|
||||
|
||||
void Manager::moveFile(const FilePtr &file, const FolderPtr &destinationFolder)
|
||||
{
|
||||
Folder *srcFolder = file->parentFolder();
|
||||
if (destinationFolder != srcFolder) {
|
||||
// Remove reference in old folder
|
||||
srcFolder->takeChild(file->id());
|
||||
// add to new Folder
|
||||
destinationFolder->addFile(file);
|
||||
}
|
||||
else {
|
||||
qDebug("Nothing to move, same destination folder");
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::saveStreamList() const
|
||||
{
|
||||
QStringList streamsUrl;
|
||||
QStringList aliases;
|
||||
FeedList streams = m_rootFolder->getAllFeeds();
|
||||
foreach (const FeedPtr &stream, streams) {
|
||||
// This backslash has nothing to do with path handling
|
||||
QString streamPath = stream->pathHierarchy().join("\\");
|
||||
if (streamPath.isNull())
|
||||
streamPath = "";
|
||||
qDebug("Saving stream path: %s", qPrintable(streamPath));
|
||||
streamsUrl << streamPath;
|
||||
aliases << stream->displayName();
|
||||
}
|
||||
Preferences *const pref = Preferences::instance();
|
||||
pref->setRssFeedsUrls(streamsUrl);
|
||||
pref->setRssFeedsAliases(aliases);
|
||||
}
|
||||
|
||||
DownloadRuleList *Manager::downloadRules() const
|
||||
{
|
||||
Q_ASSERT(m_downloadRules);
|
||||
return m_downloadRules;
|
||||
}
|
||||
|
||||
FolderPtr Manager::rootFolder() const
|
||||
{
|
||||
return m_rootFolder;
|
||||
}
|
||||
|
||||
QThread *Manager::workingThread() const
|
||||
{
|
||||
return m_workingThread;
|
||||
}
|
||||
|
||||
void Manager::refresh()
|
||||
{
|
||||
m_rootFolder->refresh();
|
||||
}
|
||||
90
src/base/rss/rssmanager.h
Normal file
90
src/base/rss/rssmanager.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSMANAGER_H
|
||||
#define RSSMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QSharedPointer>
|
||||
#include <QThread>
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class DownloadRuleList;
|
||||
class File;
|
||||
class Folder;
|
||||
class Feed;
|
||||
class Manager;
|
||||
|
||||
typedef QSharedPointer<File> FilePtr;
|
||||
typedef QSharedPointer<Folder> FolderPtr;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
|
||||
typedef QSharedPointer<Manager> ManagerPtr;
|
||||
|
||||
class Manager: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Manager(QObject *parent = 0);
|
||||
~Manager();
|
||||
|
||||
DownloadRuleList *downloadRules() const;
|
||||
FolderPtr rootFolder() const;
|
||||
QThread *workingThread() const;
|
||||
|
||||
public slots:
|
||||
void refresh();
|
||||
void loadStreamList();
|
||||
void saveStreamList() const;
|
||||
void forwardFeedContentChanged(const QString &url);
|
||||
void forwardFeedInfosChanged(const QString &url, const QString &displayName, uint unreadCount);
|
||||
void forwardFeedIconChanged(const QString &url, const QString &iconPath);
|
||||
void moveFile(const FilePtr &file, const FolderPtr &destinationFolder);
|
||||
void updateRefreshInterval(uint val);
|
||||
|
||||
signals:
|
||||
void feedContentChanged(const QString &url);
|
||||
void feedInfosChanged(const QString &url, const QString &displayName, uint unreadCount);
|
||||
void feedIconChanged(const QString &url, const QString &iconPath);
|
||||
|
||||
private:
|
||||
QTimer m_refreshTimer;
|
||||
uint m_refreshInterval;
|
||||
DownloadRuleList *m_downloadRules;
|
||||
FolderPtr m_rootFolder;
|
||||
QThread *m_workingThread;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RSSMANAGER_H
|
||||
394
src/base/scanfoldersmodel.cpp
Normal file
394
src/base/scanfoldersmodel.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christian Kandeler, Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "utils/misc.h"
|
||||
#include "utils/fs.h"
|
||||
#include "preferences.h"
|
||||
#include "logger.h"
|
||||
#include "filesystemwatcher.h"
|
||||
#include "bittorrent/session.h"
|
||||
#include "scanfoldersmodel.h"
|
||||
|
||||
struct ScanFoldersModel::PathData
|
||||
{
|
||||
PathData(const QString &watchPath, const PathType &type, const QString &downloadPath)
|
||||
: watchPath(watchPath)
|
||||
, downloadType(type)
|
||||
, downloadPath(downloadPath)
|
||||
{
|
||||
if (this->downloadPath.isEmpty() && downloadType == CUSTOM_LOCATION)
|
||||
downloadType = DEFAULT_LOCATION;
|
||||
}
|
||||
|
||||
QString watchPath;
|
||||
PathType downloadType;
|
||||
QString downloadPath; // valid for CUSTOM_LOCATION
|
||||
};
|
||||
|
||||
ScanFoldersModel *ScanFoldersModel::m_instance = 0;
|
||||
|
||||
bool ScanFoldersModel::initInstance(QObject *parent)
|
||||
{
|
||||
if (!m_instance) {
|
||||
m_instance = new ScanFoldersModel(parent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScanFoldersModel::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ScanFoldersModel *ScanFoldersModel::instance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
ScanFoldersModel::ScanFoldersModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_fsWatcher(0)
|
||||
{
|
||||
configure();
|
||||
connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
|
||||
}
|
||||
|
||||
ScanFoldersModel::~ScanFoldersModel()
|
||||
{
|
||||
qDeleteAll(m_pathList);
|
||||
}
|
||||
|
||||
int ScanFoldersModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : m_pathList.count();
|
||||
}
|
||||
|
||||
int ScanFoldersModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return NB_COLUMNS;
|
||||
}
|
||||
|
||||
QVariant ScanFoldersModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || (index.row() >= rowCount()))
|
||||
return QVariant();
|
||||
|
||||
const PathData *pathData = m_pathList.at(index.row());
|
||||
QVariant value;
|
||||
|
||||
switch (index.column()) {
|
||||
case WATCH:
|
||||
if (role == Qt::DisplayRole)
|
||||
value = Utils::Fs::toNativePath(pathData->watchPath);
|
||||
break;
|
||||
case DOWNLOAD:
|
||||
if (role == Qt::UserRole) {
|
||||
value = pathData->downloadType;
|
||||
}
|
||||
else if (role == Qt::DisplayRole) {
|
||||
switch (pathData->downloadType) {
|
||||
case DOWNLOAD_IN_WATCH_FOLDER:
|
||||
value = tr("Watch Folder");
|
||||
break;
|
||||
case DEFAULT_LOCATION:
|
||||
value = tr("Default Folder");
|
||||
break;
|
||||
case CUSTOM_LOCATION:
|
||||
value = pathData->downloadPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
QVariant ScanFoldersModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole) || (section < 0) || (section >= columnCount()))
|
||||
return QVariant();
|
||||
|
||||
QVariant title;
|
||||
|
||||
switch (section) {
|
||||
case WATCH:
|
||||
title = tr("Watched Folder");
|
||||
break;
|
||||
case DOWNLOAD:
|
||||
title = tr("Save Files to");
|
||||
break;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
Qt::ItemFlags ScanFoldersModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid() || (index.row() >= rowCount()))
|
||||
return QAbstractListModel::flags(index);
|
||||
|
||||
Qt::ItemFlags flags;
|
||||
|
||||
switch (index.column()) {
|
||||
case WATCH:
|
||||
flags = QAbstractListModel::flags(index);
|
||||
break;
|
||||
case DOWNLOAD:
|
||||
flags = QAbstractListModel::flags(index) | Qt::ItemIsEditable;
|
||||
break;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (!index.isValid() || (index.row() >= rowCount()) || (index.column() >= columnCount())
|
||||
|| (index.column() != DOWNLOAD))
|
||||
return false;
|
||||
|
||||
if (role == Qt::UserRole) {
|
||||
PathType type = static_cast<PathType>(value.toInt());
|
||||
if (type == CUSTOM_LOCATION)
|
||||
return false;
|
||||
|
||||
m_pathList[index.row()]->downloadType = type;
|
||||
m_pathList[index.row()]->downloadPath.clear();
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
else if (role == Qt::DisplayRole) {
|
||||
QString path = value.toString();
|
||||
if (path.isEmpty()) // means we didn't pass CUSTOM_LOCATION type
|
||||
return false;
|
||||
|
||||
m_pathList[index.row()]->downloadType = CUSTOM_LOCATION;
|
||||
m_pathList[index.row()]->downloadPath = Utils::Fs::toNativePath(path);
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath, const PathType &downloadType, const QString &downloadPath, bool addToFSWatcher)
|
||||
{
|
||||
QDir watchDir(watchPath);
|
||||
if (!watchDir.exists()) return DoesNotExist;
|
||||
if (!watchDir.isReadable()) return CannotRead;
|
||||
|
||||
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
||||
if (findPathData(canonicalWatchPath) != -1) return AlreadyInList;
|
||||
|
||||
QDir downloadDir(downloadPath);
|
||||
const QString &canonicalDownloadPath = downloadDir.canonicalPath();
|
||||
|
||||
if (!m_fsWatcher) {
|
||||
m_fsWatcher = new FileSystemWatcher(this);
|
||||
connect(m_fsWatcher, SIGNAL(torrentsAdded(const QStringList &)), this, SLOT(addTorrentsToSession(const QStringList &)));
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_pathList << new PathData(Utils::Fs::toNativePath(canonicalWatchPath), downloadType, Utils::Fs::toNativePath(canonicalDownloadPath));
|
||||
endInsertRows();
|
||||
|
||||
// Start scanning
|
||||
if (addToFSWatcher)
|
||||
m_fsWatcher->addPath(canonicalWatchPath);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
ScanFoldersModel::PathStatus ScanFoldersModel::updatePath(const QString &watchPath, const PathType& downloadType, const QString &downloadPath)
|
||||
{
|
||||
QDir watchDir(watchPath);
|
||||
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
||||
int row = findPathData(canonicalWatchPath);
|
||||
if (row == -1) return DoesNotExist;
|
||||
|
||||
QDir downloadDir(downloadPath);
|
||||
const QString &canonicalDownloadPath = downloadDir.canonicalPath();
|
||||
|
||||
m_pathList.at(row)->downloadType = downloadType;
|
||||
m_pathList.at(row)->downloadPath = Utils::Fs::toNativePath(canonicalDownloadPath);
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
||||
void ScanFoldersModel::addToFSWatcher(const QStringList &watchPaths)
|
||||
{
|
||||
if (!m_fsWatcher)
|
||||
return; // addPath() wasn't called before this
|
||||
|
||||
foreach (const QString &path, watchPaths) {
|
||||
QDir watchDir(path);
|
||||
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
||||
m_fsWatcher->addPath(canonicalWatchPath);
|
||||
}
|
||||
}
|
||||
|
||||
void ScanFoldersModel::removePath(int row, bool removeFromFSWatcher)
|
||||
{
|
||||
Q_ASSERT((row >= 0) && (row < rowCount()));
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
if (removeFromFSWatcher)
|
||||
m_fsWatcher->removePath(m_pathList.at(row)->watchPath);
|
||||
delete m_pathList.takeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
bool ScanFoldersModel::removePath(const QString &path, bool removeFromFSWatcher)
|
||||
{
|
||||
const int row = findPathData(path);
|
||||
if (row == -1) return false;
|
||||
|
||||
removePath(row, removeFromFSWatcher);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScanFoldersModel::removeFromFSWatcher(const QStringList &watchPaths)
|
||||
{
|
||||
foreach (const QString &path, watchPaths)
|
||||
m_fsWatcher->removePath(path);
|
||||
}
|
||||
|
||||
bool ScanFoldersModel::downloadInWatchFolder(const QString &filePath) const
|
||||
{
|
||||
const int row = findPathData(QFileInfo(filePath).dir().path());
|
||||
Q_ASSERT(row != -1);
|
||||
PathData *data = m_pathList.at(row);
|
||||
return (data->downloadType == DOWNLOAD_IN_WATCH_FOLDER);
|
||||
}
|
||||
|
||||
bool ScanFoldersModel::downloadInDefaultFolder(const QString &filePath) const
|
||||
{
|
||||
const int row = findPathData(QFileInfo(filePath).dir().path());
|
||||
Q_ASSERT(row != -1);
|
||||
PathData *data = m_pathList.at(row);
|
||||
return (data->downloadType == DEFAULT_LOCATION);
|
||||
}
|
||||
|
||||
QString ScanFoldersModel::downloadPathTorrentFolder(const QString &filePath) const
|
||||
{
|
||||
const int row = findPathData(QFileInfo(filePath).dir().path());
|
||||
Q_ASSERT(row != -1);
|
||||
PathData *data = m_pathList.at(row);
|
||||
if (data->downloadType == CUSTOM_LOCATION)
|
||||
return data->downloadPath;
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
int ScanFoldersModel::findPathData(const QString &path) const
|
||||
{
|
||||
for (int i = 0; i < m_pathList.count(); ++i)
|
||||
if (m_pathList.at(i)->watchPath == Utils::Fs::toNativePath(path))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ScanFoldersModel::makePersistent()
|
||||
{
|
||||
QVariantHash dirs;
|
||||
|
||||
foreach (const PathData *pathData, m_pathList) {
|
||||
if (pathData->downloadType == CUSTOM_LOCATION)
|
||||
dirs.insert(Utils::Fs::fromNativePath(pathData->watchPath), Utils::Fs::fromNativePath(pathData->downloadPath));
|
||||
else
|
||||
dirs.insert(Utils::Fs::fromNativePath(pathData->watchPath), pathData->downloadType);
|
||||
}
|
||||
|
||||
Preferences::instance()->setScanDirs(dirs);
|
||||
}
|
||||
|
||||
void ScanFoldersModel::configure()
|
||||
{
|
||||
QVariantHash dirs = Preferences::instance()->getScanDirs();
|
||||
|
||||
for (QVariantHash::const_iterator i = dirs.begin(), e = dirs.end(); i != e; ++i) {
|
||||
if (i.value().type() == QVariant::Int)
|
||||
addPath(i.key(), static_cast<PathType>(i.value().toInt()), QString());
|
||||
else
|
||||
addPath(i.key(), CUSTOM_LOCATION, i.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
|
||||
{
|
||||
foreach (const QString &file, pathList) {
|
||||
qDebug("File %s added", qPrintable(file));
|
||||
|
||||
BitTorrent::AddTorrentParams params;
|
||||
if (downloadInWatchFolder(file))
|
||||
params.savePath = QFileInfo(file).dir().path();
|
||||
else if (!downloadInDefaultFolder(file))
|
||||
params.savePath = downloadPathTorrentFolder(file);
|
||||
|
||||
if (file.endsWith(".magnet")) {
|
||||
QFile f(file);
|
||||
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QTextStream str(&f);
|
||||
while (!str.atEnd())
|
||||
BitTorrent::Session::instance()->addTorrent(str.readLine(), params);
|
||||
|
||||
f.close();
|
||||
Utils::Fs::forceRemove(file);
|
||||
}
|
||||
else {
|
||||
qDebug("Failed to open magnet file: %s", qPrintable(f.errorString()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
BitTorrent::TorrentInfo torrentInfo = BitTorrent::TorrentInfo::loadFromFile(file);
|
||||
if (torrentInfo.isValid()) {
|
||||
BitTorrent::Session::instance()->addTorrent(torrentInfo, params);
|
||||
Utils::Fs::forceRemove(file);
|
||||
}
|
||||
else {
|
||||
qDebug("Ignoring incomplete torrent file: %s", qPrintable(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user