Browse Source

Merge pull request #12035 from glassez/move-storage

Move torrent storages one by one
adaptive-webui-19844
Vladimir Golovnev 5 years ago committed by GitHub
parent
commit
5127156ba4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 116
      src/base/bittorrent/session.cpp
  2. 18
      src/base/bittorrent/session.h
  3. 156
      src/base/bittorrent/torrenthandle.cpp
  4. 26
      src/base/bittorrent/torrenthandle.h
  5. 3
      src/gui/transferlistwidget.cpp

116
src/base/bittorrent/session.cpp

@ -1815,8 +1815,19 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio @@ -1815,8 +1815,19 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio
for (const QString &file : files)
Utils::Fs::forceRemove(resumeDataDir.absoluteFilePath(file));
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)
{
return job.torrent == torrent;
});
if (iter != m_moveStorageQueue.end())
m_moveStorageQueue.erase(iter);
}
delete torrent;
qDebug("Torrent deleted.");
return true;
}
@ -3938,6 +3949,78 @@ void Session::handleTorrentTrackerError(TorrentHandle *const torrent, const QStr @@ -3938,6 +3949,78 @@ void Session::handleTorrentTrackerError(TorrentHandle *const torrent, const QStr
emit trackerError(torrent, trackerUrl);
}
bool Session::addMoveTorrentStorageJob(TorrentHandle *torrent, const QString &newPath, const MoveStorageMode mode)
{
Q_ASSERT(torrent);
if (m_moveStorageQueue.size() > 1) {
const auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
, [torrent](const MoveStorageJob &job)
{
return job.torrent == torrent;
});
if (iter != m_moveStorageQueue.end()) {
// remove existing inactive job
m_moveStorageQueue.erase(iter);
}
}
QString currentLocation = QString::fromStdString(
torrent->nativeHandle().status(lt::torrent_handle::query_save_path).save_path);
if (!m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrent == torrent)) {
// if there is active job for this torrent consider its target path as current location
// of this torrent to prevent creating meaningless job that will do nothing
currentLocation = m_moveStorageQueue.first().path;
}
if (QDir {currentLocation} == QDir {newPath})
return false;
const MoveStorageJob moveStorageJob {torrent, newPath, mode};
qDebug("Move storage from \"%s\" to \"%s\" is enqueued.", qUtf8Printable(currentLocation), qUtf8Printable(newPath));
if (m_moveStorageQueue.size() == 1)
moveTorrentStorage(moveStorageJob);
return true;
}
void Session::moveTorrentStorage(const MoveStorageJob &job) const
{
lt::torrent_handle handle = job.torrent->nativeHandle();
qDebug("Moving torrent storage to \"%s\"...", qUtf8Printable(job.path));
#if (LIBTORRENT_VERSION_NUM < 10200)
handle.move_storage(job.path.toUtf8().constData()
, ((job.mode == MoveStorageMode::Overwrite)
? lt::always_replace_files : lt::dont_replace));
#else
handle.move_storage(job.path.toUtf8().constData()
, ((job.mode == MoveStorageMode::Overwrite)
? lt::move_flags_t::always_replace_files : lt::move_flags_t::dont_replace));
#endif
}
void Session::handleMoveTorrentStorageJobFinished(const QString &errorMessage)
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
if (!m_moveStorageQueue.isEmpty())
moveTorrentStorage(m_moveStorageQueue.first());
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
, [&finishedJob](const MoveStorageJob &job)
{
return job.torrent == finishedJob.torrent;
});
if (iter == m_moveStorageQueue.cend()) {
// There is no more job for this torrent
finishedJob.torrent->handleStorageMoved(finishedJob.path, errorMessage);
}
}
void Session::handleTorrentTrackerWarning(TorrentHandle *const torrent, const QString &trackerUrl)
{
emit trackerWarning(torrent, trackerUrl);
@ -4254,8 +4337,6 @@ void Session::handleAlert(const lt::alert *a) @@ -4254,8 +4337,6 @@ void Session::handleAlert(const lt::alert *a)
case lt::torrent_finished_alert::alert_type:
case lt::save_resume_data_alert::alert_type:
case lt::save_resume_data_failed_alert::alert_type:
case lt::storage_moved_alert::alert_type:
case lt::storage_moved_failed_alert::alert_type:
case lt::torrent_paused_alert::alert_type:
case lt::torrent_resumed_alert::alert_type:
case lt::tracker_error_alert::alert_type:
@ -4319,6 +4400,12 @@ void Session::handleAlert(const lt::alert *a) @@ -4319,6 +4400,12 @@ void Session::handleAlert(const lt::alert *a)
handleAlertsDroppedAlert(static_cast<const lt::alerts_dropped_alert *>(a));
break;
#endif
case lt::storage_moved_alert::alert_type:
handleStorageMovedAlert(static_cast<const lt::storage_moved_alert*>(a));
break;
case lt::storage_moved_failed_alert::alert_type:
handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert*>(a));
break;
}
}
catch (const std::exception &exc) {
@ -4813,6 +4900,29 @@ void Session::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const @@ -4813,6 +4900,29 @@ void Session::handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const
}
#endif
void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
{
if (m_moveStorageQueue.isEmpty()) return;
const TorrentHandle *torrent = m_torrents.value(p->handle.info_hash());
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
if (currentJob.torrent != torrent) return;
const QString newPath {p->storage_path()};
handleMoveTorrentStorageJobFinished(newPath != currentJob.path ? tr("New path doesn't match a target path.") : QString {});
}
void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
{
if (m_moveStorageQueue.isEmpty()) return;
const TorrentHandle *torrent = m_torrents.value(p->handle.info_hash());
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
if (currentJob.torrent != torrent) return;
handleMoveTorrentStorageJobFinished(QString::fromStdString(p->message()));
}
void Session::handleStateUpdateAlert(const lt::state_update_alert *p)
{
QVector<BitTorrent::TorrentHandle *> updatedTorrents;

18
src/base/bittorrent/session.h

@ -96,6 +96,8 @@ namespace BitTorrent @@ -96,6 +96,8 @@ namespace BitTorrent
class TrackerEntry;
struct CreateTorrentParams;
enum class MoveStorageMode;
// 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
@ -461,6 +463,8 @@ namespace BitTorrent @@ -461,6 +463,8 @@ namespace BitTorrent
void handleTorrentTrackerWarning(TorrentHandle *const torrent, const QString &trackerUrl);
void handleTorrentTrackerError(TorrentHandle *const torrent, const QString &trackerUrl);
bool addMoveTorrentStorageJob(TorrentHandle *torrent, const QString &newPath, MoveStorageMode mode);
signals:
void addTorrentFailed(const QString &error);
void allTorrentsFinished();
@ -514,6 +518,13 @@ namespace BitTorrent @@ -514,6 +518,13 @@ namespace BitTorrent
void networkConfigurationChange(const QNetworkConfiguration &);
private:
struct MoveStorageJob
{
TorrentHandle *torrent;
QString path;
MoveStorageMode mode;
};
struct RemovingTorrentData
{
QString name;
@ -583,6 +594,8 @@ namespace BitTorrent @@ -583,6 +594,8 @@ namespace BitTorrent
#if (LIBTORRENT_VERSION_NUM >= 10200)
void handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const;
#endif
void handleStorageMovedAlert(const lt::storage_moved_alert *p);
void handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p);
void createTorrentHandle(const lt::torrent_handle &nativeHandle);
@ -592,6 +605,9 @@ namespace BitTorrent @@ -592,6 +605,9 @@ namespace BitTorrent
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
void moveTorrentStorage(const MoveStorageJob &job) const;
void handleMoveTorrentStorageJobFinished(const QString &errorMessage = {});
// BitTorrent
lt::session *m_nativeSession = nullptr;
@ -732,6 +748,8 @@ namespace BitTorrent @@ -732,6 +748,8 @@ namespace BitTorrent
QNetworkConfigurationManager *m_networkManager = nullptr;
QList<MoveStorageJob> m_moveStorageQueue;
static Session *m_instance;
};
}

