Allow to move content files to Trash instead of deleting them

PR #20252.
This commit is contained in:
Vladimir Golovnev
2024-06-29 08:21:35 +03:00
committed by GitHub
parent c5fa05299b
commit 4e27e88f6a
24 changed files with 383 additions and 116 deletions

View File

@@ -38,6 +38,8 @@ add_library(qbt_base STATIC
bittorrent/torrent.h
bittorrent/torrentcontenthandler.h
bittorrent/torrentcontentlayout.h
bittorrent/torrentcontentremoveoption.h
bittorrent/torrentcontentremover.h
bittorrent/torrentcreationmanager.h
bittorrent/torrentcreationtask.h
bittorrent/torrentcreator.h
@@ -145,6 +147,7 @@ add_library(qbt_base STATIC
bittorrent/sslparameters.cpp
bittorrent/torrent.cpp
bittorrent/torrentcontenthandler.cpp
bittorrent/torrentcontentremover.cpp
bittorrent/torrentcreationmanager.cpp
bittorrent/torrentcreationtask.cpp
bittorrent/torrentcreator.cpp

View File

@@ -37,17 +37,12 @@
#include "addtorrentparams.h"
#include "categoryoptions.h"
#include "sharelimitaction.h"
#include "torrentcontentremoveoption.h"
#include "trackerentry.h"
#include "trackerentrystatus.h"
class QString;
enum DeleteOption
{
DeleteTorrent,
DeleteTorrentAndFiles
};
namespace BitTorrent
{
class InfoHash;
@@ -58,6 +53,12 @@ namespace BitTorrent
struct CacheStatus;
struct SessionStatus;
enum class TorrentRemoveOption
{
KeepContent,
RemoveContent
};
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
@@ -434,6 +435,8 @@ namespace BitTorrent
virtual void setMergeTrackersEnabled(bool enabled) = 0;
virtual bool isStartPaused() const = 0;
virtual void setStartPaused(bool value) = 0;
virtual TorrentContentRemoveOption torrentContentRemoveOption() const = 0;
virtual void setTorrentContentRemoveOption(TorrentContentRemoveOption option) = 0;
virtual bool isRestored() const = 0;
@@ -453,7 +456,7 @@ namespace BitTorrent
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams &params = {}) = 0;
virtual bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteOption::DeleteTorrent) = 0;
virtual bool removeTorrent(const TorrentID &id, TorrentRemoveOption deleteOption = TorrentRemoveOption::KeepContent) = 0;
virtual bool downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;

View File

@@ -101,6 +101,7 @@
#include "nativesessionextension.h"
#include "portforwarderimpl.h"
#include "resumedatastorage.h"
#include "torrentcontentremover.h"
#include "torrentdescriptor.h"
#include "torrentimpl.h"
#include "tracker.h"
@@ -525,6 +526,7 @@ SessionImpl::SessionImpl(QObject *parent)
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
, m_torrentContentRemoveOption {BITTORRENT_SESSION_KEY(u"TorrentContentRemoveOption"_s), TorrentContentRemoveOption::MoveToTrash}
, m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
, m_seedingLimitTimer {new QTimer(this)}
, m_resumeDataTimer {new QTimer(this)}
@@ -593,6 +595,11 @@ SessionImpl::SessionImpl(QObject *parent)
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
m_torrentContentRemover = new TorrentContentRemover;
m_torrentContentRemover->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_torrentContentRemover, &QObject::deleteLater);
connect(m_torrentContentRemover, &TorrentContentRemover::jobFinished, this, &SessionImpl::torrentContentRemovingFinished);
m_ioThread->start();
initMetrics();
@@ -2287,12 +2294,12 @@ void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
if (shareLimitAction == ShareLimitAction::Remove)
{
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent."), torrentName));
deleteTorrent(torrent->id());
removeTorrent(torrent->id(), TorrentRemoveOption::KeepContent);
}
else if (shareLimitAction == ShareLimitAction::RemoveWithContent)
{
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent and deleting its content."), torrentName));
deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
removeTorrent(torrent->id(), TorrentRemoveOption::RemoveContent);
}
else if ((shareLimitAction == ShareLimitAction::Stop) && !torrent->isStopped())
{
@@ -2332,6 +2339,19 @@ void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath,
}
}
void SessionImpl::torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage)
{
if (errorMessage.isEmpty())
{
LogMsg(tr("Torrent content removed. Torrent: \"%1\"").arg(torrentName));
}
else
{
LogMsg(tr("Failed to remove torrent content. Torrent: \"%1\". Error: \"%2\"")
.arg(torrentName, errorMessage), Log::WARNING);
}
}
Torrent *SessionImpl::getTorrent(const TorrentID &id) const
{
return m_torrents.value(id);
@@ -2378,26 +2398,29 @@ void SessionImpl::banIP(const QString &ip)
// Delete a torrent from the session, given its hash
// and from the disk, if the corresponding deleteOption is chosen
bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption deleteOption)
{
TorrentImpl *const torrent = m_torrents.take(id);
if (!torrent)
return false;
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
const TorrentID torrentID = torrent->id();
const QString torrentName = torrent->name();
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrentID.toString()));
emit torrentAboutToBeRemoved(torrent);
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
// Remove it from session
if (deleteOption == DeleteTorrent)
if (deleteOption == TorrentRemoveOption::KeepContent)
{
m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), {}, deleteOption};
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
, [&nativeHandle](const MoveStorageJob &job)
, [&nativeHandle](const MoveStorageJob &job)
{
return job.torrentHandle == nativeHandle;
});
@@ -2415,14 +2438,14 @@ bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOp
}
else
{
m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), torrent->actualFilePaths(), deleteOption};
if (m_moveStorageQueue.size() > 1)
{
// Delete "move storage job" for the deleted torrent
// (note: we shouldn't delete active job)
const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
, [torrent](const MoveStorageJob &job)
, [torrent](const MoveStorageJob &job)
{
return job.torrentHandle == torrent->nativeHandle();
});
@@ -2430,12 +2453,13 @@ bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOp
m_moveStorageQueue.erase(iter);
}
m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_partfile);
}
// Remove it from torrent resume directory
m_resumeDataStorage->remove(torrent->id());
m_resumeDataStorage->remove(torrentID);
LogMsg(tr("Torrent removed. Torrent: \"%1\"").arg(torrentName));
delete torrent;
return true;
}
@@ -2463,7 +2487,7 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
}
#endif
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
m_nativeSession->remove_torrent(nativeHandle);
return true;
}
@@ -3974,6 +3998,16 @@ void SessionImpl::setStartPaused(const bool value)
m_startPaused = value;
}
TorrentContentRemoveOption SessionImpl::torrentContentRemoveOption() const
{
return m_torrentContentRemoveOption;
}
void SessionImpl::setTorrentContentRemoveOption(const TorrentContentRemoveOption option)
{
m_torrentContentRemoveOption = option;
}
QStringList SessionImpl::bannedIPs() const
{
return m_bannedIPs;
@@ -5147,7 +5181,7 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
// Last job is completed for torrent that being removing, so actually remove it
const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
if (removingTorrentData.deleteOption == DeleteTorrent)
if (removingTorrentData.removeOption == TorrentRemoveOption::KeepContent)
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
}
}
@@ -5666,74 +5700,32 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle,
return torrent;
}
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *alert)
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert */*alert*/)
{
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(alert->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter != m_removingTorrents.end())
{
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
{
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
m_removingTorrents.erase(removingTorrentDataIter);
}
}
// We cannot consider `torrent_removed_alert` as a starting point for removing content,
// because it has an inconsistent posting time between different versions of libtorrent,
// so files may still be in use in some cases.
}
void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert)
{
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(alert->info_hash);
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
// torrent_deleted_alert can also be posted due to deletion of partfile. Ignore it in such a case.
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
return;
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
m_removingTorrents.erase(removingTorrentDataIter);
handleRemovedTorrent(torrentID);
}
void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert)
{
#ifdef QBT_USES_LIBTORRENT2
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(alert->info_hash);
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
if (alert->error)
{
// libtorrent won't delete the directory if it contains files not listed in the torrent,
// so we remove the directory ourselves
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
LogMsg(tr("Removed torrent but failed to delete its content and/or partfile. Torrent: \"%1\". Error: \"%2\"")
.arg(removingTorrentDataIter->name, QString::fromLocal8Bit(alert->error.message().c_str()))
, Log::WARNING);
}
else // torrent without metadata, hence no files on disk
{
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
}
m_removingTorrents.erase(removingTorrentDataIter);
const auto errorMessage = alert->error ? QString::fromLocal8Bit(alert->error.message().c_str()) : QString();
handleRemovedTorrent(torrentID, errorMessage);
}
void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *alert)
@@ -6169,7 +6161,7 @@ void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a
if (torrent2)
{
if (torrent1)
deleteTorrent(torrentIDv1);
removeTorrent(torrentIDv1);
else
cancelDownloadMetadata(torrentIDv1);
@@ -6278,3 +6270,29 @@ void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, Q
}
});
}
void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError)
{
const auto removingTorrentDataIter = m_removingTorrents.find(torrentID);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
if (!partfileRemoveError.isEmpty())
{
LogMsg(tr("Failed to remove partfile. Torrent: \"%1\". Reason: \"%2\".")
.arg(removingTorrentDataIter->name, partfileRemoveError)
, Log::WARNING);
}
if ((removingTorrentDataIter->removeOption == TorrentRemoveOption::RemoveContent)
&& !removingTorrentDataIter->contentStoragePath.isEmpty())
{
QMetaObject::invokeMethod(m_torrentContentRemover, [this, jobData = *removingTorrentDataIter]
{
m_torrentContentRemover->performJob(jobData.name, jobData.contentStoragePath
, jobData.fileNames, m_torrentContentRemoveOption);
});
}
m_removingTorrents.erase(removingTorrentDataIter);
}

