qBittorrent
torrentfileswatcher.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2021 Vladimir Golovnev <[email protected]>
4  * Copyright (C) 2010 Christian Kandeler, Christophe Dumez <[email protected]>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give permission to
21  * link this program with the OpenSSL project's "OpenSSL" library (or with
22  * modified versions of it that use the same license as the "OpenSSL" library),
23  * and distribute the linked executables. You must obey the GNU General Public
24  * License in all respects for all of the code used other than "OpenSSL". If you
25  * modify file(s), you may extend this exception to your version of the file(s),
26  * but you are not obligated to do so. If you do not wish to do so, delete this
27  * exception statement from your version.
28  */
29 
30 #include "torrentfileswatcher.h"
31 
32 #include <chrono>
33 
34 #include <QtGlobal>
35 #include <QDir>
36 #include <QDirIterator>
37 #include <QFile>
38 #include <QFileSystemWatcher>
39 #include <QJsonArray>
40 #include <QJsonDocument>
41 #include <QJsonObject>
42 #include <QJsonValue>
43 #include <QSet>
44 #include <QTextStream>
45 #include <QThread>
46 #include <QTimer>
47 #include <QVariant>
48 
49 #include "base/algorithm.h"
55 #include "base/exceptions.h"
56 #include "base/global.h"
57 #include "base/logger.h"
58 #include "base/profile.h"
59 #include "base/settingsstorage.h"
60 #include "base/tagset.h"
61 #include "base/utils/fs.h"
62 #include "base/utils/io.h"
63 #include "base/utils/string.h"
64 
65 using namespace std::chrono_literals;
66 
67 const std::chrono::duration WATCH_INTERVAL = 10s;
68 const int MAX_FAILED_RETRIES = 5;
69 const QString CONF_FILE_NAME {QStringLiteral("watched_folders.json")};
70 
71 const QString OPTION_ADDTORRENTPARAMS {QStringLiteral("add_torrent_params")};
72 const QString OPTION_RECURSIVE {QStringLiteral("recursive")};
73 
74 const QString PARAM_CATEGORY {QStringLiteral("category")};
75 const QString PARAM_TAGS {QStringLiteral("tags")};
76 const QString PARAM_SAVEPATH {QStringLiteral("save_path")};
77 const QString PARAM_USEDOWNLOADPATH {QStringLiteral("use_download_path")};
78 const QString PARAM_DOWNLOADPATH {QStringLiteral("download_path")};
79 const QString PARAM_OPERATINGMODE {QStringLiteral("operating_mode")};
80 const QString PARAM_STOPPED {QStringLiteral("stopped")};
81 const QString PARAM_SKIPCHECKING {QStringLiteral("skip_checking")};
82 const QString PARAM_CONTENTLAYOUT {QStringLiteral("content_layout")};
83 const QString PARAM_AUTOTMM {QStringLiteral("use_auto_tmm")};
84 const QString PARAM_UPLOADLIMIT {QStringLiteral("upload_limit")};
85 const QString PARAM_DOWNLOADLIMIT {QStringLiteral("download_limit")};
86 const QString PARAM_SEEDINGTIMELIMIT {QStringLiteral("seeding_time_limit")};
87 const QString PARAM_RATIOLIMIT {QStringLiteral("ratio_limit")};
88 
89 namespace
90 {
91  TagSet parseTagSet(const QJsonArray &jsonArr)
92  {
93  TagSet tags;
94  for (const QJsonValue &jsonVal : jsonArr)
95  tags.insert(jsonVal.toString());
96 
97  return tags;
98  }
99 
100  QJsonArray serializeTagSet(const TagSet &tags)
101  {
102  QJsonArray arr;
103  for (const QString &tag : tags)
104  arr.append(tag);
105 
106  return arr;
107  }
108 
109  std::optional<bool> getOptionalBool(const QJsonObject &jsonObj, const QString &key)
110  {
111  const QJsonValue jsonVal = jsonObj.value(key);
112  if (jsonVal.isUndefined() || jsonVal.isNull())
113  return std::nullopt;
114 
115  return jsonVal.toBool();
116  }
117 
118  template <typename Enum>
119  std::optional<Enum> getOptionalEnum(const QJsonObject &jsonObj, const QString &key)
120  {
121  const QJsonValue jsonVal = jsonObj.value(key);
122  if (jsonVal.isUndefined() || jsonVal.isNull())
123  return std::nullopt;
124 
125  return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
126  }
127 
128  template <typename Enum>
129  Enum getEnum(const QJsonObject &jsonObj, const QString &key)
130  {
131  const QJsonValue jsonVal = jsonObj.value(key);
132  return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
133  }
134 
136  {
138  params.category = jsonObj.value(PARAM_CATEGORY).toString();
139  params.tags = parseTagSet(jsonObj.value(PARAM_TAGS).toArray());
140  params.savePath = jsonObj.value(PARAM_SAVEPATH).toString();
142  params.downloadPath = jsonObj.value(PARAM_DOWNLOADPATH).toString();
143  params.addForced = (getEnum<BitTorrent::TorrentOperatingMode>(jsonObj, PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced);
144  params.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED);
145  params.skipChecking = jsonObj.value(PARAM_SKIPCHECKING).toBool();
146  params.contentLayout = getOptionalEnum<BitTorrent::TorrentContentLayout>(jsonObj, PARAM_CONTENTLAYOUT);
147  params.useAutoTMM = getOptionalBool(jsonObj, PARAM_AUTOTMM);
148  params.uploadLimit = jsonObj.value(PARAM_UPLOADLIMIT).toInt(-1);
149  params.downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1);
151  params.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(BitTorrent::Torrent::USE_GLOBAL_RATIO);
152 
153  return params;
154  }
155 
157  {
158  QJsonObject jsonObj {
159  {PARAM_CATEGORY, params.category},
160  {PARAM_TAGS, serializeTagSet(params.tags)},
161  {PARAM_SAVEPATH, params.savePath},
164  ? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)},
166  {PARAM_UPLOADLIMIT, params.uploadLimit},
169  {PARAM_RATIOLIMIT, params.ratioLimit}
170  };
171 
172  if (params.addPaused)
173  jsonObj[PARAM_STOPPED] = *params.addPaused;
174  if (params.contentLayout)
176  if (params.useAutoTMM)
177  jsonObj[PARAM_AUTOTMM] = *params.useAutoTMM;
178  if (params.useDownloadPath)
179  jsonObj[PARAM_USEDOWNLOADPATH] = *params.useDownloadPath;
180 
181  return jsonObj;
182  }
183 
185  {
187  options.addTorrentParams = parseAddTorrentParams(jsonObj.value(OPTION_ADDTORRENTPARAMS).toObject());
188  options.recursive = jsonObj.value(OPTION_RECURSIVE).toBool();
189 
190  return options;
191  }
192 
194  {
195  return {
197  {OPTION_RECURSIVE, options.recursive}
198  };
199  }
200 }
201 
202 class TorrentFilesWatcher::Worker final : public QObject
203 {
204  Q_OBJECT
205  Q_DISABLE_COPY_MOVE(Worker)
206 
207 public:
208  Worker();
209 
210 public slots:
211  void setWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options);
212  void removeWatchedFolder(const QString &path);
213 
214 signals:
215  void magnetFound(const BitTorrent::MagnetUri &magnetURI, const BitTorrent::AddTorrentParams &addTorrentParams);
216  void torrentFound(const BitTorrent::TorrentInfo &torrentInfo, const BitTorrent::AddTorrentParams &addTorrentParams);
217 
218 private:
219  void onTimeout();
220  void scheduleWatchedFolderProcessing(const QString &path);
221  void processWatchedFolder(const QString &path);
222  void processFolder(const QString &path, const QString &watchedFolderPath, const TorrentFilesWatcher::WatchedFolderOptions &options);
223  void processFailedTorrents();
224  void addWatchedFolder(const QString &watchedFolderID, const TorrentFilesWatcher::WatchedFolderOptions &options);
225  void updateWatchedFolder(const QString &watchedFolderID, const TorrentFilesWatcher::WatchedFolderOptions &options);
226 
227  QFileSystemWatcher *m_watcher = nullptr;
228  QTimer *m_watchTimer = nullptr;
229  QHash<QString, TorrentFilesWatcher::WatchedFolderOptions> m_watchedFolders;
231 
232  // Failed torrents
233  QTimer *m_retryTorrentTimer = nullptr;
234  QHash<QString, QHash<QString, int>> m_failedTorrents;
235 };
236 
238 
240 {
241  if (!m_instance)
242  m_instance = new TorrentFilesWatcher;
243 }
244 
246 {
247  delete m_instance;
248  m_instance = nullptr;
249 }
250 
252 {
253  return m_instance;
254 }
255 
257  : QObject {parent}
258  , m_ioThread {new QThread(this)}
259  , m_asyncWorker {new TorrentFilesWatcher::Worker}
260 {
263 
264  m_asyncWorker->moveToThread(m_ioThread);
265  m_ioThread->start();
266 
267  load();
268 }
269 
271 {
272  m_ioThread->quit();
273  m_ioThread->wait();
274  delete m_asyncWorker;
275 }
276 
277 QString TorrentFilesWatcher::makeCleanPath(const QString &path)
278 {
279  if (path.isEmpty())
280  throw InvalidArgument(tr("Watched folder path cannot be empty."));
281 
282  if (QDir::isRelativePath(path))
283  throw InvalidArgument(tr("Watched folder path cannot be relative."));
284 
285  return QDir::cleanPath(path);
286 }
287 
289 {
290  QFile confFile {QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CONF_FILE_NAME)};
291  if (!confFile.exists())
292  {
293  loadLegacy();
294  return;
295  }
296 
297  if (!confFile.open(QFile::ReadOnly))
298  {
299  LogMsg(tr("Couldn't load Watched Folders configuration from %1. Error: %2")
300  .arg(confFile.fileName(), confFile.errorString()), Log::WARNING);
301  return;
302  }
303 
304  QJsonParseError jsonError;
305  const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
306  if (jsonError.error != QJsonParseError::NoError)
307  {
308  LogMsg(tr("Couldn't parse Watched Folders configuration from %1. Error: %2")
309  .arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
310  return;
311  }
312 
313  if (!jsonDoc.isObject())
314  {
315  LogMsg(tr("Couldn't load Watched Folders configuration from %1. Invalid data format.")
316  .arg(confFile.fileName()), Log::WARNING);
317  return;
318  }
319 
320  const QJsonObject jsonObj = jsonDoc.object();
321  for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
322  {
323  const QString &watchedFolder = it.key();
324  const WatchedFolderOptions options = parseWatchedFolderOptions(it.value().toObject());
325  try
326  {
327  doSetWatchedFolder(watchedFolder, options);
328  }
329  catch (const InvalidArgument &err)
330  {
331  LogMsg(err.message(), Log::WARNING);
332  }
333  }
334 }
335 
337 {
338  const auto dirs = SettingsStorage::instance()->loadValue<QVariantHash>("Preferences/Downloads/ScanDirsV2");
339 
340  for (auto i = dirs.cbegin(); i != dirs.cend(); ++i)
341  {
342  const QString watchedFolder = i.key();
344  if (i.value().type() == QVariant::Int)
345  {
346  if (i.value().toInt() == 0)
347  {
348  params.savePath = watchedFolder;
349  params.useAutoTMM = false;
350  }
351  }
352  else
353  {
354  const QString customSavePath = i.value().toString();
355  params.savePath = customSavePath;
356  params.useAutoTMM = false;
357  }
358 
359  try
360  {
361  doSetWatchedFolder(watchedFolder, {params, false});
362  }
363  catch (const InvalidArgument &err)
364  {
365  LogMsg(err.message(), Log::WARNING);
366  }
367  }
368 
369  store();
370  SettingsStorage::instance()->removeValue("Preferences/Downloads/ScanDirsV2");
371 }
372 
374 {
375  QJsonObject jsonObj;
376  for (auto it = m_watchedFolders.cbegin(); it != m_watchedFolders.cend(); ++it)
377  {
378  const QString &watchedFolder = it.key();
379  const WatchedFolderOptions &options = it.value();
380  jsonObj[watchedFolder] = serializeWatchedFolderOptions(options);
381  }
382 
383  const QString path = QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CONF_FILE_NAME);
384  const QByteArray data = QJsonDocument(jsonObj).toJson();
385  const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
386  if (!result)
387  {
388  LogMsg(tr("Couldn't store Watched Folders configuration to %1. Error: %2")
389  .arg(path, result.error()), Log::WARNING);
390  }
391 }
392 
393 QHash<QString, TorrentFilesWatcher::WatchedFolderOptions> TorrentFilesWatcher::folders() const
394 {
395  return m_watchedFolders;
396 }
397 
398 void TorrentFilesWatcher::setWatchedFolder(const QString &path, const WatchedFolderOptions &options)
399 {
400  doSetWatchedFolder(path, options);
401  store();
402 }
403 
404 void TorrentFilesWatcher::doSetWatchedFolder(const QString &path, const WatchedFolderOptions &options)
405 {
406  const QString cleanPath = makeCleanPath(path);
407  m_watchedFolders[cleanPath] = options;
408 
409  QMetaObject::invokeMethod(m_asyncWorker, [this, path, options]()
410  {
411  m_asyncWorker->setWatchedFolder(path, options);
412  });
413 
414  emit watchedFolderSet(cleanPath, options);
415 }
416 
418 {
419  const QString cleanPath = makeCleanPath(path);
420  if (m_watchedFolders.remove(cleanPath))
421  {
422  QMetaObject::invokeMethod(m_asyncWorker, [this, cleanPath]()
423  {
425  });
426 
427  emit watchedFolderRemoved(cleanPath);
428 
429  store();
430  }
431 }
432 
434  , const BitTorrent::AddTorrentParams &addTorrentParams)
435 {
436  BitTorrent::Session::instance()->addTorrent(magnetURI, addTorrentParams);
437 }
438 
440  , const BitTorrent::AddTorrentParams &addTorrentParams)
441 {
442  BitTorrent::Session::instance()->addTorrent(torrentInfo, addTorrentParams);
443 }
444 
446  : m_watcher {new QFileSystemWatcher(this)}
447  , m_watchTimer {new QTimer(this)}
448  , m_retryTorrentTimer {new QTimer(this)}
449 {
450  connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &Worker::scheduleWatchedFolderProcessing);
451  connect(m_watchTimer, &QTimer::timeout, this, &Worker::onTimeout);
452 
453  connect(m_retryTorrentTimer, &QTimer::timeout, this, &Worker::processFailedTorrents);
454 }
455 
457 {
458  for (const QString &path : asConst(m_watchedByTimeoutFolders))
459  processWatchedFolder(path);
460 }
461 
463 {
464  if (m_watchedFolders.contains(path))
465  updateWatchedFolder(path, options);
466  else
467  addWatchedFolder(path, options);
468 }
469 
471 {
472  m_watchedFolders.remove(path);
473 
474  m_watcher->removePath(path);
475  m_watchedByTimeoutFolders.remove(path);
476  if (m_watchedByTimeoutFolders.isEmpty())
477  m_watchTimer->stop();
478 
479  m_failedTorrents.remove(path);
480  if (m_failedTorrents.isEmpty())
481  m_retryTorrentTimer->stop();
482 }
483 
485 {
486  QTimer::singleShot(2000, this, [this, path]()
487  {
488  processWatchedFolder(path);
489  });
490 }
491 
493 {
495  processFolder(path, path, options);
496 
497  if (!m_failedTorrents.empty() && !m_retryTorrentTimer->isActive())
498  m_retryTorrentTimer->start(WATCH_INTERVAL);
499 }
500 
501 void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QString &watchedFolderPath
503 {
504  const QDir watchedDir {watchedFolderPath};
505 
506  QDirIterator dirIter {path, {"*.torrent", "*.magnet"}, QDir::Files};
507  while (dirIter.hasNext())
508  {
509  const QString filePath = dirIter.next();
510  BitTorrent::AddTorrentParams addTorrentParams = options.addTorrentParams;
511  if (path != watchedFolderPath)
512  {
513  const QString subdirPath = watchedDir.relativeFilePath(path);
514  if (addTorrentParams.useAutoTMM)
515  {
516  addTorrentParams.category = addTorrentParams.category.isEmpty()
517  ? subdirPath : (addTorrentParams.category + QLatin1Char('/') + subdirPath);
518  }
519  else
520  {
521  addTorrentParams.savePath = QDir::cleanPath(QDir(addTorrentParams.savePath).filePath(subdirPath));
522  }
523  }
524 
525  if (filePath.endsWith(QLatin1String(".magnet"), Qt::CaseInsensitive))
526  {
527  QFile file {filePath};
528  if (file.open(QIODevice::ReadOnly | QIODevice::Text))
529  {
530  QTextStream str {&file};
531  while (!str.atEnd())
532  emit magnetFound(BitTorrent::MagnetUri(str.readLine()), addTorrentParams);
533 
534  file.close();
535  Utils::Fs::forceRemove(filePath);
536  }
537  else
538  {
539  LogMsg(tr("Failed to open magnet file: %1").arg(file.errorString()));
540  }
541  }
542  else
543  {
544  const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::loadFromFile(filePath);
545  if (result)
546  {
547  emit torrentFound(result.value(), addTorrentParams);
548  Utils::Fs::forceRemove(filePath);
549  }
550  else
551  {
552  if (!m_failedTorrents.value(path).contains(filePath))
553  {
554  m_failedTorrents[path][filePath] = 0;
555  }
556  }
557  }
558  }
559 
560  if (options.recursive)
561  {
562  QDirIterator dirIter {path, (QDir::Dirs | QDir::NoDot | QDir::NoDotDot)};
563  while (dirIter.hasNext())
564  {
565  const QString folderPath = dirIter.next();
566  // Skip processing of subdirectory that is explicitly set as watched folder
567  if (!m_watchedFolders.contains(folderPath))
568  processFolder(folderPath, watchedFolderPath, options);
569  }
570  }
571 }
572 
574 {
575  // Check which torrents are still partial
576  Algorithm::removeIf(m_failedTorrents, [this](const QString &watchedFolderPath, QHash<QString, int> &partialTorrents)
577  {
578  const QDir dir {watchedFolderPath};
579  const TorrentFilesWatcher::WatchedFolderOptions options = m_watchedFolders.value(watchedFolderPath);
580  Algorithm::removeIf(partialTorrents, [this, &dir, &options](const QString &torrentPath, int &value)
581  {
582  if (!QFile::exists(torrentPath))
583  return true;
584 
585  const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::loadFromFile(torrentPath);
586  if (result)
587  {
588  BitTorrent::AddTorrentParams addTorrentParams = options.addTorrentParams;
589  const QString exactDirPath = QFileInfo(torrentPath).canonicalPath();
590  if (exactDirPath != dir.path())
591  {
592  const QString subdirPath = dir.relativeFilePath(exactDirPath);
593  if (addTorrentParams.useAutoTMM)
594  {
595  addTorrentParams.category = addTorrentParams.category.isEmpty()
596  ? subdirPath : (addTorrentParams.category + QLatin1Char('/') + subdirPath);
597  }
598  else
599  {
600  addTorrentParams.savePath = QDir(addTorrentParams.savePath).filePath(subdirPath);
601  }
602  }
603 
604  emit torrentFound(result.value(), addTorrentParams);
605  Utils::Fs::forceRemove(torrentPath);
606 
607  return true;
608  }
609 
610  if (value >= MAX_FAILED_RETRIES)
611  {
612  LogMsg(tr("Rejecting failed torrent file: %1").arg(torrentPath));
613  QFile::rename(torrentPath, torrentPath + ".qbt_rejected");
614  return true;
615  }
616 
617  ++value;
618  return false;
619  });
620 
621  if (partialTorrents.isEmpty())
622  return true;
623 
624  return false;
625  });
626 
627  // Stop the partial timer if necessary
628  if (m_failedTorrents.empty())
629  m_retryTorrentTimer->stop();
630  else
631  m_retryTorrentTimer->start(WATCH_INTERVAL);
632 }
633 
635 {
636 #if !defined Q_OS_HAIKU
637  // Check if the path points to a network file system or not
638  if (Utils::Fs::isNetworkFileSystem(path) || options.recursive)
639 #else
640  if (options.recursive)
641 #endif
642  {
643  m_watchedByTimeoutFolders.insert(path);
644  if (!m_watchTimer->isActive())
645  m_watchTimer->start(WATCH_INTERVAL);
646  }
647  else
648  {
649  m_watcher->addPath(path);
650  scheduleWatchedFolderProcessing(path);
651  }
652 
653  m_watchedFolders[path] = options;
654 
655  LogMsg(tr("Watching folder: \"%1\"").arg(Utils::Fs::toNativePath(path)));
656 }
657 
659 {
660  const bool recursiveModeChanged = (m_watchedFolders[path].recursive != options.recursive);
661 #if !defined Q_OS_HAIKU
662  if (recursiveModeChanged && !Utils::Fs::isNetworkFileSystem(path))
663 #else
664  if (recursiveModeChanged)
665 #endif
666  {
667  if (options.recursive)
668  {
669  m_watcher->removePath(path);
670 
671  m_watchedByTimeoutFolders.insert(path);
672  if (!m_watchTimer->isActive())
673  m_watchTimer->start(WATCH_INTERVAL);
674  }
675  else
676  {
677  m_watchedByTimeoutFolders.remove(path);
678  if (m_watchedByTimeoutFolders.isEmpty())
679  m_watchTimer->stop();
680 
681  m_watcher->addPath(path);
682  scheduleWatchedFolderProcessing(path);
683  }
684  }
685 
686  m_watchedFolders[path] = options;
687 }
688 
689 #include "torrentfileswatcher.moc"
static Session * instance()
Definition: session.cpp:997
bool addTorrent(const QString &source, const AddTorrentParams &params=AddTorrentParams())
Definition: session.cpp:2007
static const int USE_GLOBAL_SEEDING_TIME
Definition: torrent.h:107
static const qreal USE_GLOBAL_RATIO
Definition: torrent.h:104
static nonstd::expected< TorrentInfo, QString > loadFromFile(const QString &path) noexcept
QString message() const noexcept
Definition: exceptions.cpp:36
T loadValue(const QString &key, const T &defaultValue={}) const
static SettingsStorage * instance()
void removeValue(const QString &key)
void processFolder(const QString &path, const QString &watchedFolderPath, const TorrentFilesWatcher::WatchedFolderOptions &options)
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)
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)
void removeWatchedFolder(const QString &path)
TorrentFilesWatcher(QObject *parent=nullptr)
void watchedFolderRemoved(const QString &path)
QHash< QString, WatchedFolderOptions > folders() const
void onTorrentFound(const BitTorrent::TorrentInfo &torrentInfo, const BitTorrent::AddTorrentParams &addTorrentParams)
QHash< QString, WatchedFolderOptions > m_watchedFolders
static TorrentFilesWatcher * instance()
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
Definition: global.h:42
void LogMsg(const QString &message, const Log::MsgType &type)
Definition: logger.cpp:125
void removeIf(T &dict, BinaryPredicate &&p)
Definition: algorithm.h:50
@ WARNING
Definition: logger.h:47
bool isNetworkFileSystem(const QString &path)
Definition: fs.cpp:337
bool forceRemove(const QString &filePath)
Definition: fs.cpp:173
QString toNativePath(const QString &path)
Definition: fs.cpp:64
nonstd::expected< void, QString > saveToFile(const QString &path, const QByteArray &data)
Definition: io.cpp:69
QString fromEnum(const T &value)
Definition: string.h:67
T value(const QString &key, const T &defaultValue={})
Definition: preferences.cpp:64
BitTorrent::AddTorrentParams parseAddTorrentParams(const QJsonObject &jsonObj)
QJsonObject serializeAddTorrentParams(const BitTorrent::AddTorrentParams &params)
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)
QString specialFolderLocation(const SpecialFolder folder)
Definition: profile.cpp:131
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") set_source_files_properties($
Definition: CMakeLists.txt:5
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 PARAM_TAGS
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