156
src/base/bittorrent/torrenthandle.cpp

@ -327,7 +327,7 @@ QString TorrentHandle::currentTracker() const @@ -327,7 +327,7 @@ QString TorrentHandle::currentTracker() const
QString TorrentHandle::savePath(bool actual) const
{
if (actual)
return Utils::Fs::toUniformPath(nativeActualSavePath());
return Utils::Fs::toUniformPath(actualStorageLocation());
else
return Utils::Fs::toUniformPath(m_savePath);
}
@ -369,7 +369,7 @@ void TorrentHandle::setAutoTMMEnabled(bool enabled) @@ -369,7 +369,7 @@ void TorrentHandle::setAutoTMMEnabled(bool enabled)
m_session->handleTorrentSavingModeChanged(this);
if (m_useAutoTMM)
move_impl(m_session->categorySavePath(m_category), true);
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
}
bool TorrentHandle::hasRootFolder() const
@ -377,7 +377,7 @@ bool TorrentHandle::hasRootFolder() const @@ -377,7 +377,7 @@ bool TorrentHandle::hasRootFolder() const
return m_hasRootFolder;
}
QString TorrentHandle::nativeActualSavePath() const
QString TorrentHandle::actualStorageLocation() const
{
return QString::fromStdString(m_nativeStatus.save_path);
}
@ -898,14 +898,13 @@ void TorrentHandle::updateState() @@ -898,14 +898,13 @@ void TorrentHandle::updateState()
else if (isMoveInProgress()) {
m_state = TorrentState::Moving;
}
else if (isPaused()) {
if (hasMissingFiles())
else if (hasMissingFiles()) {
m_state = TorrentState::MissingFiles;
else
}
else if (isPaused()) {
m_state = isSeed() ? TorrentState::PausedUploading : TorrentState::PausedDownloading;
}
else {
if (m_session->isQueueingSystemEnabled() && isQueued() && !isChecking()) {
else if (m_session->isQueueingSystemEnabled() && isQueued() && !isChecking()) {
m_state = isSeed() ? TorrentState::QueuedUploading : TorrentState::QueuedDownloading;
}
else {
@ -937,7 +936,6 @@ void TorrentHandle::updateState() @@ -937,7 +936,6 @@ void TorrentHandle::updateState()
m_state = TorrentState::Unknown;
}
}
}
}
bool TorrentHandle::hasMetadata() const
@ -1334,7 +1332,7 @@ bool TorrentHandle::setCategory(const QString &category) @@ -1334,7 +1332,7 @@ bool TorrentHandle::setCategory(const QString &category)
if (m_useAutoTMM) {
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
move_impl(m_session->categorySavePath(m_category), true);
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
else
setAutoTMMEnabled(false);
}
@ -1354,21 +1352,19 @@ void TorrentHandle::move(QString path) @@ -1354,21 +1352,19 @@ void TorrentHandle::move(QString path)
if (!path.endsWith('/'))
path += '/';
move_impl(path, false);
move_impl(path, MoveStorageMode::KeepExistingFiles);
}
void TorrentHandle::move_impl(QString path, bool overwrite)
void TorrentHandle::move_impl(QString path, const MoveStorageMode mode)
{
if (path == savePath()) return;
path = Utils::Fs::toNativePath(path);
if (!useTempPath()) {
moveStorage(path, overwrite);
}
else {
if (!useTempPath())
moveStorage(path, mode);
m_savePath = path;
m_session->handleTorrentSavePathChanged(this);
}
}
void TorrentHandle::forceReannounce(int index)
@ -1502,30 +1498,10 @@ void TorrentHandle::resume_impl(bool forced) @@ -1502,30 +1498,10 @@ void TorrentHandle::resume_impl(bool forced)
m_nativeHandle.resume();
}
void TorrentHandle::moveStorage(const QString &newPath, bool overwrite)
void TorrentHandle::moveStorage(const QString &newPath, const MoveStorageMode mode)
{
if (isMoveInProgress()) {
qDebug("enqueue move storage to %s", qUtf8Printable(newPath));
m_moveStorageInfo.queuedPath = newPath;
m_moveStorageInfo.queuedOverwrite = overwrite;
}
else {
const QString oldPath = nativeActualSavePath();
if (QDir(oldPath) == QDir(newPath)) return;
qDebug("move storage: %s to %s", qUtf8Printable(oldPath), qUtf8Printable(newPath));
// Actually move the storage
#if (LIBTORRENT_VERSION_NUM < 10200)
m_nativeHandle.move_storage(newPath.toUtf8().constData()
, (overwrite ? lt::always_replace_files : lt::dont_replace));
#else
m_nativeHandle.move_storage(newPath.toUtf8().constData()
, (overwrite ? lt::move_flags_t::always_replace_files : lt::move_flags_t::dont_replace));
#endif
m_moveStorageInfo.oldPath = oldPath;
m_moveStorageInfo.newPath = newPath;
updateState();
}
if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
m_storageIsMoving = true;
}
void TorrentHandle::renameFile(const int index, const QString &name)
@ -1540,66 +1516,18 @@ void TorrentHandle::handleStateUpdate(const lt::torrent_status &nativeStatus) @@ -1540,66 +1516,18 @@ void TorrentHandle::handleStateUpdate(const lt::torrent_status &nativeStatus)
updateStatus(nativeStatus);
}
void TorrentHandle::handleStorageMovedAlert(const lt::storage_moved_alert *p)
{
if (!isMoveInProgress()) {
qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
return;
}
const QString newPath(p->storage_path());
if (newPath != m_moveStorageInfo.newPath) {
qWarning() << Q_FUNC_INFO << ": New path doesn't match a path in a queue.";
return;
}
LogMsg(tr("Successfully moved torrent: %1. New path: %2").arg(name(), m_moveStorageInfo.newPath));
const QDir oldDir {m_moveStorageInfo.oldPath};
if ((oldDir == QDir(m_session->torrentTempPath(info())))
&& (oldDir != QDir(m_session->tempPath()))) {
// torrent without root folder still has it in its temporary save path
// so its temp path isn't equal to temp path root
qDebug() << "Removing torrent temp folder:" << m_moveStorageInfo.oldPath;
Utils::Fs::smartRemoveEmptyFolderTree(m_moveStorageInfo.oldPath);
}
m_moveStorageInfo.newPath.clear();
updateStatus();
if (!m_moveStorageInfo.queuedPath.isEmpty()) {
moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
m_moveStorageInfo.queuedPath.clear();
}
if (!useTempPath()) {
m_savePath = newPath;
m_session->handleTorrentSavePathChanged(this);
}
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()();
}
void TorrentHandle::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
void TorrentHandle::handleStorageMoved(const QString &newPath, const QString &errorMessage)
{
if (!isMoveInProgress()) {
qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
return;
}
m_storageIsMoving = false;
LogMsg(tr("Could not move torrent: '%1'. Reason: %2")
.arg(name(), QString::fromStdString(p->message())), Log::CRITICAL);
if (!errorMessage.isEmpty())
LogMsg(tr("Could not move torrent: %1. Reason: %2").arg(name(), errorMessage), Log::CRITICAL);
else
LogMsg(tr("Successfully moved torrent: %1. New path: %2").arg(name(), newPath));
m_moveStorageInfo.newPath.clear();
updateStatus();
if (!m_moveStorageInfo.queuedPath.isEmpty()) {
moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
m_moveStorageInfo.queuedPath.clear();
}
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()();
}
@ -1922,7 +1850,7 @@ void TorrentHandle::handleTempPathChanged() @@ -1922,7 +1850,7 @@ void TorrentHandle::handleTempPathChanged()
void TorrentHandle::handleCategorySavePathChanged()
{
if (m_useAutoTMM)
move_impl(m_session->categorySavePath(m_category), true);
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
}
void TorrentHandle::handleAppendExtensionToggled()
@ -1953,12 +1881,6 @@ void TorrentHandle::handleAlert(const lt::alert *a) @@ -1953,12 +1881,6 @@ void TorrentHandle::handleAlert(const lt::alert *a)
case lt::save_resume_data_failed_alert::alert_type:
handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
break;
case lt::storage_moved_alert::alert_type:
handleStorageMovedAlert(static_cast<const lt::storage_moved_alert*>(a));
break;
case lt::storage_moved_failed_alert::alert_type:
handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert*>(a));
break;
case lt::torrent_paused_alert::alert_type:
handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
break;
@ -2028,19 +1950,27 @@ void TorrentHandle::adjustActualSavePath() @@ -2028,19 +1950,27 @@ void TorrentHandle::adjustActualSavePath()
void TorrentHandle::adjustActualSavePath_impl()
{
QString path;
if (!useTempPath()) {
// Disabling temp dir
// Moving all torrents to their destination folder
path = savePath();
const bool needUseTempDir = useTempPath();
const QDir tempDir {m_session->torrentTempPath(info())};
const QDir currentDir {actualStorageLocation()};
const QDir targetDir {needUseTempDir ? tempDir : QDir {savePath()}};
if (targetDir == currentDir) return;
if (!needUseTempDir) {
if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()})) {
// torrent without root folder still has it in its temporary save path
// so its temp path isn't equal to temp path root
const QString currentDirPath = currentDir.absolutePath();
m_moveFinishedTriggers.append([currentDirPath]
{
qDebug() << "Removing torrent temp folder:" << currentDirPath;
Utils::Fs::smartRemoveEmptyFolderTree(currentDirPath);
});
}
else {
// Moving all downloading torrents to temporary folder
path = m_session->torrentTempPath(info());
qDebug() << "Moving torrent to its temporary folder:" << path;
}
moveStorage(Utils::Fs::toNativePath(path), true);
moveStorage(Utils::Fs::toNativePath(targetDir.absolutePath()), MoveStorageMode::Overwrite);
}
lt::torrent_handle TorrentHandle::nativeHandle() const
@ -2057,7 +1987,7 @@ void TorrentHandle::updateTorrentInfo() @@ -2057,7 +1987,7 @@ void TorrentHandle::updateTorrentInfo()
bool TorrentHandle::isMoveInProgress() const
{
return !m_moveStorageInfo.newPath.isEmpty();
return m_storageIsMoving;
}
bool TorrentHandle::useTempPath() const