View File

@@ -75,6 +75,7 @@ namespace BitTorrent
class InfoHash;
class ResumeDataStorage;
class Torrent;
class TorrentContentRemover;
class TorrentDescriptor;
class TorrentImpl;
class Tracker;
@@ -411,6 +412,8 @@ namespace BitTorrent
void setMergeTrackersEnabled(bool enabled) override;
bool isStartPaused() const override;
void setStartPaused(bool value) override;
TorrentContentRemoveOption torrentContentRemoveOption() const override;
void setTorrentContentRemoveOption(TorrentContentRemoveOption option) override;
bool isRestored() const override;
@@ -430,7 +433,7 @@ namespace BitTorrent
bool isKnownTorrent(const InfoHash &infoHash) const override;
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams &params = {}) override;
bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent) override;
bool removeTorrent(const TorrentID &id, TorrentRemoveOption deleteOption = TorrentRemoveOption::KeepContent) override;
bool downloadMetadata(const TorrentDescriptor &torrentDescr) override;
bool cancelDownloadMetadata(const TorrentID &id) override;
@@ -491,6 +494,7 @@ namespace BitTorrent
void handleIPFilterParsed(int ruleCount);
void handleIPFilterError();
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
private:
struct ResumeSessionContext;
@@ -506,8 +510,9 @@ namespace BitTorrent
struct RemovingTorrentData
{
QString name;
Path pathToRemove;
DeleteOption deleteOption {};
Path contentStoragePath;
PathList fileNames;
TorrentRemoveOption removeOption {};
};
explicit SessionImpl(QObject *parent = nullptr);
@@ -599,6 +604,8 @@ namespace BitTorrent
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers);
void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {});
CachedSettingValue<QString> m_DHTBootstrapNodes;
CachedSettingValue<bool> m_isDHTEnabled;
CachedSettingValue<bool> m_isLSDEnabled;
@@ -723,6 +730,7 @@ namespace BitTorrent
CachedSettingValue<int> m_I2POutboundQuantity;
CachedSettingValue<int> m_I2PInboundLength;
CachedSettingValue<int> m_I2POutboundLength;
CachedSettingValue<TorrentContentRemoveOption> m_torrentContentRemoveOption;
SettingValue<bool> m_startPaused;
lt::session *m_nativeSession = nullptr;
@@ -765,6 +773,7 @@ namespace BitTorrent
QThreadPool *m_asyncWorker = nullptr;
ResumeDataStorage *m_resumeDataStorage = nullptr;
FileSearcher *m_fileSearcher = nullptr;
TorrentContentRemover *m_torrentContentRemover = nullptr;
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;

