diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index a56c24984..681ab4afa 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(qbt_base STATIC bittorrent/abstractfilestorage.h bittorrent/addtorrentparams.h bittorrent/bandwidthscheduler.h + bittorrent/bencoderesumedatastorage.h bittorrent/cachestatus.h bittorrent/common.h bittorrent/customstorage.h @@ -94,6 +95,7 @@ add_library(qbt_base STATIC asyncfilestorage.cpp bittorrent/abstractfilestorage.cpp bittorrent/bandwidthscheduler.cpp + bittorrent/bencoderesumedatastorage.cpp bittorrent/customstorage.cpp bittorrent/downloadpriority.cpp bittorrent/filesearcher.cpp @@ -105,7 +107,6 @@ add_library(qbt_base STATIC bittorrent/peeraddress.cpp bittorrent/peerinfo.cpp bittorrent/portforwarderimpl.cpp - bittorrent/resumedatastorage.cpp bittorrent/session.cpp bittorrent/speedmonitor.cpp bittorrent/statistics.cpp diff --git a/src/base/base.pri b/src/base/base.pri index 48359c637..d80f97393 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -4,6 +4,7 @@ HEADERS += \ $$PWD/bittorrent/abstractfilestorage.h \ $$PWD/bittorrent/addtorrentparams.h \ $$PWD/bittorrent/bandwidthscheduler.h \ + $$PWD/bittorrent/bencoderesumedatastorage.h \ $$PWD/bittorrent/cachestatus.h \ $$PWD/bittorrent/common.h \ $$PWD/bittorrent/customstorage.h \ @@ -94,6 +95,7 @@ SOURCES += \ $$PWD/asyncfilestorage.cpp \ $$PWD/bittorrent/abstractfilestorage.cpp \ $$PWD/bittorrent/bandwidthscheduler.cpp \ + $$PWD/bittorrent/bencoderesumedatastorage.cpp \ $$PWD/bittorrent/customstorage.cpp \ $$PWD/bittorrent/downloadpriority.cpp \ $$PWD/bittorrent/filesearcher.cpp \ @@ -105,7 +107,6 @@ SOURCES += \ $$PWD/bittorrent/peeraddress.cpp \ $$PWD/bittorrent/peerinfo.cpp \ $$PWD/bittorrent/portforwarderimpl.cpp \ - $$PWD/bittorrent/resumedatastorage.cpp \ $$PWD/bittorrent/session.cpp \ $$PWD/bittorrent/speedmonitor.cpp \ $$PWD/bittorrent/statistics.cpp \ diff --git a/src/base/bittorrent/resumedatastorage.cpp b/src/base/bittorrent/bencoderesumedatastorage.cpp similarity index 69% rename from src/base/bittorrent/resumedatastorage.cpp rename to src/base/bittorrent/bencoderesumedatastorage.cpp index 7984a2971..c7d49cf26 100644 --- a/src/base/bittorrent/resumedatastorage.cpp +++ b/src/base/bittorrent/bencoderesumedatastorage.cpp @@ -26,10 +26,11 @@ * exception statement from your version. */ -#include "resumedatastorage.h" +#include "bencoderesumedatastorage.h" #include #include +#include #include #include #include @@ -37,18 +38,42 @@ #include #include #include +#include #include "base/algorithm.h" +#include "base/exceptions.h" #include "base/global.h" #include "base/logger.h" #include "base/profile.h" #include "base/utils/fs.h" #include "base/utils/io.h" #include "base/utils/string.h" +#include "infohash.h" +#include "loadtorrentparams.h" #include "torrentinfo.h" +namespace BitTorrent +{ + class BencodeResumeDataStorage::Worker final : public QObject + { + Q_DISABLE_COPY(Worker) + + public: + explicit Worker(const QDir &resumeDataDir); + + void store(const TorrentID &id, const LoadTorrentParams &resumeData) const; + void remove(const TorrentID &id) const; + void storeQueue(const QVector &queue) const; + + private: + const QDir m_resumeDataDir; + }; +} + namespace { + const char RESUME_FOLDER[] = "BT_backup"; + template QString fromLTString(const LTStr &str) { @@ -65,11 +90,31 @@ namespace entryList.emplace_back(setValue.toStdString()); return entryList; } + + void writeEntryToFile(const QString &filepath, const lt::entry &data) + { + QSaveFile file {filepath}; + if (!file.open(QIODevice::WriteOnly)) + throw RuntimeError(file.errorString()); + + lt::bencode(Utils::IO::FileDeviceOutputIterator {file}, data); + if (file.error() != QFileDevice::NoError || !file.commit()) + throw RuntimeError(file.errorString()); + } } -BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const QString &resumeFolderPath) - : m_resumeDataDir {resumeFolderPath} +BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(QObject *parent) + : ResumeDataStorage {parent} + , m_resumeDataDir {Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + RESUME_FOLDER)} + , m_ioThread {new QThread {this}} + , m_asyncWorker {new Worker {m_resumeDataDir}} { + if (!m_resumeDataDir.exists() && !m_resumeDataDir.mkpath(m_resumeDataDir.absolutePath())) + { + throw RuntimeError {tr("Cannot create torrent resume folder: \"%1\"") + .arg(Utils::Fs::toNativePath(m_resumeDataDir.absolutePath()))}; + } + const QRegularExpression filenamePattern {QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$")}; const QStringList filenames = m_resumeDataDir.entryList(QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted); @@ -109,6 +154,16 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const QString &re } qDebug("Registered torrents count: %d", m_registeredTorrents.size()); + + m_asyncWorker->moveToThread(m_ioThread); + connect(m_ioThread, &QThread::finished, m_asyncWorker, &QObject::deleteLater); + m_ioThread->start(); +} + +BitTorrent::BencodeResumeDataStorage::~BencodeResumeDataStorage() +{ + m_ioThread->quit(); + m_ioThread->wait(); } QVector BitTorrent::BencodeResumeDataStorage::registeredTorrents() const @@ -122,44 +177,19 @@ std::optional BitTorrent::BencodeResumeDataStorag const QString fastresumePath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(idString)); const QString torrentFilePath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.torrent").arg(idString)); - const auto readFile = [](const QString &path, QByteArray &buf) -> bool + QFile file {fastresumePath}; + if (!file.open(QIODevice::ReadOnly)) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) - { - LogMsg(tr("Cannot read file %1: %2").arg(path, file.errorString()), Log::WARNING); - return false; - } - - buf = file.readAll(); - return true; - }; - - QByteArray data; - if (!readFile(fastresumePath, data)) + LogMsg(tr("Cannot read file %1: %2").arg(fastresumePath, file.errorString()), Log::WARNING); return std::nullopt; + } + const QByteArray data = file.readAll(); const TorrentInfo metadata = TorrentInfo::loadFromFile(torrentFilePath); return loadTorrentResumeData(data, metadata); } -void BitTorrent::BencodeResumeDataStorage::storeQueue(const QVector &queue) const -{ - QByteArray data; - data.reserve(((TorrentID::length() * 2) + 1) * queue.size()); - for (const TorrentID &torrentID : queue) - data += (torrentID.toString().toLatin1() + '\n'); - - const QString filepath = m_resumeDataDir.absoluteFilePath(QLatin1String("queue")); - QSaveFile file {filepath}; - if (!file.open(QIODevice::WriteOnly) || (file.write(data) != data.size()) || !file.commit()) - { - LogMsg(tr("Couldn't save data to '%1'. Error: %2") - .arg(filepath, file.errorString()), Log::CRITICAL); - } -} - std::optional BitTorrent::BencodeResumeDataStorage::loadTorrentResumeData( const QByteArray &data, const TorrentInfo &metadata) const { @@ -222,16 +252,17 @@ std::optional BitTorrent::BencodeResumeDataStorag if (p.flags & lt::torrent_flags::stop_when_ready) { // If torrent has "stop_when_ready" flag set then it is actually "stopped" - torrentParams.paused = true; - torrentParams.forced = false; + torrentParams.stopped = true; + torrentParams.operatingMode = TorrentOperatingMode::AutoManaged; // ...but temporarily "resumed" to perform some service jobs (e.g. checking) p.flags &= ~lt::torrent_flags::paused; p.flags |= lt::torrent_flags::auto_managed; } else { - torrentParams.paused = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed); - torrentParams.forced = !(p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed); + torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed); + torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed) + ? TorrentOperatingMode::AutoManaged : TorrentOperatingMode::Forced; } const bool hasMetadata = (p.ti && p.ti->is_valid()); @@ -242,11 +273,40 @@ std::optional BitTorrent::BencodeResumeDataStorag } void BitTorrent::BencodeResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const +{ + QMetaObject::invokeMethod(m_asyncWorker, [this, id, resumeData]() + { + m_asyncWorker->store(id, resumeData); + }); +} + +void BitTorrent::BencodeResumeDataStorage::remove(const TorrentID &id) const +{ + QMetaObject::invokeMethod(m_asyncWorker, [this, id]() + { + m_asyncWorker->remove(id); + }); +} + +void BitTorrent::BencodeResumeDataStorage::storeQueue(const QVector &queue) const +{ + QMetaObject::invokeMethod(m_asyncWorker, [this, queue]() + { + m_asyncWorker->storeQueue(queue); + }); +} + +BitTorrent::BencodeResumeDataStorage::Worker::Worker(const QDir &resumeDataDir) + : m_resumeDataDir {resumeDataDir} +{ +} + +void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) const { // We need to adjust native libtorrent resume data lt::add_torrent_params p = resumeData.ltAddTorrentParams; p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString(); - if (resumeData.paused) + if (resumeData.stopped) { p.flags |= lt::torrent_flags::paused; p.flags &= ~lt::torrent_flags::auto_managed; @@ -255,7 +315,7 @@ void BitTorrent::BencodeResumeDataStorage::store(const TorrentID &id, const Load { // Torrent can be actually "running" but temporarily "paused" to perform some // service jobs behind the scenes so we need to restore it as "running" - if (!resumeData.forced) + if (resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged) { p.flags |= lt::torrent_flags::auto_managed; } @@ -266,6 +326,25 @@ void BitTorrent::BencodeResumeDataStorage::store(const TorrentID &id, const Load } } + // metadata is stored in separate .torrent file + const std::shared_ptr torrentInfo = std::move(p.ti); + if (torrentInfo) + { + const QString torrentFilepath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.torrent").arg(id.toString())); + const lt::create_torrent torrentCreator = lt::create_torrent(*torrentInfo); + const lt::entry metadata = torrentCreator.generate(); + try + { + writeEntryToFile(torrentFilepath, metadata); + } + catch (const RuntimeError &err) + { + LogMsg(tr("Couldn't save torrent metadata to '%1'. Error: %2") + .arg(torrentFilepath, err.message()), Log::CRITICAL); + return; + } + } + lt::entry data = lt::write_resume_data(p); data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).toStdString(); @@ -278,25 +357,19 @@ void BitTorrent::BencodeResumeDataStorage::store(const TorrentID &id, const Load data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString(); data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority; - const QString filepath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(id.toString())); - - QSaveFile file {filepath}; - if (!file.open(QIODevice::WriteOnly)) + const QString resumeFilepath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(id.toString())); + try { - LogMsg(tr("Couldn't save data to '%1'. Error: %2") - .arg(filepath, file.errorString()), Log::CRITICAL); - return; + writeEntryToFile(resumeFilepath, data); } - - lt::bencode(Utils::IO::FileDeviceOutputIterator {file}, data); - if ((file.error() != QFileDevice::NoError) || !file.commit()) + catch (const RuntimeError &err) { - LogMsg(tr("Couldn't save data to '%1'. Error: %2") - .arg(filepath, file.errorString()), Log::CRITICAL); + LogMsg(tr("Couldn't save torrent resume data to '%1'. Error: %2") + .arg(resumeFilepath, err.message()), Log::CRITICAL); } } -void BitTorrent::BencodeResumeDataStorage::remove(const TorrentID &id) const +void BitTorrent::BencodeResumeDataStorage::Worker::remove(const TorrentID &id) const { const QString resumeFilename = QString::fromLatin1("%1.fastresume").arg(id.toString()); Utils::Fs::forceRemove(m_resumeDataDir.absoluteFilePath(resumeFilename)); @@ -304,3 +377,19 @@ void BitTorrent::BencodeResumeDataStorage::remove(const TorrentID &id) const const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(id.toString()); Utils::Fs::forceRemove(m_resumeDataDir.absoluteFilePath(torrentFilename)); } + +void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector &queue) const +{ + QByteArray data; + data.reserve(((BitTorrent::TorrentID::length() * 2) + 1) * queue.size()); + for (const BitTorrent::TorrentID &torrentID : queue) + data += (torrentID.toString().toLatin1() + '\n'); + + const QString filepath = m_resumeDataDir.absoluteFilePath(QLatin1String("queue")); + QSaveFile file {filepath}; + if (!file.open(QIODevice::WriteOnly) || (file.write(data) != data.size()) || !file.commit()) + { + LogMsg(tr("Couldn't save data to '%1'. Error: %2") + .arg(filepath, file.errorString()), Log::CRITICAL); + } +} diff --git a/src/base/bittorrent/bencoderesumedatastorage.h b/src/base/bittorrent/bencoderesumedatastorage.h new file mode 100644 index 000000000..7523cda5c --- /dev/null +++ b/src/base/bittorrent/bencoderesumedatastorage.h @@ -0,0 +1,68 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015, 2018 Vladimir Golovnev + * + * 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 "resumedatastorage.h" + +class QByteArray; +class QThread; + +namespace BitTorrent +{ + class TorrentInfo; + + class BencodeResumeDataStorage final : public ResumeDataStorage + { + Q_OBJECT + Q_DISABLE_COPY(BencodeResumeDataStorage) + + public: + explicit BencodeResumeDataStorage(QObject *parent = nullptr); + ~BencodeResumeDataStorage() override; + + QVector registeredTorrents() const override; + std::optional load(const TorrentID &id) const override; + void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override; + void remove(const TorrentID &id) const override; + void storeQueue(const QVector &queue) const override; + + private: + std::optional loadTorrentResumeData(const QByteArray &data, const TorrentInfo &metadata) const; + + const QDir m_resumeDataDir; + QVector m_registeredTorrents; + QThread *m_ioThread = nullptr; + + class Worker; + Worker *m_asyncWorker = nullptr; + }; +} diff --git a/src/base/bittorrent/loadtorrentparams.h b/src/base/bittorrent/loadtorrentparams.h index 4192303c8..a4e946f71 100644 --- a/src/base/bittorrent/loadtorrentparams.h +++ b/src/base/bittorrent/loadtorrentparams.h @@ -47,10 +47,10 @@ namespace BitTorrent QSet tags; QString savePath; TorrentContentLayout contentLayout = TorrentContentLayout::Original; + TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged; bool firstLastPiecePriority = false; bool hasSeedStatus = false; - bool forced = false; - bool paused = false; + bool stopped = false; qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; diff --git a/src/base/bittorrent/resumedatastorage.h b/src/base/bittorrent/resumedatastorage.h index bc7e2fcc8..e96409da1 100644 --- a/src/base/bittorrent/resumedatastorage.h +++ b/src/base/bittorrent/resumedatastorage.h @@ -30,19 +30,14 @@ #include -#include - -#include +#include #include -#include - -#include "infohash.h" -#include "loadtorrentparams.h" - -class QByteArray; namespace BitTorrent { + class TorrentID; + struct LoadTorrentParams; + class ResumeDataStorage : public QObject { Q_OBJECT @@ -57,25 +52,4 @@ namespace BitTorrent virtual void remove(const TorrentID &id) const = 0; virtual void storeQueue(const QVector &queue) const = 0; }; - - class BencodeResumeDataStorage final : public ResumeDataStorage - { - Q_OBJECT - Q_DISABLE_COPY(BencodeResumeDataStorage) - - public: - explicit BencodeResumeDataStorage(const QString &resumeFolderPath); - - QVector registeredTorrents() const override; - std::optional load(const TorrentID &id) const override; - void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override; - void remove(const TorrentID &id) const override; - void storeQueue(const QVector &queue) const override; - - private: - std::optional loadTorrentResumeData(const QByteArray &data, const TorrentInfo &metadata) const; - - const QDir m_resumeDataDir; - QVector m_registeredTorrents; - }; } diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 3ee31176f..33a802e56 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -85,15 +85,16 @@ #include "base/utils/random.h" #include "base/version.h" #include "bandwidthscheduler.h" +#include "bencoderesumedatastorage.h" #include "common.h" #include "customstorage.h" #include "filesearcher.h" #include "filterparserthread.h" +#include "loadtorrentparams.h" #include "ltunderlyingtype.h" #include "magneturi.h" #include "nativesessionextension.h" #include "portforwarderimpl.h" -#include "resumedatastorage.h" #include "statistics.h" #include "torrentimpl.h" #include "tracker.h" @@ -103,7 +104,6 @@ using namespace BitTorrent; namespace { const char PEER_ID[] = "qB"; - const char RESUME_FOLDER[] = "BT_backup"; const char USER_AGENT[] = "qBittorrent/" QBT_VERSION_2; void torrentQueuePositionUp(const lt::torrent_handle &handle) @@ -439,7 +439,6 @@ Session::Session(QObject *parent) #if defined(Q_OS_WIN) , m_OSMemoryPriority(BITTORRENT_KEY("OSMemoryPriority"), OSMemoryPriority::BelowNormal) #endif - , m_resumeFolderLock {new QFile {this}} , m_seedingLimitTimer {new QTimer {this}} , m_resumeDataTimer {new QTimer {this}} , m_statistics {new Statistics {this}} @@ -980,9 +979,6 @@ Session::~Session() m_ioThread->quit(); m_ioThread->wait(); - - m_resumeFolderLock->close(); - m_resumeFolderLock->remove(); } void Session::initInstance() @@ -1844,10 +1840,7 @@ bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption } // Remove it from torrent resume directory - QMetaObject::invokeMethod(m_resumeDataStorage, [this, torrentID = torrent->id()]() - { - m_resumeDataStorage->remove(torrentID); - }); + m_resumeDataStorage->remove(torrent->id()); delete torrent; return true; @@ -2061,8 +2054,8 @@ LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorr loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority; loadTorrentParams.hasSeedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout()); - loadTorrentParams.forced = addTorrentParams.addForced; - loadTorrentParams.paused = addTorrentParams.addPaused.value_or(isAddTorrentPaused()); + loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged); + loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused()); loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit; loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit; @@ -2181,11 +2174,11 @@ bool Session::addTorrent_impl(const std::variant &source else p.flags &= ~lt::torrent_flags::seed_mode; - if (loadTorrentParams.paused || !loadTorrentParams.forced) + if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::AutoManaged)) p.flags |= lt::torrent_flags::paused; else p.flags &= ~lt::torrent_flags::paused; - if (loadTorrentParams.paused || loadTorrentParams.forced) + if (loadTorrentParams.stopped || (loadTorrentParams.operatingMode == TorrentOperatingMode::Forced)) p.flags &= ~lt::torrent_flags::auto_managed; else p.flags |= lt::torrent_flags::auto_managed; @@ -2300,23 +2293,28 @@ void Session::exportTorrentFile(const Torrent *torrent, TorrentExportFolder fold ((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty())); const QString validName = Utils::Fs::toValidFileSystemName(torrent->name()); - const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(torrent->id().toString()); QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName); - const QString torrentPath = QDir(m_resumeFolderPath).absoluteFilePath(torrentFilename); const QDir exportPath(folder == TorrentExportFolder::Regular ? torrentExportDirectory() : finishedTorrentExportDirectory()); if (exportPath.exists() || exportPath.mkpath(exportPath.absolutePath())) { QString newTorrentPath = exportPath.absoluteFilePath(torrentExportFilename); int counter = 0; - while (QFile::exists(newTorrentPath) && !Utils::Fs::sameFiles(torrentPath, newTorrentPath)) + while (QFile::exists(newTorrentPath)) { // Append number to torrent name to make it unique torrentExportFilename = QString::fromLatin1("%1 %2.torrent").arg(validName).arg(++counter); newTorrentPath = exportPath.absoluteFilePath(torrentExportFilename); } - if (!QFile::exists(newTorrentPath)) - QFile::copy(torrentPath, newTorrentPath); + try + { + torrent->info().saveToFile(newTorrentPath); + } + catch (const RuntimeError &err) + { + LogMsg(tr("Couldn't export torrent metadata file '%1'. Reason: %2") + .arg(newTorrentPath, err.message()), Log::WARNING); + } } } @@ -2382,14 +2380,12 @@ void Session::saveTorrentsQueue() const } } - QMetaObject::invokeMethod(m_resumeDataStorage - , [this, queue]() { m_resumeDataStorage->storeQueue(queue); }); + m_resumeDataStorage->storeQueue(queue); } void Session::removeTorrentsQueue() const { - QMetaObject::invokeMethod(m_resumeDataStorage - , [this]() { m_resumeDataStorage->storeQueue({}); }); + m_resumeDataStorage->storeQueue({}); } void Session::setDefaultSavePath(QString path) @@ -3844,21 +3840,9 @@ void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVe void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent) { - // Save metadata - const QDir resumeDataDir {m_resumeFolderPath}; - const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->id().toString())}; - try - { - torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName)); - // Copy the torrent file to the export folder - if (!torrentExportDirectory().isEmpty()) - exportTorrentFile(torrent); - } - catch (const RuntimeError &err) - { - LogMsg(tr("Couldn't save torrent metadata file '%1'. Reason: %2") - .arg(torrentFileName, err.message()), Log::CRITICAL); - } + // Copy the torrent file to the export folder + if (!torrentExportDirectory().isEmpty()) + exportTorrentFile(torrent); emit torrentMetadataReceived(torrent); } @@ -3919,8 +3903,7 @@ void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const Loa { --m_numResumeData; - QMetaObject::invokeMethod(m_resumeDataStorage - , [this, torrentID = torrent->id(), data]() { m_resumeDataStorage->store(torrentID, data); }); + m_resumeDataStorage->store(torrent->id(), data); } void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl) @@ -4055,26 +4038,7 @@ bool Session::hasPerTorrentSeedingTimeLimit() const void Session::initResumeDataStorage() { - m_resumeFolderPath = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + RESUME_FOLDER); - const QDir resumeFolderDir(m_resumeFolderPath); - if (resumeFolderDir.exists() || resumeFolderDir.mkpath(resumeFolderDir.absolutePath())) - { - m_resumeFolderLock->setFileName(resumeFolderDir.absoluteFilePath("session.lock")); - if (!m_resumeFolderLock->open(QFile::WriteOnly)) - { - throw RuntimeError {tr("Cannot write to torrent resume folder: \"%1\"") - .arg(Utils::Fs::toNativePath(m_resumeFolderPath))}; - } - } - else - { - throw RuntimeError {tr("Cannot create torrent resume folder: \"%1\"") - .arg(Utils::Fs::toNativePath(m_resumeFolderPath))}; - } - - m_resumeDataStorage = new BencodeResumeDataStorage {m_resumeFolderPath}; - m_resumeDataStorage->moveToThread(m_ioThread); - connect(m_ioThread, &QThread::finished, m_resumeDataStorage, &QObject::deleteLater); + m_resumeDataStorage = new BencodeResumeDataStorage(this); } void Session::configureDeferred() @@ -4378,24 +4342,14 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle) } else { + m_resumeDataStorage->store(torrent->id(), params); + // The following is useless for newly added magnet if (hasMetadata) { - // Backup torrent file - const QDir resumeDataDir {m_resumeFolderPath}; - const QString torrentFileName {QString::fromLatin1("%1.torrent").arg(torrent->id().toString())}; - try - { - torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName)); - // Copy the torrent file to the export folder - if (!torrentExportDirectory().isEmpty()) - exportTorrentFile(torrent); - } - catch (const RuntimeError &err) - { - LogMsg(tr("Couldn't save torrent metadata file '%1'. Reason: %2") - .arg(torrentFileName, err.message()), Log::CRITICAL); - } + // Copy the torrent file to the export folder + if (!torrentExportDirectory().isEmpty()) + exportTorrentFile(torrent); } if (isAddTrackersEnabled() && !torrent->isPrivate()) @@ -4403,10 +4357,6 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle) LogMsg(tr("'%1' added to download list.", "'torrent name' was added to download list.") .arg(torrent->name())); - - // In case of crash before the scheduled generation - // of the fastresumes. - torrent->saveResumeData(); } if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0)) diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index aea73f7a9..344f95433 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -52,7 +52,6 @@ #include "torrentinfo.h" #include "trackerentry.h" -class QFile; #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) class QNetworkConfiguration; class QNetworkConfigurationManager; @@ -751,8 +750,6 @@ namespace BitTorrent int m_numResumeData = 0; int m_extraLimit = 0; QVector m_additionalTrackerList; - QString m_resumeFolderPath; - QFile *m_resumeFolderLock = nullptr; bool m_refreshEnqueued = false; QTimer *m_seedingLimitTimer = nullptr; @@ -763,7 +760,7 @@ namespace BitTorrent QPointer m_bwScheduler; // Tracker QPointer m_tracker; - // fastresume data writing thread + QThread *m_ioThread = nullptr; ResumeDataStorage *m_resumeDataStorage = nullptr; FileSearcher *m_fileSearcher = nullptr; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index ce2c16f12..c520c1e14 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -240,12 +240,12 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession , m_tags(params.tags) , m_ratioLimit(params.ratioLimit) , m_seedingTimeLimit(params.seedingTimeLimit) - , m_operatingMode(params.forced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged) + , m_operatingMode(params.operatingMode) , m_contentLayout(params.contentLayout) , m_hasSeedStatus(params.hasSeedStatus) , m_hasFirstLastPiecePriority(params.firstLastPiecePriority) , m_useAutoTMM(params.savePath.isEmpty()) - , m_isStopped(params.paused) + , m_isStopped(params.stopped) , m_ltAddTorrentParams(params.ltAddTorrentParams) { if (m_useAutoTMM) @@ -1470,6 +1470,7 @@ void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QSt m_maintenanceJob = MaintenanceJob::None; updateStatus(); + prepareResumeData(p); m_session->handleTorrentMetadataReceived(this); } @@ -1728,6 +1729,26 @@ void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p) } void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) +{ + if (m_maintenanceJob == MaintenanceJob::HandleMetadata) + { + m_ltAddTorrentParams = p->params; + + m_ltAddTorrentParams.have_pieces.clear(); + m_ltAddTorrentParams.verified_pieces.clear(); + + TorrentInfo metadata = TorrentInfo {m_nativeHandle.torrent_file()}; + metadata.setContentLayout(m_contentLayout); + + m_session->findIncompleteFiles(metadata, m_savePath); + } + else + { + prepareResumeData(p->params); + } +} + +void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms) { if (m_hasMissingFiles) { @@ -1736,7 +1757,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces; // Update recent resume data but preserve existing progress - m_ltAddTorrentParams = p->params; + m_ltAddTorrentParams = params; m_ltAddTorrentParams.have_pieces = havePieces; m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces; m_ltAddTorrentParams.verified_pieces = verifiedPieces; @@ -1744,22 +1765,11 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) else { // Update recent resume data - m_ltAddTorrentParams = p->params; + m_ltAddTorrentParams = params; } m_ltAddTorrentParams.added_time = addedTime().toSecsSinceEpoch(); - if (m_maintenanceJob == MaintenanceJob::HandleMetadata) - { - m_ltAddTorrentParams.have_pieces.clear(); - m_ltAddTorrentParams.verified_pieces.clear(); - - TorrentInfo metadata = TorrentInfo {m_nativeHandle.torrent_file()}; - metadata.setContentLayout(m_contentLayout); - - m_session->findIncompleteFiles(metadata, m_savePath); - } - LoadTorrentParams resumeData; resumeData.name = m_name; resumeData.category = m_category; @@ -1770,8 +1780,8 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) resumeData.seedingTimeLimit = m_seedingTimeLimit; resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority; resumeData.hasSeedStatus = m_hasSeedStatus; - resumeData.paused = m_isStopped; - resumeData.forced = (m_operatingMode == TorrentOperatingMode::Forced); + resumeData.stopped = m_isStopped; + resumeData.operatingMode = m_operatingMode; resumeData.ltAddTorrentParams = m_ltAddTorrentParams; m_session->handleTorrentResumeDataReady(this, resumeData); diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index 5b49b7d7b..82a4e9f90 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -269,6 +269,7 @@ namespace BitTorrent void manageIncompleteFiles(); void applyFirstLastPiecePriority(bool enabled, const QVector &updatedFilePrio = {}); + void prepareResumeData(const lt::add_torrent_params ¶ms); void endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames); void reload();