26
src/base/bittorrent/torrenthandle.h

@ -99,6 +99,12 @@ namespace BitTorrent @@ -99,6 +99,12 @@ namespace BitTorrent
int numPeers = 0;
};
enum class MoveStorageMode
{
KeepExistingFiles,
Overwrite
};
enum class TorrentState
{
Unknown = -1,
@ -345,6 +351,7 @@ namespace BitTorrent @@ -345,6 +351,7 @@ namespace BitTorrent
void handleCategorySavePathChanged();
void handleAppendExtensionToggled();
void saveResumeData();
void handleStorageMoved(const QString &newPath, const QString &errorMessage);
/**
* @brief fraction of file pieces that are available at least from one peer
@ -376,8 +383,6 @@ namespace BitTorrent @@ -376,8 +383,6 @@ namespace BitTorrent
void handlePerformanceAlert(const lt::performance_alert *p) const;
void handleSaveResumeDataAlert(const lt::save_resume_data_alert *p);
void handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p);
void handleStorageMovedAlert(const lt::storage_moved_alert *p);
void handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p);
void handleTorrentCheckedAlert(const lt::torrent_checked_alert *p);
void handleTorrentFinishedAlert(const lt::torrent_finished_alert *p);
void handleTorrentPausedAlert(const lt::torrent_paused_alert *p);
@ -388,14 +393,14 @@ namespace BitTorrent @@ -388,14 +393,14 @@ namespace BitTorrent
void resume_impl(bool forced);
bool isMoveInProgress() const;
QString nativeActualSavePath() const;
QString actualStorageLocation() const;
bool isAutoManaged() const;
void setAutoManaged(bool enable);
void adjustActualSavePath();
void adjustActualSavePath_impl();
void move_impl(QString path, bool overwrite);
void moveStorage(const QString &newPath, bool overwrite);
void move_impl(QString path, MoveStorageMode mode);
void moveStorage(const QString &newPath, MoveStorageMode mode);
void manageIncompleteFiles();
void setFirstLastPiecePriorityImpl(bool enabled, const QVector<DownloadPriority> &updatedFilePrio = {});
@ -408,16 +413,7 @@ namespace BitTorrent @@ -408,16 +413,7 @@ namespace BitTorrent
InfoHash m_hash;
struct
{
QString oldPath;
QString newPath;
// queuedPath is where files should be moved to,
// when current moving is completed
QString queuedPath;
bool queuedOverwrite = true;
} m_moveStorageInfo;
bool m_storageIsMoving = false;
// m_moveFinishedTriggers is activated only when the following conditions are met:
// all file rename jobs complete, all file move jobs complete
QQueue<EventTrigger> m_moveFinishedTriggers;

3
src/gui/transferlistwidget.cpp

@ -507,12 +507,9 @@ void TransferListWidget::setSelectedTorrentsLocation() @@ -507,12 +507,9 @@ void TransferListWidget::setSelectedTorrentsLocation()
if (torrents.isEmpty()) return;
const QString oldLocation = torrents[0]->savePath();
qDebug("Old location is %s", qUtf8Printable(oldLocation));
const QString newLocation = QFileDialog::getExistingDirectory(this, tr("Choose save path"), oldLocation,
QFileDialog::DontConfirmOverwrite | QFileDialog::ShowDirsOnly | QFileDialog::HideNameFilterDetails);
if (newLocation.isEmpty() || !QDir(newLocation).exists()) return;
qDebug("New location is %s", qUtf8Printable(newLocation));
// Actually move storage
for (BitTorrent::TorrentHandle *const torrent : torrents) {

Loading…
Cancel
Save