View File

@@ -228,6 +228,7 @@ namespace BitTorrent
virtual void setShareLimitAction(ShareLimitAction action) = 0;
virtual PathList filePaths() const = 0;
virtual PathList actualFilePaths() const = 0;
virtual TorrentInfo info() const = 0;
virtual bool isFinished() const = 0;

View File

@@ -0,0 +1,50 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QMetaEnum>
namespace BitTorrent
{
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
inline namespace TorrentContentRemoveOptionNS
{
Q_NAMESPACE
enum class TorrentContentRemoveOption
{
Delete,
MoveToTrash
};
Q_ENUM_NS(TorrentContentRemoveOption)
}
}

View File

@@ -0,0 +1,61 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "torrentcontentremover.h"
#include "base/utils/fs.h"
void BitTorrent::TorrentContentRemover::performJob(const QString &torrentName, const Path &basePath
, const PathList &fileNames, const TorrentContentRemoveOption option)
{
QString errorMessage;
if (!fileNames.isEmpty())
{
const auto removeFileFn = [&option](const Path &filePath)
{
return ((option == TorrentContentRemoveOption::MoveToTrash)
? Utils::Fs::moveFileToTrash : Utils::Fs::removeFile)(filePath);
};
for (const Path &fileName : fileNames)
{
if (const auto result = removeFileFn(basePath / fileName)
; !result && errorMessage.isEmpty())
{
errorMessage = result.error();
}
}
const Path rootPath = Path::findRootFolder(fileNames);
if (!rootPath.isEmpty())
Utils::Fs::smartRemoveEmptyFolderTree(basePath / rootPath);
}
emit jobFinished(torrentName, errorMessage);
}

