36 #include <QDirIterator>
38 #include <QFileSystemWatcher>
40 #include <QJsonDocument>
41 #include <QJsonObject>
44 #include <QTextStream>
65 using namespace std::chrono_literals;
94 for (
const QJsonValue &jsonVal : jsonArr)
95 tags.insert(jsonVal.toString());
103 for (
const QString &tag : tags)
111 const QJsonValue jsonVal = jsonObj.value(key);
112 if (jsonVal.isUndefined() || jsonVal.isNull())
115 return jsonVal.toBool();
118 template <
typename Enum>
121 const QJsonValue jsonVal = jsonObj.value(key);
122 if (jsonVal.isUndefined() || jsonVal.isNull())
125 return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
128 template <
typename Enum>
129 Enum
getEnum(
const QJsonObject &jsonObj,
const QString &key)
131 const QJsonValue jsonVal = jsonObj.value(key);
132 return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
143 params.
addForced = (getEnum<BitTorrent::TorrentOperatingMode>(jsonObj,
PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced);
158 QJsonObject jsonObj {
164 ? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)},
205 Q_DISABLE_COPY_MOVE(
Worker)
212 void removeWatchedFolder(
const QString &path);
220 void scheduleWatchedFolderProcessing(
const QString &path);
221 void processWatchedFolder(
const QString &path);
223 void processFailedTorrents();
227 QFileSystemWatcher *m_watcher =
nullptr;
228 QTimer *m_watchTimer =
nullptr;
233 QTimer *m_retryTorrentTimer =
nullptr;
248 m_instance =
nullptr;
258 , m_ioThread {new QThread(this)}
282 if (QDir::isRelativePath(path))
285 return QDir::cleanPath(path);
291 if (!confFile.exists())
297 if (!confFile.open(QFile::ReadOnly))
299 LogMsg(tr(
"Couldn't load Watched Folders configuration from %1. Error: %2")
300 .arg(confFile.fileName(), confFile.errorString()),
Log::WARNING);
304 QJsonParseError jsonError;
305 const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
306 if (jsonError.error != QJsonParseError::NoError)
308 LogMsg(tr(
"Couldn't parse Watched Folders configuration from %1. Error: %2")
309 .arg(confFile.fileName(), jsonError.errorString()),
Log::WARNING);
313 if (!jsonDoc.isObject())
315 LogMsg(tr(
"Couldn't load Watched Folders configuration from %1. Invalid data format.")
320 const QJsonObject jsonObj = jsonDoc.object();
321 for (
auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
323 const QString &watchedFolder = it.key();
340 for (
auto i = dirs.cbegin(); i != dirs.cend(); ++i)
342 const QString watchedFolder = i.key();
344 if (i.value().type() == QVariant::Int)
346 if (i.value().toInt() == 0)
354 const QString customSavePath = i.value().toString();
378 const QString &watchedFolder = it.key();
384 const QByteArray data = QJsonDocument(jsonObj).toJson();
388 LogMsg(tr(
"Couldn't store Watched Folders configuration to %1. Error: %2")
409 QMetaObject::invokeMethod(
m_asyncWorker, [
this, path, options]()
446 : m_watcher {new QFileSystemWatcher(this)}
447 , m_watchTimer {new QTimer(this)}
448 , m_retryTorrentTimer {new QTimer(this)}
458 for (
const QString &path :
asConst(m_watchedByTimeoutFolders))
459 processWatchedFolder(path);
465 updateWatchedFolder(path, options);
467 addWatchedFolder(path, options);
474 m_watcher->removePath(path);
475 m_watchedByTimeoutFolders.remove(path);
476 if (m_watchedByTimeoutFolders.isEmpty())
477 m_watchTimer->stop();
479 m_failedTorrents.remove(path);
480 if (m_failedTorrents.isEmpty())
481 m_retryTorrentTimer->stop();
486 QTimer::singleShot(2000,
this, [
this, path]()
488 processWatchedFolder(path);
495 processFolder(path, path, options);
497 if (!m_failedTorrents.empty() && !m_retryTorrentTimer->isActive())
504 const QDir watchedDir {watchedFolderPath};
506 QDirIterator dirIter {path, {
"*.torrent",
"*.magnet"}, QDir::Files};
507 while (dirIter.hasNext())
509 const QString filePath = dirIter.next();
511 if (path != watchedFolderPath)
513 const QString subdirPath = watchedDir.relativeFilePath(path);
517 ? subdirPath : (addTorrentParams.
category + QLatin1Char(
'/') + subdirPath);
521 addTorrentParams.
savePath = QDir::cleanPath(QDir(addTorrentParams.
savePath).filePath(subdirPath));
525 if (filePath.endsWith(QLatin1String(
".magnet"), Qt::CaseInsensitive))
527 QFile
file {filePath};
528 if (
file.open(QIODevice::ReadOnly | QIODevice::Text))
530 QTextStream str {&
file};
539 LogMsg(tr(
"Failed to open magnet file: %1").arg(
file.errorString()));
547 emit torrentFound(result.value(), addTorrentParams);
552 if (!m_failedTorrents.value(path).contains(filePath))
554 m_failedTorrents[path][filePath] = 0;
562 QDirIterator dirIter {path, (QDir::Dirs | QDir::NoDot | QDir::NoDotDot)};
563 while (dirIter.hasNext())
565 const QString folderPath = dirIter.next();
568 processFolder(folderPath, watchedFolderPath, options);
576 Algorithm::removeIf(m_failedTorrents, [
this](
const QString &watchedFolderPath, QHash<QString, int> &partialTorrents)
578 const QDir dir {watchedFolderPath};
582 if (!QFile::exists(torrentPath))
589 const QString exactDirPath = QFileInfo(torrentPath).canonicalPath();
590 if (exactDirPath != dir.path())
592 const QString subdirPath = dir.relativeFilePath(exactDirPath);
596 ? subdirPath : (addTorrentParams.
category + QLatin1Char(
'/') + subdirPath);
600 addTorrentParams.
savePath = QDir(addTorrentParams.
savePath).filePath(subdirPath);
604 emit torrentFound(result.value(), addTorrentParams);
612 LogMsg(tr(
"Rejecting failed torrent file: %1").arg(torrentPath));
613 QFile::rename(torrentPath, torrentPath +
".qbt_rejected");
621 if (partialTorrents.isEmpty())
628 if (m_failedTorrents.empty())
629 m_retryTorrentTimer->stop();
636 #if !defined Q_OS_HAIKU
643 m_watchedByTimeoutFolders.insert(path);
644 if (!m_watchTimer->isActive())
649 m_watcher->addPath(path);
650 scheduleWatchedFolderProcessing(path);
661 #if !defined Q_OS_HAIKU
664 if (recursiveModeChanged)
669 m_watcher->removePath(path);
671 m_watchedByTimeoutFolders.insert(path);
672 if (!m_watchTimer->isActive())
677 m_watchedByTimeoutFolders.remove(path);
678 if (m_watchedByTimeoutFolders.isEmpty())
679 m_watchTimer->stop();
681 m_watcher->addPath(path);
682 scheduleWatchedFolderProcessing(path);
689 #include "torrentfileswatcher.moc"
static Session * instance()
bool addTorrent(const QString &source, const AddTorrentParams ¶ms=AddTorrentParams())
static const int USE_GLOBAL_SEEDING_TIME
static const qreal USE_GLOBAL_RATIO
static nonstd::expected< TorrentInfo, QString > loadFromFile(const QString &path) noexcept
QString message() const noexcept
T loadValue(const QString &key, const T &defaultValue={}) const
static SettingsStorage * instance()
void removeValue(const QString &key)
QFileSystemWatcher * m_watcher
QSet< QString > m_watchedByTimeoutFolders
void processFolder(const QString &path, const QString &watchedFolderPath, const TorrentFilesWatcher::WatchedFolderOptions &options)
void processFailedTorrents()
QHash< QString, TorrentFilesWatcher::WatchedFolderOptions > m_watchedFolders
void magnetFound(const BitTorrent::MagnetUri &magnetURI, const BitTorrent::AddTorrentParams &addTorrentParams)
void torrentFound(const BitTorrent::TorrentInfo &torrentInfo, const BitTorrent::AddTorrentParams &addTorrentParams)
QTimer * m_retryTorrentTimer
void removeWatchedFolder(const QString &path)
void processWatchedFolder(const QString &path)
QHash< QString, QHash< QString, int > > m_failedTorrents
void scheduleWatchedFolderProcessing(const QString &path)
void setWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options)
void updateWatchedFolder(const QString &watchedFolderID, const TorrentFilesWatcher::WatchedFolderOptions &options)
void addWatchedFolder(const QString &watchedFolderID, const TorrentFilesWatcher::WatchedFolderOptions &options)
static void initInstance()
void removeWatchedFolder(const QString &path)
TorrentFilesWatcher(QObject *parent=nullptr)
void watchedFolderRemoved(const QString &path)
static void freeInstance()
QHash< QString, WatchedFolderOptions > folders() const
void onTorrentFound(const BitTorrent::TorrentInfo &torrentInfo, const BitTorrent::AddTorrentParams &addTorrentParams)
QHash< QString, WatchedFolderOptions > m_watchedFolders
static TorrentFilesWatcher * instance()
~TorrentFilesWatcher() override
static QString makeCleanPath(const QString &path)
void doSetWatchedFolder(const QString &path, const WatchedFolderOptions &options)
static TorrentFilesWatcher * m_instance
void onMagnetFound(const BitTorrent::MagnetUri &magnetURI, const BitTorrent::AddTorrentParams &addTorrentParams)
void setWatchedFolder(const QString &path, const WatchedFolderOptions &options)
void watchedFolderSet(const QString &path, const WatchedFolderOptions &options)
constexpr std::add_const_t< T > & asConst(T &t) noexcept
void LogMsg(const QString &message, const Log::MsgType &type)
void removeIf(T &dict, BinaryPredicate &&p)
bool isNetworkFileSystem(const QString &path)
bool forceRemove(const QString &filePath)
QString toNativePath(const QString &path)
nonstd::expected< void, QString > saveToFile(const QString &path, const QByteArray &data)
QString fromEnum(const T &value)
T value(const QString &key, const T &defaultValue={})
BitTorrent::AddTorrentParams parseAddTorrentParams(const QJsonObject &jsonObj)
QJsonObject serializeAddTorrentParams(const BitTorrent::AddTorrentParams ¶ms)
std::optional< Enum > getOptionalEnum(const QJsonObject &jsonObj, const QString &key)
TagSet parseTagSet(const QJsonArray &jsonArr)
Enum getEnum(const QJsonObject &jsonObj, const QString &key)
QJsonObject serializeWatchedFolderOptions(const TorrentFilesWatcher::WatchedFolderOptions &options)
TorrentFilesWatcher::WatchedFolderOptions parseWatchedFolderOptions(const QJsonObject &jsonObj)
std::optional< bool > getOptionalBool(const QJsonObject &jsonObj, const QString &key)
QJsonArray serializeTagSet(const TagSet &tags)
QString specialFolderLocation(const SpecialFolder folder)
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") set_source_files_properties($
std::optional< bool > useDownloadPath
std::optional< bool > useAutoTMM
std::optional< bool > addPaused
std::optional< BitTorrent::TorrentContentLayout > contentLayout
BitTorrent::AddTorrentParams addTorrentParams
const QString PARAM_DOWNLOADPATH
const QString PARAM_AUTOTMM
const QString PARAM_DOWNLOADLIMIT
const QString PARAM_RATIOLIMIT
const QString PARAM_CONTENTLAYOUT
const QString PARAM_SKIPCHECKING
const QString PARAM_STOPPED
const QString OPTION_ADDTORRENTPARAMS
const QString PARAM_CATEGORY
const int MAX_FAILED_RETRIES
const std::chrono::duration WATCH_INTERVAL
const QString OPTION_RECURSIVE
const QString PARAM_SAVEPATH
const QString PARAM_SEEDINGTIMELIMIT
const QString PARAM_USEDOWNLOADPATH
const QString PARAM_UPLOADLIMIT
const QString PARAM_OPERATINGMODE
const QString CONF_FILE_NAME