From f940b863f1b3d9b163e31238d37f622870f76c35 Mon Sep 17 00:00:00 2001 From: Radu Carpa Date: Fri, 25 Aug 2023 14:47:22 +0200 Subject: [PATCH 1/2] Enable torrent creation via the api Implement a new api controller to handle generation of .torrent files. Add a new TorrentCreationManager singleton which keeps tracks of torrent creation tasks. Use a separate ThreadPool in the Manager. It acts as a queueing mechanism for creation tasks to avoid too many tasks running in parallel. Slightly adapt the TorrentCreator for the new needs: allow to return the content of the generated .torrent file without requiring to write it to disk at all. If no savePath is passed, the created torrent file will be kept in memory. Also, communicate the actual pieceSize used for generation (if it's set to 0, libtorrent selects it automatically). By default, the created torrents will be added to the session. This behavior can be disabled by setting `startSeeding = false`. The maximum number of tasks in the manager is bounded by a configuration value. If this limit is reached, the manager will automatically remove the oldest completed task. If no such task exists (all tasks are pending or active), an error will be returned to the user. Closes #5614. --- src/base/CMakeLists.txt | 2 + .../bittorrent/torrentcreationmanager.cpp | 264 ++++++++++++++++++ src/base/bittorrent/torrentcreationmanager.h | 135 +++++++++ src/base/bittorrent/torrentcreator.cpp | 47 +++- src/base/bittorrent/torrentcreator.h | 13 +- src/gui/torrentcreatordialog.cpp | 19 +- src/gui/torrentcreatordialog.h | 2 +- src/webui/CMakeLists.txt | 2 + src/webui/api/metafilecontroller.cpp | 208 ++++++++++++++ src/webui/api/metafilecontroller.h | 50 ++++ src/webui/webapplication.cpp | 2 + src/webui/webapplication.h | 4 + 12 files changed, 725 insertions(+), 23 deletions(-) create mode 100644 src/base/bittorrent/torrentcreationmanager.cpp create mode 100644 src/base/bittorrent/torrentcreationmanager.h create mode 100644 src/webui/api/metafilecontroller.cpp create mode 100644 src/webui/api/metafilecontroller.h diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 854bd5b38bcc..d706ed0d60f3 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(qbt_base STATIC bittorrent/torrent.h bittorrent/torrentcontenthandler.h bittorrent/torrentcontentlayout.h + bittorrent/torrentcreationmanager.h bittorrent/torrentcreator.h bittorrent/torrentdescriptor.h bittorrent/torrentimpl.h @@ -137,6 +138,7 @@ add_library(qbt_base STATIC bittorrent/speedmonitor.cpp bittorrent/torrent.cpp bittorrent/torrentcontenthandler.cpp + bittorrent/torrentcreationmanager.cpp bittorrent/torrentcreator.cpp bittorrent/torrentdescriptor.cpp bittorrent/torrentimpl.cpp diff --git a/src/base/bittorrent/torrentcreationmanager.cpp b/src/base/bittorrent/torrentcreationmanager.cpp new file mode 100644 index 000000000000..12ca7903abca --- /dev/null +++ b/src/base/bittorrent/torrentcreationmanager.cpp @@ -0,0 +1,264 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015, 2018 Vladimir Golovnev + * Copyright (C) 2006 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. + */ + +#include "torrentcreationmanager.h" + +#include +#include +#include +#include + +#define SETTINGS_KEY(name) u"TorrentCreator/Manager/" name + +using namespace BitTorrent; + +QPointer TorrentCreationManager::m_instance = nullptr; + +TorrentCreationTask::TorrentCreationTask(const QString &id, const BitTorrent::TorrentCreatorParams ¶ms, QObject *parent) + : QObject(parent) + , m_id(id) + , m_params(params) +{ + m_timeAdded = QDateTime::currentDateTime(); +} + +void TorrentCreationTask::setProgress(int progress) +{ + if (m_timeStarted.isNull()) + m_timeStarted = QDateTime::currentDateTime(); + m_progress = progress; +} + +bool TorrentCreationTask::isDone() const +{ + return !m_timeDone.isNull(); +} + +QDateTime TorrentCreationTask::timeAdded() const +{ + return m_timeAdded; +} + +QDateTime TorrentCreationTask::timeStarted() const +{ + return m_timeStarted; +} + +QDateTime TorrentCreationTask::timeDone() const +{ + return m_timeDone; +} + +void TorrentCreationTask::setDone() +{ + if (m_timeStarted.isNull()) + m_timeStarted = QDateTime::currentDateTime(); + m_timeDone = QDateTime::currentDateTime(); +} + +bool TorrentCreationTask::isDoneWithSuccess() const +{ + return !m_timeDone.isNull() && (!m_result.content.isEmpty() || !m_result.path.isEmpty()); +} + +bool TorrentCreationTask::isDoneWithError() const +{ + return !m_timeDone.isNull() && !m_errorMsg.isEmpty(); +} + +bool TorrentCreationTask::isRunning() const +{ + return !m_timeStarted.isNull() && m_timeDone.isNull(); +} + +QString TorrentCreationTask::id() const +{ + return m_id; +} + +QByteArray TorrentCreationTask::content() const +{ + if (!isDoneWithSuccess()) + return QByteArray {}; + + if (!m_result.content.isEmpty()) + return m_result.content; + + QFile file(m_result.path.toString()); + if(!file.open(QIODevice::ReadOnly)) + return QByteArray {}; + + return file.readAll(); +} + +const BitTorrent::TorrentCreatorParams &TorrentCreationTask::params() const +{ + return m_params; +} + +int TorrentCreationTask::progress() const +{ + return m_progress; +} + +QString TorrentCreationTask::errorMsg() const +{ + return m_errorMsg; +} + + +TorrentCreationManager::TorrentCreationManager() + : m_maxTasks(SETTINGS_KEY(u"MaxTasks"_s), 256) + , m_numThreads(SETTINGS_KEY(u"NumThreads"_s), 0) + , m_threadPool {this} +{ + Q_ASSERT(!m_instance); // only one instance is allowed + m_instance = this; + + if (m_numThreads > 0) + m_threadPool.setMaxThreadCount(m_numThreads); +} + +TorrentCreationManager::~TorrentCreationManager() +{ + qDeleteAll(m_tasks); +} + +TorrentCreationManager *TorrentCreationManager::instance() +{ + if (!m_instance) + m_instance = new TorrentCreationManager; + return m_instance; +} + +void TorrentCreationManager::freeInstance() +{ + delete m_instance; +} + +QString TorrentCreationManager::createTask(const TorrentCreatorParams ¶ms, bool startSeeding) +{ + if (m_tasks.size() >= m_maxTasks) + { + // Try to delete old finished tasks to stay under target + std::pair p = m_tasks.get().equal_range(std::make_tuple(true)); + while(p.first != p.second && m_tasks.size() >= m_maxTasks) + { + TorrentCreationTask *task = *p.first; + p.first = m_tasks.get().erase(p.first); + delete task; + } + } + if (m_tasks.size() >= m_maxTasks) + return {}; + + QString taskId = QUuid::createUuid().toString(QUuid::WithoutBraces); + while (m_tasks.get().find(taskId) != m_tasks.get().end()) + taskId = QUuid::createUuid().toString(QUuid::WithoutBraces); + + auto *torrentCreator = new TorrentCreator(params, this); + auto *creationTask = new TorrentCreationTask(taskId, params, this); + + auto onProgress = [creationTask](int progress) + { + creationTask->setProgress(progress); + }; + auto onSuccess = [this, startSeeding, taskId](const TorrentCreatorResult &result) + { + markSuccess(taskId, result); + if (startSeeding) + result.startSeeding(false); + }; + auto onFailure = [this, taskId]([[maybe_unused]] const QString &msg) + { + markFailed(taskId, msg); + }; + connect(torrentCreator, &BitTorrent::TorrentCreator::creationSuccess, this, onSuccess); + connect(torrentCreator, &BitTorrent::TorrentCreator::creationFailure, this, onFailure); + connect(torrentCreator, &BitTorrent::TorrentCreator::updateProgress, creationTask, onProgress); + connect(creationTask, &QObject::destroyed, torrentCreator, &BitTorrent::TorrentCreator::requestInterruption); + + m_tasks.get().insert(creationTask); + m_threadPool.start(torrentCreator); + return taskId; +} + +QStringList TorrentCreationManager::taskIds() const +{ + QStringList ids; + ids.reserve(m_tasks.get().size()); + for (TorrentCreationTask * task : m_tasks.get()) + ids << task->id(); + return ids; +} + +TorrentCreationTask *TorrentCreationManager::getTask(const QString &id) const +{ + const auto iter = m_tasks.get().find(id); + if (iter == m_tasks.get().end()) + return nullptr; + + return *iter; +} + +bool TorrentCreationManager::deleteTask(const QString &id) +{ + const auto iter = m_tasks.get().find(id); + if (iter == m_tasks.get().end()) + return false; + + TorrentCreationTask *task = *iter; + m_tasks.get().erase(iter); + delete task; + return true; +} + +void TorrentCreationManager::markFailed(const QString &id, const QString &msg) +{ + + const auto iter = m_tasks.get().find(id); + if (iter != m_tasks.get().end()){ + m_tasks.get().modify(m_tasks.project(iter), + [](TorrentCreationTask *task) { task->setDone(); }); + TorrentCreationTask *task = *iter; + task->m_errorMsg = msg; + } +} + +void TorrentCreationManager::markSuccess(const QString &id, const BitTorrent::TorrentCreatorResult &result) +{ + const auto iter = m_tasks.get().find(id); + if (iter != m_tasks.get().end()){ + m_tasks.get().modify(m_tasks.project(iter), + [](TorrentCreationTask *task) { task->setDone(); }); + TorrentCreationTask *task = *iter; + task->m_result = result; + task->m_params.pieceSize = result.pieceSize; + } +} diff --git a/src/base/bittorrent/torrentcreationmanager.h b/src/base/bittorrent/torrentcreationmanager.h new file mode 100644 index 000000000000..7ad5449edfbc --- /dev/null +++ b/src/base/bittorrent/torrentcreationmanager.h @@ -0,0 +1,135 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015, 2018 Vladimir Golovnev + * Copyright (C) 2006 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/settingvalue.h" +#include "torrentcreator.h" + + +namespace BitTorrent +{ + + class TorrentCreationTask : public QObject + { + Q_OBJECT + Q_DISABLE_COPY_MOVE(TorrentCreationTask) + + friend class TorrentCreationManager; + public: + TorrentCreationTask(const QString &id, const TorrentCreatorParams ¶ms, QObject *parent = nullptr); + QString id() const; + QByteArray content() const; + QString errorMsg() const; + const TorrentCreatorParams ¶ms() const; + void setProgress(int progress); + int progress() const; + void setDone(); + bool isDone() const; + QDateTime timeAdded() const; + QDateTime timeStarted() const; + QDateTime timeDone() const; + + bool isDoneWithSuccess() const; + bool isDoneWithError() const; + bool isRunning() const; + + private: + QString m_id; + TorrentCreatorParams m_params; + int m_progress = 0; + QDateTime m_timeAdded; + QDateTime m_timeStarted; + QDateTime m_timeDone; + + TorrentCreatorResult m_result; + QString m_errorMsg; + }; + + class TorrentCreationManager final : public QObject + { + Q_OBJECT + Q_DISABLE_COPY_MOVE(TorrentCreationManager) + + public: + TorrentCreationManager(); + ~TorrentCreationManager() override; + static TorrentCreationManager *instance(); + static void freeInstance(); + QString createTask(const TorrentCreatorParams ¶ms, bool startSeeding = true); + TorrentCreationTask* getTask(const QString &id) const; + bool deleteTask(const QString &id); + QStringList taskIds() const; + + private: + static QPointer m_instance; + + struct ById{}; + struct ByCompletion{}; + using TasksSet = boost::multi_index_container< + TorrentCreationTask *, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique< + boost::multi_index::tag, + boost::multi_index::const_mem_fun>, + boost::multi_index::ordered_non_unique< + boost::multi_index::tag, + boost::multi_index::composite_key< + TorrentCreationTask *, + boost::multi_index::const_mem_fun, + boost::multi_index::const_mem_fun + > + > + > + >; + using TaskSetById = TasksSet::index::type; + using TaskSetByCompletion = TasksSet::index::type; + + CachedSettingValue m_maxTasks; + CachedSettingValue m_numThreads; + + TasksSet m_tasks; + QThreadPool m_threadPool; + + void markFailed(const QString &id, const QString &msg); + void markSuccess(const QString &id, const TorrentCreatorResult &result); + }; +} diff --git a/src/base/bittorrent/torrentcreator.cpp b/src/base/bittorrent/torrentcreator.cpp index bdc0128697f2..d221ed570c9d 100644 --- a/src/base/bittorrent/torrentcreator.cpp +++ b/src/base/bittorrent/torrentcreator.cpp @@ -38,6 +38,7 @@ #include #include +#include "addtorrentparams.h" #include "base/exceptions.h" #include "base/global.h" #include "base/utils/compare.h" @@ -45,6 +46,7 @@ #include "base/utils/io.h" #include "base/version.h" #include "lttypecast.h" +#include "session.h" namespace { @@ -74,6 +76,30 @@ namespace using namespace BitTorrent; +nonstd::expected +TorrentCreatorResult::startSeeding(bool ignoreShareLimits) const +{ + const auto loadResult = content.isEmpty() + ? TorrentDescriptor::loadFromFile(path) + : TorrentDescriptor::load(content); + if (!loadResult) + return loadResult; + + BitTorrent::AddTorrentParams params; + params.savePath = branchPath; + params.skipChecking = true; + if (ignoreShareLimits) + { + params.ratioLimit = BitTorrent::Torrent::NO_RATIO_LIMIT; + params.seedingTimeLimit = BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT; + params.inactiveSeedingTimeLimit = BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT; + } + params.useAutoTMM = false; // otherwise if it is on by default, it will overwrite `savePath` to the default save path + + Session::instance()->addTorrent(loadResult.value(), params); + return loadResult; +} + TorrentCreator::TorrentCreator(const TorrentCreatorParams ¶ms, QObject *parent) : QObject(parent) , m_params {params} @@ -205,13 +231,24 @@ void TorrentCreator::run() checkInterruptionRequested(); - // create the torrent - const nonstd::expected result = Utils::IO::saveToFile(m_params.savePath, entry); - if (!result) - throw RuntimeError(result.error()); + BitTorrent::TorrentCreatorResult creatorResult; + creatorResult.path = m_params.savePath; + creatorResult.branchPath = parentPath; + creatorResult.pieceSize = newTorrent.piece_length(); + if (!m_params.savePath.isEmpty()) + { + // create the torrent file + const nonstd::expected result = Utils::IO::saveToFile(m_params.savePath, entry); + if (!result) + throw RuntimeError(result.error()); + } + else + { + lt::bencode(std::back_inserter(creatorResult.content), entry); + } emit updateProgress(100); - emit creationSuccess(m_params.savePath, parentPath); + emit creationSuccess(creatorResult); } catch (const RuntimeError &err) { diff --git a/src/base/bittorrent/torrentcreator.h b/src/base/bittorrent/torrentcreator.h index 79588b07ffa4..212630a4db7c 100644 --- a/src/base/bittorrent/torrentcreator.h +++ b/src/base/bittorrent/torrentcreator.h @@ -35,6 +35,7 @@ #include #include "base/path.h" +#include "torrentdescriptor.h" namespace BitTorrent { @@ -65,6 +66,16 @@ namespace BitTorrent QStringList urlSeeds; }; + struct TorrentCreatorResult + { + Path path; + Path branchPath; + int pieceSize; + QByteArray content; + + nonstd::expected startSeeding(bool ignoreShareLimits) const; + }; + class TorrentCreator final : public QObject, public QRunnable { Q_OBJECT @@ -89,7 +100,7 @@ namespace BitTorrent signals: void creationFailure(const QString &msg); - void creationSuccess(const Path &path, const Path &branchPath); + void creationSuccess(const TorrentCreatorResult &result); void updateProgress(int progress); private: diff --git a/src/gui/torrentcreatordialog.cpp b/src/gui/torrentcreatordialog.cpp index 27dda0be0d90..02794bd0f08f 100644 --- a/src/gui/torrentcreatordialog.cpp +++ b/src/gui/torrentcreatordialog.cpp @@ -261,36 +261,23 @@ void TorrentCreatorDialog::handleCreationFailure(const QString &msg) setInteractionEnabled(true); } -void TorrentCreatorDialog::handleCreationSuccess(const Path &path, const Path &branchPath) +void TorrentCreatorDialog::handleCreationSuccess(const BitTorrent::TorrentCreatorResult &result) { setCursor(QCursor(Qt::ArrowCursor)); setInteractionEnabled(true); QMessageBox::information(this, tr("Torrent creator") - , u"%1\n%2"_s.arg(tr("Torrent created:"), path.toString())); + , u"%1\n%2"_s.arg(tr("Torrent created:"), result.path.toString())); if (m_ui->checkStartSeeding->isChecked()) { - const auto loadResult = BitTorrent::TorrentDescriptor::loadFromFile(path); + const auto loadResult = result.startSeeding(m_ui->checkIgnoreShareLimits->isChecked()); if (!loadResult) { const QString message = tr("Add torrent to transfer list failed.") + u'\n' + tr("Reason: \"%1\"").arg(loadResult.error()); QMessageBox::critical(this, tr("Add torrent failed"), message); return; } - - BitTorrent::AddTorrentParams params; - params.savePath = branchPath; - params.skipChecking = true; - if (m_ui->checkIgnoreShareLimits->isChecked()) - { - params.ratioLimit = BitTorrent::Torrent::NO_RATIO_LIMIT; - params.seedingTimeLimit = BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT; - params.inactiveSeedingTimeLimit = BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT; - } - params.useAutoTMM = false; // otherwise if it is on by default, it will overwrite `savePath` to the default save path - - BitTorrent::Session::instance()->addTorrent(loadResult.value(), params); } } diff --git a/src/gui/torrentcreatordialog.h b/src/gui/torrentcreatordialog.h index addda955d832..c941ace63045 100644 --- a/src/gui/torrentcreatordialog.h +++ b/src/gui/torrentcreatordialog.h @@ -58,7 +58,7 @@ private slots: void onAddFileButtonClicked(); void onAddFolderButtonClicked(); void handleCreationFailure(const QString &msg); - void handleCreationSuccess(const Path &path, const Path &branchPath); + void handleCreationSuccess(const BitTorrent::TorrentCreatorResult &result); private: void dropEvent(QDropEvent *event) override; diff --git a/src/webui/CMakeLists.txt b/src/webui/CMakeLists.txt index 610774a72f52..970b359d77f3 100644 --- a/src/webui/CMakeLists.txt +++ b/src/webui/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(qbt_webui STATIC api/authcontroller.h api/isessionmanager.h api/logcontroller.h + api/metafilecontroller.h api/rsscontroller.h api/searchcontroller.h api/synccontroller.h @@ -22,6 +23,7 @@ add_library(qbt_webui STATIC api/appcontroller.cpp api/authcontroller.cpp api/logcontroller.cpp + api/metafilecontroller.cpp api/rsscontroller.cpp api/searchcontroller.cpp api/synccontroller.cpp diff --git a/src/webui/api/metafilecontroller.cpp b/src/webui/api/metafilecontroller.cpp new file mode 100644 index 000000000000..f60cd2179d5d --- /dev/null +++ b/src/webui/api/metafilecontroller.cpp @@ -0,0 +1,208 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2018 Thomas Piccirello + * + * 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 "metafilecontroller.h" + +#include +#include +#include + +#include "base/global.h" +#include "base/bittorrent/torrentcreator.h" +#include "base/bittorrent/torrentcreationmanager.h" +#include "base/utils/string.h" +#include "apierror.h" + +const QString KEY_COMMENT = u"comment"_s; +const QString KEY_FORMAT = u"format"_s; +const QString KEY_ID = u"id"_s; +const QString KEY_INPUT_PATH = u"inputPath"_s; +const QString KEY_OPTIMIZE_ALIGNMENT = u"optimizeAlignment"_s; +const QString KEY_PADDED_FILE_SIZE_LIMIT = u"paddedFileSizeLimit"_s; +const QString KEY_PIECE_SIZE = u"pieceSize"_s; +const QString KEY_PRIVATE = u"private"_s; +const QString KEY_SAVE_PATH = u"savePath"_s; +const QString KEY_SOURCE = u"source"_s; +const QString KEY_TRACKERS = u"trackers"_s; +const QString KEY_URL_SEEDS = u"urlSeeds"_s; + +namespace +{ + using Utils::String::parseBool; + using Utils::String::parseInt; + using Utils::String::parseDouble; +} + +void MetafileController::createAction() +{ + requireParams({u"inputPath"_s}); + + BitTorrent::TorrentCreatorParams createTorrentParams; + createTorrentParams.isPrivate = parseBool(params()[KEY_PRIVATE]).value_or(false); +#ifdef QBT_USES_LIBTORRENT2 + const QString format = params()[KEY_FORMAT].toLower(); + if (format == u"v1"_s) + createTorrentParams.torrentFormat = BitTorrent::TorrentFormat::V1; + else if (format == u"v2"_s) + createTorrentParams.torrentFormat = BitTorrent::TorrentFormat::V2; + else + createTorrentParams.torrentFormat = BitTorrent::TorrentFormat::Hybrid; +#else + createTorrentParams.isAlignmentOptimized = parseBool(params()[KEY_OPTIMIZE_ALIGNMENT]).value_or(true); + createTorrentParams.paddedFileSizeLimit = parseInt(params()[KEY_PADDED_FILE_SIZE_LIMIT]).value_or(-1); +#endif + createTorrentParams.pieceSize = parseInt(params()[KEY_PIECE_SIZE]).value_or(0); + createTorrentParams.inputPath = Path {params()[KEY_INPUT_PATH]}; + createTorrentParams.savePath = Path {params()[KEY_SAVE_PATH]}; + createTorrentParams.comment = params()[KEY_COMMENT]; + createTorrentParams.source = params()[KEY_COMMENT]; + createTorrentParams.trackers = params()[KEY_TRACKERS].split(u'|'); + createTorrentParams.urlSeeds = params()[KEY_URL_SEEDS].split(u'|'); + + bool const startSeeding = parseBool(params()[u"startSeeding"_s]).value_or(true); + + QString taskId = BitTorrent::TorrentCreationManager::instance()->createTask(createTorrentParams, startSeeding); + if (taskId.isEmpty()) + throw APIError(APIErrorType::Conflict, tr("Too many active tasks")); + + setResult(QJsonObject {{KEY_ID, taskId}}); +} + +void MetafileController::statusAction() +{ + const QString id = params()[KEY_ID]; + BitTorrent::TorrentCreationManager *manager = BitTorrent::TorrentCreationManager::instance(); + + if (!id.isEmpty() && !manager->getTask(id)) + throw APIError(APIErrorType::NotFound); + + QJsonArray statusArray; + const QStringList ids{id.isEmpty() ? manager->taskIds() : QStringList {id}}; + + for (const QString &task_id: ids){ + BitTorrent::TorrentCreationTask * task = manager->getTask(task_id); + if (!task) [[unlikely]] + throw APIError(APIErrorType::NotFound); + + QJsonObject taskJson { + {KEY_ID, task_id}, + {KEY_INPUT_PATH, task->params().inputPath.toString()}, + {KEY_PRIVATE, task->params().isPrivate}, + {u"timeAdded"_s, task->timeAdded().toString()}, + }; + + if (!task->params().comment.isEmpty()) + taskJson[KEY_COMMENT] = task->params().comment; + + if (task->params().pieceSize) + taskJson[KEY_PIECE_SIZE] = task->params().pieceSize; + + if (!task->params().savePath.isEmpty()) + taskJson[KEY_SAVE_PATH] = task->params().savePath.toString(); + + if (!task->params().source.isEmpty()) + taskJson[KEY_SOURCE] = task->params().source; + + if (!task->params().trackers.isEmpty()) + taskJson[KEY_TRACKERS] = QJsonArray::fromStringList(task->params().trackers); + + if (!task->params().urlSeeds.isEmpty()) + taskJson[KEY_URL_SEEDS] = QJsonArray::fromStringList(task->params().urlSeeds); + + if (!task->timeStarted().isNull()) + taskJson[u"timeStarted"_s] = task->timeStarted().toString(); + + if (!task->timeDone().isNull()) + taskJson[u"timeDone"_s] = task->timeDone().toString(); + + if (task->isDoneWithError()) + { + taskJson[u"status"_s] = u"Error"_s; + taskJson[u"error_msg"_s] = task->errorMsg(); + } + else if (task->isDoneWithSuccess()) + { + taskJson[u"status"_s] = u"Done"_s; + } + else if (task->isRunning()) + { + taskJson[u"status"_s] = u"Processing"_s; + taskJson[u"progress"_s] = task->progress(); + } + else + { + taskJson[u"status"_s] = u"Pending"_s; + } + +#ifdef QBT_USES_LIBTORRENT2 + switch (task->params().torrentFormat) + { + case BitTorrent::TorrentFormat::V1: + taskJson[KEY_FORMAT] = u"v1"_s; + break; + case BitTorrent::TorrentFormat::V2: + taskJson[KEY_FORMAT] = u"v2"_s; + break; + default: + taskJson[KEY_FORMAT] = u"hybrid"_s; + } +#else + taskJson[KEY_OPTIMIZE_ALIGNMENT] = task->params().isAlignmentOptimized; + taskJson[KEY_PADDED_FILE_SIZE_LIMIT] = task->params().paddedFileSizeLimit; +#endif + + statusArray << taskJson; + } + + setResult(statusArray); +} + +void MetafileController::resultAction() +{ + requireParams({KEY_ID}); + const QString id = params()[KEY_ID]; + + BitTorrent::TorrentCreationTask * task = BitTorrent::TorrentCreationManager::instance()->getTask(id); + if (!task) + throw APIError(APIErrorType::NotFound); + + QByteArray const content = task->content(); + if (content.isEmpty()) + throw APIError(APIErrorType::NotFound); + + setResult(content); +} + +void MetafileController::deleteAction() +{ + requireParams({KEY_ID}); + const QString id = params()[KEY_ID]; + + if (!BitTorrent::TorrentCreationManager::instance()->deleteTask(id)) + throw APIError(APIErrorType::NotFound); +} diff --git a/src/webui/api/metafilecontroller.h b/src/webui/api/metafilecontroller.h new file mode 100644 index 000000000000..39f87f9392a9 --- /dev/null +++ b/src/webui/api/metafilecontroller.h @@ -0,0 +1,50 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2018 Thomas Piccirello + * + * 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. + */ + +#pragma once + +#include + +#include "base/bittorrent/torrentcreator.h" +#include "apicontroller.h" + + +class MetafileController: public APIController +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(MetafileController) + +public: + using APIController::APIController; + +private slots: + void createAction(); + void statusAction(); + void resultAction(); + void deleteAction(); +}; diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 49963a7cf2a8..424a750c3fbc 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -59,6 +59,7 @@ #include "api/appcontroller.h" #include "api/authcontroller.h" #include "api/logcontroller.h" +#include "api/metafilecontroller.h" #include "api/rsscontroller.h" #include "api/searchcontroller.h" #include "api/synccontroller.h" @@ -715,6 +716,7 @@ void WebApplication::sessionStart() m_currentSession->registerAPIController(u"app"_s); m_currentSession->registerAPIController(u"log"_s); + m_currentSession->registerAPIController(u"metafile"_s); m_currentSession->registerAPIController(u"rss"_s); m_currentSession->registerAPIController(u"search"_s); m_currentSession->registerAPIController(u"torrents"_s); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index bdbe78e8db61..77e9e0227b2e 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -155,6 +155,10 @@ class WebApplication final : public ApplicationComponent {{u"app"_s, u"shutdown"_s}, Http::METHOD_POST}, {{u"auth"_s, u"login"_s}, Http::METHOD_POST}, {{u"auth"_s, u"logout"_s}, Http::METHOD_POST}, + {{u"metafile"_s, u"create"_s}, Http::METHOD_POST}, + {{u"metafile"_s, u"delete"_s}, Http::METHOD_POST}, + {{u"metafile"_s, u"getFile"_s}, Http::METHOD_GET}, + {{u"metafile"_s, u"status"_s}, Http::METHOD_GET}, {{u"rss"_s, u"addFeed"_s}, Http::METHOD_POST}, {{u"rss"_s, u"setFeedURL"_s}, Http::METHOD_POST}, {{u"rss"_s, u"addFolder"_s}, Http::METHOD_POST}, From b22a0e68072ce69c69f37acc8bf869c2570f5fc1 Mon Sep 17 00:00:00 2001 From: Radu Carpa Date: Wed, 24 Jan 2024 10:38:33 +0100 Subject: [PATCH 2/2] Add Radu Carpa in the copyright section --- src/base/bittorrent/torrentcreationmanager.cpp | 1 + src/base/bittorrent/torrentcreationmanager.h | 1 + src/base/bittorrent/torrentcreator.cpp | 1 + src/base/bittorrent/torrentcreator.h | 1 + src/gui/torrentcreatordialog.cpp | 1 + src/gui/torrentcreatordialog.h | 1 + src/webui/api/metafilecontroller.cpp | 1 + src/webui/api/metafilecontroller.h | 1 + src/webui/webapplication.cpp | 1 + src/webui/webapplication.h | 1 + 10 files changed, 10 insertions(+) diff --git a/src/base/bittorrent/torrentcreationmanager.cpp b/src/base/bittorrent/torrentcreationmanager.cpp index 12ca7903abca..52f1eb18635f 100644 --- a/src/base/bittorrent/torrentcreationmanager.cpp +++ b/src/base/bittorrent/torrentcreationmanager.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * diff --git a/src/base/bittorrent/torrentcreationmanager.h b/src/base/bittorrent/torrentcreationmanager.h index 7ad5449edfbc..b5ac35b775d1 100644 --- a/src/base/bittorrent/torrentcreationmanager.h +++ b/src/base/bittorrent/torrentcreationmanager.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * diff --git a/src/base/bittorrent/torrentcreator.cpp b/src/base/bittorrent/torrentcreator.cpp index d221ed570c9d..d5b43eeecfc1 100644 --- a/src/base/bittorrent/torrentcreator.cpp +++ b/src/base/bittorrent/torrentcreator.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2010 Christophe Dumez * * This program is free software; you can redistribute it and/or diff --git a/src/base/bittorrent/torrentcreator.h b/src/base/bittorrent/torrentcreator.h index 212630a4db7c..df9c572078f4 100644 --- a/src/base/bittorrent/torrentcreator.h +++ b/src/base/bittorrent/torrentcreator.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2010 Christophe Dumez * * This program is free software; you can redistribute it and/or diff --git a/src/gui/torrentcreatordialog.cpp b/src/gui/torrentcreatordialog.cpp index 02794bd0f08f..ed0dd35f4752 100644 --- a/src/gui/torrentcreatordialog.cpp +++ b/src/gui/torrentcreatordialog.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2017 Mike Tzou (Chocobo1) * Copyright (C) 2010 Christophe Dumez * diff --git a/src/gui/torrentcreatordialog.h b/src/gui/torrentcreatordialog.h index c941ace63045..28397818468b 100644 --- a/src/gui/torrentcreatordialog.h +++ b/src/gui/torrentcreatordialog.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2017 Mike Tzou (Chocobo1) * Copyright (C) 2010 Christophe Dumez * diff --git a/src/webui/api/metafilecontroller.cpp b/src/webui/api/metafilecontroller.cpp index f60cd2179d5d..a5411a4c4a60 100644 --- a/src/webui/api/metafilecontroller.cpp +++ b/src/webui/api/metafilecontroller.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2018 Thomas Piccirello * * This program is free software; you can redistribute it and/or diff --git a/src/webui/api/metafilecontroller.h b/src/webui/api/metafilecontroller.h index 39f87f9392a9..328576112b86 100644 --- a/src/webui/api/metafilecontroller.h +++ b/src/webui/api/metafilecontroller.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2018 Thomas Piccirello * * This program is free software; you can redistribute it and/or diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 424a750c3fbc..b9e121c37b7f 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2014, 2022-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 77e9e0227b2e..02b3e62e9714 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Radu Carpa * Copyright (C) 2014, 2017, 2022-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or