View File

@@ -0,0 +1,53 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QObject>
#include "base/path.h"
#include "torrentcontentremoveoption.h"
namespace BitTorrent
{
class TorrentContentRemover final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(TorrentContentRemover)
public:
using QObject::QObject;
public slots:
void performJob(const QString &torrentName, const Path &basePath
, const PathList &fileNames, TorrentContentRemoveOption option);
signals:
void jobFinished(const QString &torrentName, const QString &errorMessage);
};
}

View File

@@ -986,6 +986,21 @@ PathList TorrentImpl::filePaths() const
return m_filePaths;
}
PathList TorrentImpl::actualFilePaths() const
{
if (!hasMetadata())
return {};
PathList paths;
paths.reserve(filesCount());
const lt::file_storage files = nativeTorrentInfo()->files();
for (const lt::file_index_t &nativeIndex : asConst(m_torrentInfo.nativeIndexes()))
paths.emplaceBack(files.file_path(nativeIndex));
return paths;
}
QVector<DownloadPriority> TorrentImpl::filePriorities() const
{
return m_filePriorities;

View File

@@ -153,6 +153,7 @@ namespace BitTorrent
Path actualFilePath(int index) const override;
qlonglong fileSize(int index) const override;
PathList filePaths() const override;
PathList actualFilePaths() const override;
QVector<DownloadPriority> filePriorities() const override;
TorrentInfo info() const override;

View File

@@ -134,17 +134,17 @@ void Preferences::setCustomUIThemePath(const Path &path)
setValue(u"Preferences/General/CustomUIThemePath"_s, path);
}
bool Preferences::deleteTorrentFilesAsDefault() const
bool Preferences::removeTorrentContent() const
{
return value(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, false);
}
void Preferences::setDeleteTorrentFilesAsDefault(const bool del)
void Preferences::setRemoveTorrentContent(const bool remove)
{
if (del == deleteTorrentFilesAsDefault())
if (remove == removeTorrentContent())
return;
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, del);
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, remove);
}
bool Preferences::confirmOnExit() const

View File

@@ -105,8 +105,8 @@ public:
void setUseCustomUITheme(bool use);
Path customUIThemePath() const;
void setCustomUIThemePath(const Path &path);
bool deleteTorrentFilesAsDefault() const;
void setDeleteTorrentFilesAsDefault(bool del);
bool removeTorrentContent() const;
void setRemoveTorrentContent(bool remove);
bool confirmOnExit() const;
void setConfirmOnExit(bool confirm);
bool speedInTitleBar() const;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -29,8 +29,6 @@
#include "fs.h"
#include <cerrno>
#include <cstring>
#include <filesystem>
#if defined(Q_OS_WIN)
@@ -52,6 +50,7 @@
#include <unistd.h>
#endif
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
@@ -311,20 +310,42 @@ bool Utils::Fs::renameFile(const Path &from, const Path &to)
*
* This function will try to fix the file permissions before removing it.
*/
bool Utils::Fs::removeFile(const Path &path)
nonstd::expected<void, QString> Utils::Fs::removeFile(const Path &path)
{
if (QFile::remove(path.data()))
return true;
QFile file {path.data()};
if (file.remove())
return {};
if (!file.exists())
return true;
return {};
// Make sure we have read/write permissions
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
return file.remove();
if (file.remove())
return {};
return nonstd::make_unexpected(file.errorString());
}
nonstd::expected<void, QString> Utils::Fs::moveFileToTrash(const Path &path)
{
QFile file {path.data()};
if (file.moveToTrash())
return {};
if (!file.exists())
return {};
// Make sure we have read/write permissions
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
if (file.moveToTrash())
return {};
const QString errorMessage = file.errorString();
return nonstd::make_unexpected(!errorMessage.isEmpty() ? errorMessage : QCoreApplication::translate("fs", "Unknown error"));
}
bool Utils::Fs::isReadable(const Path &path)
{
return QFileInfo(path.data()).isReadable();

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -35,6 +35,7 @@
#include <QString>
#include "base/3rdparty/expected.hpp"
#include "base/global.h"
#include "base/pathfwd.h"
@@ -60,7 +61,8 @@ namespace Utils::Fs
bool copyFile(const Path &from, const Path &to);
bool renameFile(const Path &from, const Path &to);
bool removeFile(const Path &path);
nonstd::expected<void, QString> removeFile(const Path &path);
nonstd::expected<void, QString> moveFileToTrash(const Path &path);
bool mkdir(const Path &dirPath);
bool mkpath(const Path &dirPath);
bool rmdir(const Path &dirPath);