Browse Source

Merge pull request #15852 from glassez/torrent-info

Improve torrent content handling
adaptive-webui-19844
Vladimir Golovnev 3 years ago committed by GitHub
parent
commit
5347897b7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/base/CMakeLists.txt
  2. 1
      src/base/base.pri
  3. 1
      src/base/bittorrent/addtorrentparams.h
  4. 118
      src/base/bittorrent/session.cpp
  5. 2
      src/base/bittorrent/session.h
  6. 70
      src/base/bittorrent/torrentcontentlayout.cpp
  7. 5
      src/base/bittorrent/torrentcontentlayout.h
  8. 64
      src/base/bittorrent/torrentimpl.cpp
  9. 7
      src/base/bittorrent/torrentimpl.h
  10. 161
      src/base/bittorrent/torrentinfo.cpp
  11. 23
      src/base/bittorrent/torrentinfo.h
  12. 37
      src/base/utils/fs.cpp
  13. 4
      src/base/utils/fs.h
  14. 128
      src/gui/addnewtorrentdialog.cpp
  15. 4
      src/gui/addnewtorrentdialog.h
  16. 4
      src/gui/properties/peerlistwidget.cpp
  17. 18
      src/gui/properties/piecesbar.cpp
  18. 2
      src/gui/properties/propertieswidget.cpp
  19. 4
      src/gui/torrentcontentmodel.cpp
  20. 4
      src/gui/torrentcontentmodel.h
  21. 6
      src/webui/api/synccontroller.cpp
  22. 9
      src/webui/api/torrentscontroller.cpp

1
src/base/CMakeLists.txt

@ -116,6 +116,7 @@ add_library(qbt_base STATIC @@ -116,6 +116,7 @@ add_library(qbt_base STATIC
bittorrent/speedmonitor.cpp
bittorrent/statistics.cpp
bittorrent/torrent.cpp
bittorrent/torrentcontentlayout.cpp
bittorrent/torrentcreatorthread.cpp
bittorrent/torrentimpl.cpp
bittorrent/torrentinfo.cpp

1
src/base/base.pri

@ -116,6 +116,7 @@ SOURCES += \ @@ -116,6 +116,7 @@ SOURCES += \
$$PWD/bittorrent/speedmonitor.cpp \
$$PWD/bittorrent/statistics.cpp \
$$PWD/bittorrent/torrent.cpp \
$$PWD/bittorrent/torrentcontentlayout.cpp \
$$PWD/bittorrent/torrentcreatorthread.cpp \
$$PWD/bittorrent/torrentimpl.cpp \
$$PWD/bittorrent/torrentinfo.cpp \

1
src/base/bittorrent/addtorrentparams.h

@ -53,6 +53,7 @@ namespace BitTorrent @@ -53,6 +53,7 @@ namespace BitTorrent
bool firstLastPiecePriority = false;
bool addForced = false;
std::optional<bool> addPaused;
QStringList filePaths; // used if TorrentInfo is set
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
bool skipChecking = false;
std::optional<BitTorrent::TorrentContentLayout> contentLayout;

118
src/base/bittorrent/session.cpp

@ -1698,11 +1698,14 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result) @@ -1698,11 +1698,14 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result)
{
case Net::DownloadStatus::Success:
emit downloadFromUrlFinished(result.url);
addTorrent(TorrentInfo::load(result.data).value_or(TorrentInfo()), m_downloadedTorrents.take(result.url));
if (const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::load(result.data); loadResult)
addTorrent(loadResult.value(), m_downloadedTorrents.take(result.url));
else
LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING);
break;
case Net::DownloadStatus::RedirectedToMagnet:
emit downloadFromUrlFinished(result.url);
addTorrent(MagnetUri {result.magnet}, m_downloadedTorrents.take(result.url));
addTorrent(MagnetUri(result.magnet), m_downloadedTorrents.take(result.url));
break;
default:
emit downloadFromUrlFailed(result.url, result.errorString);
@ -1727,7 +1730,7 @@ void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, c @@ -1727,7 +1730,7 @@ void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, c
lt::add_torrent_params &p = params.ltAddTorrentParams;
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
const TorrentInfo torrentInfo {p.ti};
const TorrentInfo torrentInfo {*p.ti};
const auto nativeIndexes = torrentInfo.nativeIndexes();
for (int i = 0; i < fileNames.size(); ++i)
p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
@ -2028,13 +2031,15 @@ bool Session::addTorrent(const QString &source, const AddTorrentParams &params) @@ -2028,13 +2031,15 @@ bool Session::addTorrent(const QString &source, const AddTorrentParams &params)
return addTorrent(magnetUri, params);
TorrentFileGuard guard {source};
if (addTorrent(TorrentInfo::loadFromFile(source).value_or(TorrentInfo()), params))
const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(source);
if (!loadResult)
{
guard.markAsAddedToSession();
return true;
LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING);
return false;
}
return false;
guard.markAsAddedToSession();
return addTorrent(loadResult.value(), params);
}
bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &params)
@ -2046,8 +2051,6 @@ bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &par @@ -2046,8 +2051,6 @@ bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &par
bool Session::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams &params)
{
if (!torrentInfo.isValid()) return false;
return addTorrent_impl(torrentInfo, params);
}
@ -2093,9 +2096,7 @@ LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorr @@ -2093,9 +2096,7 @@ LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorr
bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams)
{
const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source));
const auto id = TorrentID::fromInfoHash(hasMetadata ? metadata.infoHash() : magnetUri.infoHash());
const auto id = TorrentID::fromInfoHash(hasMetadata ? std::get<TorrentInfo>(source).infoHash() : std::get<MagnetUri>(source).infoHash());
// It looks illogical that we don't just use an existing handle,
// but as previous experience has shown, it actually creates unnecessary
@ -2111,12 +2112,29 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source @@ -2111,12 +2112,29 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
TorrentImpl *const torrent = m_torrents.value(id);
if (torrent)
{ // a duplicate torrent is added
if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
if (torrent->isPrivate())
return false;
// merge trackers and web seeds
torrent->addTrackers(hasMetadata ? metadata.trackers() : magnetUri.trackers());
torrent->addUrlSeeds(hasMetadata ? metadata.urlSeeds() : magnetUri.urlSeeds());
if (hasMetadata)
{
const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
if (torrentInfo.isPrivate())
return false;
// merge trackers and web seeds
torrent->addTrackers(torrentInfo.trackers());
torrent->addUrlSeeds(torrentInfo.urlSeeds());
}
else
{
const MagnetUri &magnetUri = std::get<MagnetUri>(source);
// merge trackers and web seeds
torrent->addTrackers(magnetUri.trackers());
torrent->addUrlSeeds(magnetUri.urlSeeds());
}
return true;
}
@ -2129,39 +2147,53 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source @@ -2129,39 +2147,53 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
const QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
if (hasMetadata)
{
metadata.setContentLayout(loadTorrentParams.contentLayout);
if (!loadTorrentParams.hasSeedStatus)
{
findIncompleteFiles(metadata, actualSavePath);
isFindingIncompleteFiles = true;
}
const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
// if torrent name wasn't explicitly set we handle the case of
// initial renaming of torrent content and rename torrent accordingly
if (loadTorrentParams.name.isEmpty())
{
QString contentName = metadata.rootFolder();
if (contentName.isEmpty() && (metadata.filesCount() == 1))
contentName = Utils::Fs::fileName(metadata.filePath(0));
QString contentName = torrentInfo.rootFolder();
if (contentName.isEmpty() && (torrentInfo.filesCount() == 1))
contentName = Utils::Fs::fileName(torrentInfo.filePath(0));
if (!contentName.isEmpty() && (contentName != metadata.name()))
if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
loadTorrentParams.name = contentName;
}
Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
const TorrentContentLayout contentLayout = ((loadTorrentParams.contentLayout == TorrentContentLayout::Original)
? detectContentLayout(torrentInfo.filePaths()) : loadTorrentParams.contentLayout);
QStringList filePaths = (!addTorrentParams.filePaths.isEmpty() ? addTorrentParams.filePaths : torrentInfo.filePaths());
applyContentLayout(filePaths, contentLayout, Utils::Fs::findRootFolder(torrentInfo.filePaths()));
if (!loadTorrentParams.hasSeedStatus)
{
findIncompleteFiles(torrentInfo, actualSavePath, filePaths);
isFindingIncompleteFiles = true;
}
const auto nativeIndexes = torrentInfo.nativeIndexes();
if (!filePaths.isEmpty())
{
for (int index = 0; index < addTorrentParams.filePaths.size(); ++index)
p.renamed_files[nativeIndexes[index]] = Utils::Fs::toNativePath(addTorrentParams.filePaths.at(index)).toStdString();
}
Q_ASSERT(p.file_priorities.empty());
const int internalFilesCount = metadata.nativeInfo()->files().num_files(); // including .pad files
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
// Use qBittorrent default priority rather than libtorrent's (4)
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
const auto nativeIndexes = metadata.nativeIndexes();
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
p.ti = metadata.nativeInfo();
p.ti = torrentInfo.nativeInfo();
}
else
{
const MagnetUri &magnetUri = std::get<MagnetUri>(source);
p = magnetUri.addTorrentParams();
if (loadTorrentParams.name.isEmpty() && !p.name.empty())
@ -2232,10 +2264,12 @@ bool Session::loadTorrent(LoadTorrentParams params) @@ -2232,10 +2264,12 @@ bool Session::loadTorrent(LoadTorrentParams params)
return true;
}
void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const
void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath, const QStringList &filePaths) const
{
Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
const QStringList originalFileNames = torrentInfo.filePaths();
const QStringList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
const QString completeSavePath = savePath;
const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
QMetaObject::invokeMethod(m_fileSearcher, [=]()
@ -3899,9 +3933,9 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent) @@ -3899,9 +3933,9 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
if (!torrentExportDirectory().isEmpty())
{
#ifdef QBT_USES_LIBTORRENT2
const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file_with_hashes()};
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file_with_hashes()};
#else
const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file()};
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file()};
#endif
exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
}
@ -3955,9 +3989,9 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent) @@ -3955,9 +3989,9 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
if (!finishedTorrentExportDirectory().isEmpty())
{
#ifdef QBT_USES_LIBTORRENT2
const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file_with_hashes()};
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file_with_hashes()};
#else
const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file()};
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file()};
#endif
exportTorrentFile(torrentInfo, finishedTorrentExportDirectory(), torrent->name());
}
@ -4155,7 +4189,7 @@ void Session::recursiveTorrentDownload(const TorrentID &id) @@ -4155,7 +4189,7 @@ void Session::recursiveTorrentDownload(const TorrentID &id)
for (const QString &torrentRelpath : asConst(torrent->filePaths()))
{
if (torrentRelpath.endsWith(".torrent"))
if (torrentRelpath.endsWith(QLatin1String(".torrent")))
{
LogMsg(tr("Recursive download of file '%1' embedded in torrent '%2'"
, "Recursive download of 'test.torrent' embedded in torrent 'test2'")
@ -4165,7 +4199,11 @@ void Session::recursiveTorrentDownload(const TorrentID &id) @@ -4165,7 +4199,11 @@ void Session::recursiveTorrentDownload(const TorrentID &id)
AddTorrentParams params;
// Passing the save path along to the sub torrent file
params.savePath = torrent->savePath();
addTorrent(TorrentInfo::loadFromFile(torrentFullpath).value_or(TorrentInfo()), params);
const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(torrentFullpath);
if (loadResult)
addTorrent(loadResult.value(), params);
else
LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING);
}
}
}
@ -4468,7 +4506,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle) @@ -4468,7 +4506,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
// Copy the torrent file to the export folder
if (!torrentExportDirectory().isEmpty())
{
const TorrentInfo torrentInfo {params.ltAddTorrentParams.ti};
const TorrentInfo torrentInfo {*params.ltAddTorrentParams.ti};
exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
}
}
@ -4597,7 +4635,7 @@ void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) @@ -4597,7 +4635,7 @@ void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
if (downloadedMetadataIter != m_downloadedMetadata.end())
{
const TorrentInfo metadata {p->handle.torrent_file()};
const TorrentInfo metadata {*p->handle.torrent_file()};
m_downloadedMetadata.erase(downloadedMetadataIter);
--m_extraLimit;

2
src/base/bittorrent/session.h

@ -499,7 +499,7 @@ namespace BitTorrent @@ -499,7 +499,7 @@ namespace BitTorrent
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, MoveStorageMode mode);
void findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const;
void findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath, const QStringList &filePaths = {}) const;
signals:
void allTorrentsFinished();

70
src/base/bittorrent/torrentcontentlayout.cpp

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020-2021 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 "torrentcontentlayout.h"
#include "base/utils/fs.h"
namespace
{
QString removeExtension(const QString &fileName)
{
const QString extension = Utils::Fs::fileExtension(fileName);
return extension.isEmpty()
? fileName
: fileName.chopped(extension.size() + 1);
}
}
BitTorrent::TorrentContentLayout BitTorrent::detectContentLayout(const QStringList &filePaths)
{
const QString rootFolder = Utils::Fs::findRootFolder(filePaths);
return (rootFolder.isEmpty()
? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder);
}
void BitTorrent::applyContentLayout(QStringList &filePaths, const TorrentContentLayout contentLayout, const QString &rootFolder)
{
Q_ASSERT(!filePaths.isEmpty());
switch (contentLayout)
{
case TorrentContentLayout::Subfolder:
if (Utils::Fs::findRootFolder(filePaths).isEmpty())
Utils::Fs::addRootFolder(filePaths, !rootFolder.isEmpty() ? rootFolder : removeExtension(filePaths.at(0)));
break;
case TorrentContentLayout::NoSubfolder:
Utils::Fs::stripRootFolder(filePaths);
break;
default:
break;
}
}

5
src/base/bittorrent/torrentcontentlayout.h

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020-2021 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
@ -48,4 +48,7 @@ namespace BitTorrent @@ -48,4 +48,7 @@ namespace BitTorrent
Q_ENUM_NS(TorrentContentLayout)
}
TorrentContentLayout detectContentLayout(const QStringList &filePaths);
void applyContentLayout(QStringList &filePaths, TorrentContentLayout contentLayout, const QString &rootFolder = {});
}

64
src/base/bittorrent/torrentimpl.cpp

@ -272,7 +272,19 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession @@ -272,7 +272,19 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
{
// Initialize it only if torrent is added with metadata.
// Otherwise it should be initialized in "Metadata received" handler.
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
Q_ASSERT(m_filePaths.isEmpty());
const int filesCount = m_torrentInfo.filesCount();
m_filePaths.reserve(filesCount);
const std::shared_ptr<const lt::torrent_info> currentInfo = m_nativeHandle.torrent_file();
const lt::file_storage &fileStorage = currentInfo->files();
for (int i = 0; i < filesCount; ++i)
{
const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
const QString filePath = Utils::Fs::toUniformPath(QString::fromStdString(fileStorage.file_path(nativeIndex)));
m_filePaths.append(filePath);
}
}
initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
@ -743,7 +755,7 @@ int TorrentImpl::seedingTimeLimit() const @@ -743,7 +755,7 @@ int TorrentImpl::seedingTimeLimit() const
QString TorrentImpl::filePath(const int index) const
{
return m_torrentInfo.filePath(index);
return m_filePaths.at(index);
}
qlonglong TorrentImpl::fileSize(const int index) const
@ -753,7 +765,7 @@ qlonglong TorrentImpl::fileSize(const int index) const @@ -753,7 +765,7 @@ qlonglong TorrentImpl::fileSize(const int index) const
QStringList TorrentImpl::filePaths() const
{
return m_torrentInfo.filePaths();
return m_filePaths;
}
// Return a list of absolute paths corresponding
@ -1461,7 +1473,7 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector< @@ -1461,7 +1473,7 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<
// Determine the priority to set
const DownloadPriority newPrio = enabled ? DownloadPriority::Maximum : filePrio;
const auto piecePrio = static_cast<lt::download_priority_t>(static_cast<int>(newPrio));
const TorrentInfo::PieceRange extremities = info().filePieces(index);
const TorrentInfo::PieceRange extremities = m_torrentInfo.filePieces(index);
// worst case: AVI index = 1% of total file size (at the end of the file)
const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength());
@ -1484,12 +1496,14 @@ void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QSt @@ -1484,12 +1496,14 @@ void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QSt
{
lt::add_torrent_params &p = m_ltAddTorrentParams;
const TorrentInfo torrentInfo {m_nativeHandle.torrent_file()};
const auto nativeIndexes = torrentInfo.nativeIndexes();
const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
m_torrentInfo = TorrentInfo(*metadata);
m_filePaths = fileNames;
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
for (int i = 0; i < fileNames.size(); ++i)
p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
p.ti = torrentInfo.nativeInfo();
p.ti = metadata;
const int internalFilesCount = p.ti->files().num_files(); // including .pad files
// Use qBittorrent default priority rather than libtorrent's (4)
@ -1536,8 +1550,6 @@ void TorrentImpl::reload() @@ -1536,8 +1550,6 @@ void TorrentImpl::reload()
m_nativeHandle = m_nativeSession->add_torrent(p);
m_nativeHandle.queue_position_set(queuePos);
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
}
void TorrentImpl::pause()
@ -1608,10 +1620,6 @@ void TorrentImpl::moveStorage(const QString &newPath, const MoveStorageMode mode @@ -1608,10 +1620,6 @@ void TorrentImpl::moveStorage(const QString &newPath, const MoveStorageMode mode
void TorrentImpl::renameFile(const int index, const QString &path)
{
#ifndef QBT_USES_LIBTORRENT2
const QString oldPath = filePath(index);
m_oldPath[index].push_back(oldPath);
#endif
++m_renameCount;
m_nativeHandle.rename_file(m_torrentInfo.nativeIndexes().at(index), Utils::Fs::toNativePath(path).toStdString());
}
@ -1766,10 +1774,11 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) @@ -1766,10 +1774,11 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
m_ltAddTorrentParams.have_pieces.clear();
m_ltAddTorrentParams.verified_pieces.clear();
TorrentInfo metadata = TorrentInfo {m_nativeHandle.torrent_file()};
metadata.setContentLayout(m_contentLayout);
TorrentInfo metadata = TorrentInfo(*m_nativeHandle.torrent_file());
m_session->findIncompleteFiles(metadata, m_savePath);
QStringList filePaths = metadata.filePaths();
applyContentLayout(filePaths, m_contentLayout);
m_session->findIncompleteFiles(metadata, m_savePath, filePaths);
}
else
{
@ -1848,15 +1857,11 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p) @@ -1848,15 +1857,11 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
// Remove empty leftover folders
// For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
// be removed if they are empty
#ifndef QBT_USES_LIBTORRENT2
const QString oldFilePath = m_oldPath[fileIndex].takeFirst();
if (m_oldPath[fileIndex].isEmpty())
m_oldPath.remove(fileIndex);
#else
const QString oldFilePath = Utils::Fs::toUniformPath(p->old_name());
#endif
const QString oldFilePath = m_filePaths.at(fileIndex);
const QString newFilePath = Utils::Fs::toUniformPath(p->new_name());
m_filePaths[fileIndex] = newFilePath;
QList<QStringView> oldPathParts = QStringView(oldFilePath).split('/', Qt::SkipEmptyParts);
oldPathParts.removeLast(); // drop file name part
QList<QStringView> newPathParts = QStringView(newFilePath).split('/', Qt::SkipEmptyParts);
@ -1897,12 +1902,6 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert @@ -1897,12 +1902,6 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
.arg(name(), filePath(fileIndex), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
#ifndef QBT_USES_LIBTORRENT2
m_oldPath[fileIndex].removeFirst();
if (m_oldPath[fileIndex].isEmpty())
m_oldPath.remove(fileIndex);
#endif
--m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()();
@ -2081,7 +2080,7 @@ void TorrentImpl::adjustActualSavePath() @@ -2081,7 +2080,7 @@ void TorrentImpl::adjustActualSavePath()
void TorrentImpl::adjustActualSavePath_impl()
{
const bool needUseTempDir = useTempPath();
const QDir tempDir {m_session->torrentTempPath(info())};
const QDir tempDir {m_session->torrentTempPath(m_torrentInfo)};
const QDir currentDir {actualStorageLocation()};
const QDir targetDir {needUseTempDir ? tempDir : QDir {savePath()}};
@ -2291,6 +2290,8 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities) @@ -2291,6 +2290,8 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
QVector<qreal> TorrentImpl::availableFileFractions() const
{
Q_ASSERT(hasMetadata());
const int filesCount = this->filesCount();
if (filesCount <= 0) return {};
@ -2300,10 +2301,9 @@ QVector<qreal> TorrentImpl::availableFileFractions() const @@ -2300,10 +2301,9 @@ QVector<qreal> TorrentImpl::availableFileFractions() const
QVector<qreal> res;
res.reserve(filesCount);
const TorrentInfo info = this->info();
for (int i = 0; i < filesCount; ++i)
{
const TorrentInfo::PieceRange filePieces = info.filePieces(i);
const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
int availablePieces = 0;
for (const int piece : filePieces)

7
src/base/bittorrent/torrentimpl.h

@ -289,6 +289,7 @@ namespace BitTorrent @@ -289,6 +289,7 @@ namespace BitTorrent
lt::torrent_status m_nativeStatus;
TorrentState m_state = TorrentState::Unknown;
TorrentInfo m_torrentInfo;
QStringList m_filePaths;
SpeedMonitor m_speedMonitor;
InfoHash m_infoHash;
@ -301,12 +302,6 @@ namespace BitTorrent @@ -301,12 +302,6 @@ namespace BitTorrent
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
#ifndef QBT_USES_LIBTORRENT2
// Until libtorrent provided an "old_name" field in `file_renamed_alert`
// we relied on this workaround to remove empty leftover folders
QHash<int, QVector<QString>> m_oldPath;
#endif
QHash<QString, QMap<lt::tcp::endpoint, int>> m_trackerPeerCounts;
FileErrorInfo m_lastFileError;

161
src/base/bittorrent/torrentinfo.cpp

@ -49,38 +49,14 @@ @@ -49,38 +49,14 @@
using namespace BitTorrent;
namespace
{
QString getRootFolder(const QStringList &filePaths)
{
QString rootFolder;
for (const QString &filePath : filePaths)
{
if (QDir::isAbsolutePath(filePath)) continue;
const auto filePathElements = QStringView(filePath).split(u'/');
// if at least one file has no root folder, no common root folder exists
if (filePathElements.count() <= 1) return {};
if (rootFolder.isEmpty())
rootFolder = filePathElements.at(0).toString();
else if (rootFolder != filePathElements.at(0))
return {};
}
return rootFolder;
}
}
const int torrentInfoId = qRegisterMetaType<TorrentInfo>();
TorrentInfo::TorrentInfo(std::shared_ptr<const lt::torrent_info> nativeInfo)
: m_nativeInfo {std::const_pointer_cast<lt::torrent_info>(nativeInfo)}
TorrentInfo::TorrentInfo(const lt::torrent_info &nativeInfo)
: m_nativeInfo {std::make_shared<const lt::torrent_info>(nativeInfo)}
{
if (!m_nativeInfo)
return;
Q_ASSERT(m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
const lt::file_storage &fileStorage = m_nativeInfo->files();
const lt::file_storage &fileStorage = m_nativeInfo->orig_files();
m_nativeIndexes.reserve(fileStorage.num_files());
for (const lt::file_index_t nativeIndex : fileStorage.file_range())
{
@ -105,6 +81,11 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other) @@ -105,6 +81,11 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
return *this;
}
bool TorrentInfo::isValid() const
{
return (m_nativeInfo != nullptr);
}
nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data) noexcept
{
// 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
@ -118,11 +99,11 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data) @@ -118,11 +99,11 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data)
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
TorrentInfo info {std::shared_ptr<lt::torrent_info>(new lt::torrent_info(node, ec))};
lt::torrent_info nativeInfo {node, ec};
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
return info;
return TorrentInfo(nativeInfo);
}
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const QString &path) noexcept
@ -173,11 +154,6 @@ nonstd::expected<void, QString> TorrentInfo::saveToFile(const QString &path) con @@ -173,11 +154,6 @@ nonstd::expected<void, QString> TorrentInfo::saveToFile(const QString &path) con
return {};
}
bool TorrentInfo::isValid() const
{
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
}
InfoHash TorrentInfo::infoHash() const
{
if (!isValid()) return {};
@ -192,6 +168,7 @@ InfoHash TorrentInfo::infoHash() const @@ -192,6 +168,7 @@ InfoHash TorrentInfo::infoHash() const
QString TorrentInfo::name() const
{
if (!isValid()) return {};
return QString::fromStdString(m_nativeInfo->orig_files().name());
}
@ -206,56 +183,65 @@ QDateTime TorrentInfo::creationDate() const @@ -206,56 +183,65 @@ QDateTime TorrentInfo::creationDate() const
QString TorrentInfo::creator() const
{
if (!isValid()) return {};
return QString::fromStdString(m_nativeInfo->creator());
}
QString TorrentInfo::comment() const
{
if (!isValid()) return {};
return QString::fromStdString(m_nativeInfo->comment());
}
bool TorrentInfo::isPrivate() const
{
if (!isValid()) return false;
return m_nativeInfo->priv();
}
qlonglong TorrentInfo::totalSize() const
{
if (!isValid()) return -1;
return m_nativeInfo->total_size();
}
int TorrentInfo::filesCount() const
{
if (!isValid()) return -1;
return m_nativeIndexes.size();
}
int TorrentInfo::pieceLength() const
{
if (!isValid()) return -1;
return m_nativeInfo->piece_length();
}
int TorrentInfo::pieceLength(const int index) const
{
if (!isValid()) return -1;
return m_nativeInfo->piece_size(lt::piece_index_t {index});
}
int TorrentInfo::piecesCount() const
{
if (!isValid()) return -1;
return m_nativeInfo->num_pieces();
}
QString TorrentInfo::filePath(const int index) const
{
if (!isValid()) return {};
return Utils::Fs::toUniformPath(
QString::fromStdString(m_nativeInfo->files().file_path(m_nativeIndexes[index])));
QString::fromStdString(m_nativeInfo->orig_files().file_path(m_nativeIndexes[index])));
}
QStringList TorrentInfo::filePaths() const
@ -268,23 +254,18 @@ QStringList TorrentInfo::filePaths() const @@ -268,23 +254,18 @@ QStringList TorrentInfo::filePaths() const
return list;
}
QString TorrentInfo::origFilePath(const int index) const
{
if (!isValid()) return {};
return Utils::Fs::toUniformPath(
QString::fromStdString(m_nativeInfo->orig_files().file_path(m_nativeIndexes[index])));
}
qlonglong TorrentInfo::fileSize(const int index) const
{
if (!isValid()) return -1;
return m_nativeInfo->files().file_size(m_nativeIndexes[index]);
return m_nativeInfo->orig_files().file_size(m_nativeIndexes[index]);
}
qlonglong TorrentInfo::fileOffset(const int index) const
{
if (!isValid()) return -1;
return m_nativeInfo->files().file_offset(m_nativeIndexes[index]);
return m_nativeInfo->orig_files().file_offset(m_nativeIndexes[index]);
}
QVector<TrackerEntry> TorrentInfo::trackers() const
@ -349,8 +330,8 @@ QVector<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const @@ -349,8 +330,8 @@ QVector<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
if (!isValid() || (pieceIndex < 0) || (pieceIndex >= piecesCount()))
return {};
const std::vector<lt::file_slice> files = nativeInfo()->map_block(
lt::piece_index_t {pieceIndex}, 0, nativeInfo()->piece_size(lt::piece_index_t {pieceIndex}));
const std::vector<lt::file_slice> files = m_nativeInfo->map_block(
lt::piece_index_t {pieceIndex}, 0, m_nativeInfo->piece_size(lt::piece_index_t {pieceIndex}));
QVector<int> res;
res.reserve(static_cast<decltype(res)::size_type>(files.size()));
for (const lt::file_slice &fileSlice : files)
@ -403,7 +384,7 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const @@ -403,7 +384,7 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
return {};
}
const lt::file_storage &files = nativeInfo()->files();
const lt::file_storage &files = m_nativeInfo->orig_files();
const auto fileSize = files.file_size(m_nativeIndexes[fileIndex]);
const auto fileOffset = files.file_offset(m_nativeIndexes[fileIndex]);
@ -415,26 +396,25 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const @@ -415,26 +396,25 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
return makeInterval(beginIdx, endIdx);
}
void TorrentInfo::renameFile(const int index, const QString &newPath)
{
if (!isValid()) return;
nativeInfo()->rename_file(m_nativeIndexes[index], Utils::Fs::toNativePath(newPath).toStdString());
}
int TorrentInfo::fileIndex(const QString &fileName) const
{
// the check whether the object is valid is not needed here
// because if filesCount() returns -1 the loop exits immediately
for (int i = 0; i < filesCount(); ++i)
{
if (fileName == filePath(i))
return i;
}
return -1;
}
QString TorrentInfo::rootFolder() const
{
return getRootFolder(filePaths());
if (!isValid())
return {};
return Utils::Fs::findRootFolder(filePaths());
}
bool TorrentInfo::hasRootFolder() const
@ -442,75 +422,20 @@ bool TorrentInfo::hasRootFolder() const @@ -442,75 +422,20 @@ bool TorrentInfo::hasRootFolder() const
return !rootFolder().isEmpty();
}
void TorrentInfo::setContentLayout(const TorrentContentLayout layout)
{
switch (layout)
{
case TorrentContentLayout::Original:
setContentLayout(defaultContentLayout());
break;
case TorrentContentLayout::Subfolder:
if (rootFolder().isEmpty())
addRootFolder();
break;
case TorrentContentLayout::NoSubfolder:
if (!rootFolder().isEmpty())
stripRootFolder();
break;
}
}
void TorrentInfo::stripRootFolder()
TorrentContentLayout TorrentInfo::contentLayout() const
{
lt::file_storage files = m_nativeInfo->files();
// Solution for case of renamed root folder
const QString path = filePath(0);
const std::string newName = path.left(path.indexOf('/')).toStdString();
if (files.name() != newName)
{
files.set_name(newName);
for (const lt::file_index_t nativeIndex : files.file_range())
files.rename_file(nativeIndex, files.file_path(nativeIndex));
}
files.set_name("");
m_nativeInfo->remap_files(files);
}
void TorrentInfo::addRootFolder()
{
const QString originalName = name();
Q_ASSERT(!originalName.isEmpty());
const QString extension = Utils::Fs::fileExtension(originalName);
const QString rootFolder = extension.isEmpty()
? originalName
: originalName.chopped(extension.size() + 1);
const std::string rootPrefix = Utils::Fs::toNativePath(rootFolder + QLatin1Char {'/'}).toStdString();
lt::file_storage files = m_nativeInfo->files();
files.set_name(rootFolder.toStdString());
for (const lt::file_index_t nativeIndex : files.file_range())
files.rename_file(nativeIndex, rootPrefix + files.file_path(nativeIndex));
m_nativeInfo->remap_files(files);
}
TorrentContentLayout TorrentInfo::defaultContentLayout() const
{
QStringList origFilePaths;
origFilePaths.reserve(filesCount());
for (int i = 0; i < filesCount(); ++i)
origFilePaths << origFilePath(i);
if (!isValid())
return TorrentContentLayout::Original;
const QString origRootFolder = getRootFolder(origFilePaths);
return (origRootFolder.isEmpty()
? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder);
return detectContentLayout(filePaths());
}
std::shared_ptr<lt::torrent_info> TorrentInfo::nativeInfo() const
{
return m_nativeInfo;
if (!isValid())
return nullptr;
return std::make_shared<lt::torrent_info>(*m_nativeInfo);
}
QVector<lt::file_index_t> TorrentInfo::nativeIndexes() const

23
src/base/bittorrent/torrentinfo.h

@ -35,7 +35,6 @@ @@ -35,7 +35,6 @@
#include "base/3rdparty/expected.hpp"
#include "base/indexrange.h"
#include "abstractfilestorage.h"
#include "torrentcontentlayout.h"
class QByteArray;
@ -48,14 +47,16 @@ namespace BitTorrent @@ -48,14 +47,16 @@ namespace BitTorrent
class InfoHash;
struct TrackerEntry;
class TorrentInfo final : public AbstractFileStorage
class TorrentInfo
{
Q_DECLARE_TR_FUNCTIONS(TorrentInfo)
public:
explicit TorrentInfo(std::shared_ptr<const lt::torrent_info> nativeInfo = {});
TorrentInfo() = default;
TorrentInfo(const TorrentInfo &other);
explicit TorrentInfo(const lt::torrent_info &nativeInfo);
static nonstd::expected<TorrentInfo, QString> load(const QByteArray &data) noexcept;
static nonstd::expected<TorrentInfo, QString> loadFromFile(const QString &path) noexcept;
nonstd::expected<void, QString> saveToFile(const QString &path) const;
@ -70,14 +71,13 @@ namespace BitTorrent @@ -70,14 +71,13 @@ namespace BitTorrent
QString comment() const;
bool isPrivate() const;
qlonglong totalSize() const;
int filesCount() const override;
int filesCount() const;
int pieceLength() const;
int pieceLength(int index) const;
int piecesCount() const;
QString filePath(int index) const override;
QString filePath(int index) const;
QStringList filePaths() const;
QString origFilePath(int index) const;
qlonglong fileSize(int index) const override;
qlonglong fileSize(int index) const;
qlonglong fileOffset(int index) const;
QVector<TrackerEntry> trackers() const;
QVector<QUrl> urlSeeds() const;
@ -92,11 +92,8 @@ namespace BitTorrent @@ -92,11 +92,8 @@ namespace BitTorrent
PieceRange filePieces(const QString &file) const;
PieceRange filePieces(int fileIndex) const;
void renameFile(int index, const QString &newPath) override;
QString rootFolder() const;
bool hasRootFolder() const;
void setContentLayout(TorrentContentLayout layout);
std::shared_ptr<lt::torrent_info> nativeInfo() const;
QVector<lt::file_index_t> nativeIndexes() const;
@ -104,11 +101,9 @@ namespace BitTorrent @@ -104,11 +101,9 @@ namespace BitTorrent
private:
// returns file index or -1 if fileName is not found
int fileIndex(const QString &fileName) const;
void stripRootFolder();
void addRootFolder();
TorrentContentLayout defaultContentLayout() const;
TorrentContentLayout contentLayout() const;
std::shared_ptr<lt::torrent_info> m_nativeInfo;
std::shared_ptr<const lt::torrent_info> m_nativeInfo;
// internal indexes of files (payload only, excluding any .pad files)
// by which they are addressed in libtorrent

37
src/base/utils/fs.cpp

@ -398,3 +398,40 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path) @@ -398,3 +398,40 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path)
#endif
}
#endif // Q_OS_HAIKU
QString Utils::Fs::findRootFolder(const QStringList &filePaths)
{
QString rootFolder;
for (const QString &filePath : filePaths)
{
const auto filePathElements = QStringView(filePath).split(u'/');
// if at least one file has no root folder, no common root folder exists
if (filePathElements.count() <= 1)
return {};
if (rootFolder.isEmpty())
rootFolder = filePathElements.at(0).toString();
else if (rootFolder != filePathElements.at(0))
return {};
}
return rootFolder;
}
void Utils::Fs::stripRootFolder(QStringList &filePaths)
{
const QString commonRootFolder = findRootFolder(filePaths);
if (commonRootFolder.isEmpty())
return;
for (QString &filePath : filePaths)
filePath = filePath.mid(commonRootFolder.size() + 1);
}
void Utils::Fs::addRootFolder(QStringList &filePaths, const QString &rootFolder)
{
Q_ASSERT(!rootFolder.isEmpty());
for (QString &filePath : filePaths)
filePath = rootFolder + QLatin1Char('/') + filePath;
}

4
src/base/utils/fs.h

@ -71,6 +71,10 @@ namespace Utils::Fs @@ -71,6 +71,10 @@ namespace Utils::Fs
QString tempPath();
QString findRootFolder(const QStringList &filePaths);
void stripRootFolder(QStringList &filePaths);
void addRootFolder(QStringList &filePaths, const QString &name);
#if !defined Q_OS_HAIKU
bool isNetworkFileSystem(const QString &path);
#endif

128
src/gui/addnewtorrentdialog.cpp

@ -43,6 +43,7 @@ @@ -43,6 +43,7 @@
#include "base/bittorrent/magneturi.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h"
#include "base/bittorrent/torrentcontentlayout.h"
#include "base/global.h"
#include "base/net/downloadmanager.h"
#include "base/settingsstorage.h"
@ -72,6 +73,51 @@ namespace @@ -72,6 +73,51 @@ namespace
{
return SettingsStorage::instance();
}
class FileStorageAdaptor final : public BitTorrent::AbstractFileStorage
{
public:
FileStorageAdaptor(const BitTorrent::TorrentInfo &torrentInfo, QStringList &filePaths)
: m_torrentInfo {torrentInfo}
, m_filePaths {filePaths}
{
Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
}
int filesCount() const override
{
return m_torrentInfo.filesCount();
}
qlonglong fileSize(const int index) const override
{
Q_ASSERT((index >= 0) && (index < filesCount()));
return m_torrentInfo.fileSize(index);
}
QString filePath(const int index) const override
{
Q_ASSERT((index >= 0) && (index < filesCount()));
return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
}
void renameFile(const int index, const QString &newFilePath) override
{
Q_ASSERT((index >= 0) && (index < filesCount()));
const QString currentFilePath = filePath(index);
if (currentFilePath == newFilePath)
return;
if (m_filePaths.isEmpty())
m_filePaths = m_torrentInfo.filePaths();
m_filePaths[index] = newFilePath;
}
private:
const BitTorrent::TorrentInfo &m_torrentInfo;
QStringList &m_filePaths;
};
}
const int AddNewTorrentDialog::minPathHistoryLength;
@ -119,6 +165,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP @@ -119,6 +165,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
m_ui->contentLayoutComboBox->setCurrentIndex(
static_cast<int>(m_torrentParams.contentLayout.value_or(session->torrentContentLayout())));
connect(m_ui->contentLayoutComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::contentLayoutChanged);
m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential);
m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
@ -146,10 +193,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP @@ -146,10 +193,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
// Signal / slots
connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut);
connect(editHotkey, &QShortcut::activated
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
connect(editHotkey, &QShortcut::activated, this, &AddNewTorrentDialog::renameSelectedFile);
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked, this, &AddNewTorrentDialog::renameSelectedFile);
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
}
@ -251,7 +296,6 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath) @@ -251,7 +296,6 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
: torrentPath;
const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::loadFromFile(decodedPath);
m_torrentInfo = result.value_or(BitTorrent::TorrentInfo());
if (!result)
{
RaisedMessageBox::critical(this, tr("Invalid torrent")
@ -260,6 +304,7 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath) @@ -260,6 +304,7 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
return false;
}
m_torrentInfo = result.value();
m_torrentGuard = std::make_unique<TorrentFileGuard>(decodedPath);
return loadTorrentImpl();
@ -267,7 +312,6 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath) @@ -267,7 +312,6 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
bool AddNewTorrentDialog::loadTorrentImpl()
{
m_hasMetadata = true;
const auto torrentID = BitTorrent::TorrentID::fromInfoHash(m_torrentInfo.infoHash());
// Prevent showing the dialog if download is already present
@ -398,7 +442,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel() @@ -398,7 +442,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
// Determine torrent size
qlonglong torrentSize = 0;
if (m_hasMetadata)
if (hasMetadata())
{
if (m_contentModel)
{
@ -442,6 +486,31 @@ void AddNewTorrentDialog::categoryChanged(int index) @@ -442,6 +486,31 @@ void AddNewTorrentDialog::categoryChanged(int index)
}
}
void AddNewTorrentDialog::contentLayoutChanged(const int index)
{
if (!hasMetadata())
return;
const auto filePriorities = m_contentModel->model()->getFilePriorities();
m_contentModel->model()->clear();
Q_ASSERT(!m_torrentParams.filePaths.isEmpty());
const auto contentLayout = ((index == 0)
? BitTorrent::detectContentLayout(m_torrentInfo.filePaths())
: static_cast<BitTorrent::TorrentContentLayout>(index));
BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Utils::Fs::findRootFolder(m_torrentInfo.filePaths()));
m_contentModel->model()->setupModelData(FileStorageAdaptor(m_torrentInfo, m_torrentParams.filePaths));
m_contentModel->model()->updateFilesPriorities(filePriorities);
// Expand single-item folders recursively
QModelIndex currentIndex;
while (m_contentModel->rowCount(currentIndex) == 1)
{
currentIndex = m_contentModel->index(0, 0, currentIndex);
m_ui->contentTreeView->setExpanded(currentIndex, true);
}
}
void AddNewTorrentDialog::setSavePath(const QString &newPath)
{
int existingIndex = indexOfSavePath(newPath);
@ -457,7 +526,7 @@ void AddNewTorrentDialog::setSavePath(const QString &newPath) @@ -457,7 +526,7 @@ void AddNewTorrentDialog::setSavePath(const QString &newPath)
void AddNewTorrentDialog::saveTorrentFile()
{
Q_ASSERT(m_hasMetadata);
Q_ASSERT(hasMetadata());
const QString torrentFileExtension {C_TORRENT_FILE_EXTENSION};
const QString filter {tr("Torrent file (*%1)").arg(torrentFileExtension)};
@ -479,6 +548,11 @@ void AddNewTorrentDialog::saveTorrentFile() @@ -479,6 +548,11 @@ void AddNewTorrentDialog::saveTorrentFile()
}
}
bool AddNewTorrentDialog::hasMetadata() const
{
return m_torrentInfo.isValid();
}
void AddNewTorrentDialog::populateSavePathComboBox()
{
m_ui->savePath->clear();
@ -548,8 +622,7 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &) @@ -548,8 +622,7 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
menu->setAttribute(Qt::WA_DeleteOnClose);
if (selectedRows.size() == 1)
{
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."), this, &AddNewTorrentDialog::renameSelectedFile);
menu->addSeparator();
QMenu *priorityMenu = menu->addMenu(tr("Priority"));
@ -634,7 +707,7 @@ void AddNewTorrentDialog::accept() @@ -634,7 +707,7 @@ void AddNewTorrentDialog::accept()
setEnabled(!m_ui->checkBoxNeverShow->isChecked());
// Add torrent
if (!m_hasMetadata)
if (!hasMetadata())
BitTorrent::Session::instance()->addTorrent(m_magnetURI, m_torrentParams);
else
BitTorrent::Session::instance()->addTorrent(m_torrentInfo, m_torrentParams);
@ -645,7 +718,7 @@ void AddNewTorrentDialog::accept() @@ -645,7 +718,7 @@ void AddNewTorrentDialog::accept()
void AddNewTorrentDialog::reject()
{
if (!m_hasMetadata)
if (!hasMetadata())
{
setMetadataProgressIndicator(false);
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.infoHash().toTorrentID());
@ -656,20 +729,14 @@ void AddNewTorrentDialog::reject() @@ -656,20 +729,14 @@ void AddNewTorrentDialog::reject()
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
{
Q_ASSERT(metadata.isValid());
if (metadata.infoHash() != m_magnetURI.infoHash()) return;
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
if (!metadata.isValid())
{
RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata."));
setMetadataProgressIndicator(false, tr("Invalid metadata"));
return;
}
// Good to go
m_torrentInfo = metadata;
m_hasMetadata = true;
setMetadataProgressIndicator(true, tr("Parsing metadata..."));
// Update UI
@ -694,7 +761,7 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co @@ -694,7 +761,7 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co
void AddNewTorrentDialog::setupTreeview()
{
if (!m_hasMetadata)
if (!hasMetadata())
{
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
@ -718,8 +785,14 @@ void AddNewTorrentDialog::setupTreeview() @@ -718,8 +785,14 @@ void AddNewTorrentDialog::setupTreeview()
, qOverload<const QModelIndex &>(&QAbstractItemView::edit));
connect(m_ui->contentTreeView, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::displayContentTreeMenu);
const auto contentLayout = ((m_ui->contentLayoutComboBox->currentIndex() == 0)
? BitTorrent::detectContentLayout(m_torrentInfo.filePaths())
: static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex()));
if (m_torrentParams.filePaths.isEmpty())
m_torrentParams.filePaths = m_torrentInfo.filePaths();
BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Utils::Fs::findRootFolder(m_torrentInfo.filePaths()));
// List files in torrent
m_contentModel->model()->setupModelData(m_torrentInfo);
m_contentModel->model()->setupModelData(FileStorageAdaptor(m_torrentInfo, m_torrentParams.filePaths));
if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
m_ui->contentTreeView->header()->restoreState(state);
@ -747,7 +820,6 @@ void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &down @@ -747,7 +820,6 @@ void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &down
case Net::DownloadStatus::Success:
{
const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::load(downloadResult.data);
m_torrentInfo = result.value_or(BitTorrent::TorrentInfo());
if (!result)
{
RaisedMessageBox::critical(this, tr("Invalid torrent"), tr("Failed to load from URL: %1.\nError: %2")
@ -755,6 +827,7 @@ void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &down @@ -755,6 +827,7 @@ void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &down
return;
}
m_torrentInfo = result.value();
m_torrentGuard = std::make_unique<TorrentFileGuard>();
if (loadTorrentImpl())
@ -800,3 +873,12 @@ void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked) @@ -800,3 +873,12 @@ void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
{
m_torrentGuard->setAutoRemove(!checked);
}
void AddNewTorrentDialog::renameSelectedFile()
{
if (hasMetadata())
{
FileStorageAdaptor fileStorageAdaptor {m_torrentInfo, m_torrentParams.filePaths};
m_ui->contentTreeView->renameSelectedFile(fileStorageAdaptor);
}
}

4
src/gui/addnewtorrentdialog.h

@ -85,7 +85,9 @@ private slots: @@ -85,7 +85,9 @@ private slots:
void handleDownloadFinished(const Net::DownloadResult &downloadResult);
void TMMChanged(int index);
void categoryChanged(int index);
void contentLayoutChanged(int index);
void doNotDeleteTorrentClicked(bool checked);
void renameSelectedFile();
void accept() override;
void reject() override;
@ -104,13 +106,13 @@ private: @@ -104,13 +106,13 @@ private:
void setupTreeview();
void setSavePath(const QString &newPath);
void saveTorrentFile();
bool hasMetadata() const;
void showEvent(QShowEvent *event) override;
Ui::AddNewTorrentDialog *m_ui;
TorrentContentFilterModel *m_contentModel = nullptr;
PropListDelegate *m_contentDelegate = nullptr;
bool m_hasMetadata = false;
BitTorrent::MagnetUri m_magnetURI;
BitTorrent::TorrentInfo m_torrentInfo;
int m_oldIndex = 0;

4
src/gui/properties/peerlistwidget.cpp

@ -471,7 +471,9 @@ void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTor @@ -471,7 +471,9 @@ void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTor
setModelData(row, PeerListColumns::TOT_UP, totalUp, peer.totalUpload(), intDataTextAlignment);
setModelData(row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + '%'), peer.relevance(), intDataTextAlignment);
const QStringList downloadingFiles {torrent->info().filesForPiece(peer.downloadingPieceIndex())};
const QStringList downloadingFiles {torrent->hasMetadata()
? torrent->info().filesForPiece(peer.downloadingPieceIndex())
: QStringList()};
const QString downloadingFilesDisplayValue = downloadingFiles.join(';');
setModelData(row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue, downloadingFilesDisplayValue, {}, downloadingFiles.join(QLatin1Char('\n')));

18
src/gui/properties/piecesbar.cpp

@ -256,13 +256,14 @@ void PiecesBar::showToolTip(const QHelpEvent *e) @@ -256,13 +256,14 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
const bool showDetailedInformation = QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
if (showDetailedInformation && m_torrent->hasMetadata())
{
const BitTorrent::TorrentInfo torrentInfo = m_torrent->info();
const int imagePos = e->pos().x() - borderWidth;
if ((imagePos >=0) && (imagePos < m_image.width()))
{
stream << "<html><body>";
PieceIndexToImagePos transform {m_torrent->info(), m_image};
PieceIndexToImagePos transform {torrentInfo, m_image};
int pieceIndex = transform.pieceIndex(imagePos);
const QVector<int> files {m_torrent->info().fileIndicesForPiece(pieceIndex)};
const QVector<int> files {torrentInfo.fileIndicesForPiece(pieceIndex)};
QString tooltipTitle;
if (files.count() > 1)
@ -271,7 +272,7 @@ void PiecesBar::showToolTip(const QHelpEvent *e) @@ -271,7 +272,7 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
}
else
{
if (m_torrent->info().fileSize(files.front()) == m_torrent->info().pieceLength(pieceIndex))
if (torrentInfo.fileSize(files.front()) == torrentInfo.pieceLength(pieceIndex))
tooltipTitle = tr("File in this piece");
else
tooltipTitle = tr("File in these pieces");
@ -281,8 +282,8 @@ void PiecesBar::showToolTip(const QHelpEvent *e) @@ -281,8 +282,8 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
for (int f : files)
{
const QString filePath {m_torrent->info().filePath(f)};
renderer(Utils::Misc::friendlyUnit(m_torrent->info().fileSize(f)), filePath);
const QString filePath {torrentInfo.filePath(f)};
renderer(Utils::Misc::friendlyUnit(torrentInfo.fileSize(f)), filePath);
}
stream << "</body></html>";
}
@ -306,13 +307,14 @@ void PiecesBar::highlightFile(int imagePos) @@ -306,13 +307,14 @@ void PiecesBar::highlightFile(int imagePos)
if (!m_torrent || !m_torrent->hasMetadata() || (imagePos < 0) || (imagePos >= m_image.width()))
return;
PieceIndexToImagePos transform {m_torrent->info(), m_image};
const BitTorrent::TorrentInfo torrentInfo = m_torrent->info();
PieceIndexToImagePos transform {torrentInfo, m_image};
int pieceIndex = transform.pieceIndex(imagePos);
QVector<int> fileIndices {m_torrent->info().fileIndicesForPiece(pieceIndex)};
QVector<int> fileIndices {torrentInfo.fileIndicesForPiece(pieceIndex)};
if (fileIndices.count() == 1)
{
BitTorrent::TorrentInfo::PieceRange filePieces = m_torrent->info().filePieces(fileIndices.first());
BitTorrent::TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(fileIndices.first());
ImageRange imageRange = transform.imagePos(filePieces);
QRect newHighlightedRegion {imageRange.first(), 0, imageRange.size(), m_image.height()};

2
src/gui/properties/propertieswidget.cpp

@ -528,7 +528,7 @@ void PropertiesWidget::loadDynamicData() @@ -528,7 +528,7 @@ void PropertiesWidget::loadDynamicData()
if (!isContentInitialized)
{
// List files in torrent
m_propListModel->model()->setupModelData(m_torrent->info());
m_propListModel->model()->setupModelData(*m_torrent);
// Load file priorities
m_propListModel->model()->updateFilesPriorities(m_torrent->filePriorities());
// Update file progress/availability

4
src/gui/torrentcontentmodel.cpp

@ -50,8 +50,8 @@ @@ -50,8 +50,8 @@
#include <QPixmapCache>
#endif
#include "base/bittorrent/abstractfilestorage.h"
#include "base/bittorrent/downloadpriority.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/global.h"
#include "base/utils/fs.h"
#include "torrentcontentmodelfile.h"
@ -485,7 +485,7 @@ void TorrentContentModel::clear() @@ -485,7 +485,7 @@ void TorrentContentModel::clear()
endResetModel();
}
void TorrentContentModel::setupModelData(const BitTorrent::TorrentInfo &info)
void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &info)
{
qDebug("setup model data called");
const int filesCount = info.filesCount();

4
src/gui/torrentcontentmodel.h

@ -41,7 +41,7 @@ class TorrentContentModelFile; @@ -41,7 +41,7 @@ class TorrentContentModelFile;
namespace BitTorrent
{
class TorrentInfo;
class AbstractFileStorage;
}
class TorrentContentModel final : public QAbstractItemModel
@ -74,7 +74,7 @@ public: @@ -74,7 +74,7 @@ public:
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = {}) const override;
void clear();
void setupModelData(const BitTorrent::TorrentInfo &info);
void setupModelData(const BitTorrent::AbstractFileStorage &info);
signals:
void filteredFilesChanged();

6
src/webui/api/synccontroller.cpp

@ -573,10 +573,12 @@ void SyncController::torrentPeersAction() @@ -573,10 +573,12 @@ void SyncController::torrentPeersAction()
{KEY_PEER_CONNECTION_TYPE, pi.connectionType()},
{KEY_PEER_FLAGS, pi.flags()},
{KEY_PEER_FLAGS_DESCRIPTION, pi.flagsDescription()},
{KEY_PEER_RELEVANCE, pi.relevance()},
{KEY_PEER_FILES, torrent->info().filesForPiece(pi.downloadingPieceIndex()).join('\n')}
{KEY_PEER_RELEVANCE, pi.relevance()}
};
if (torrent->hasMetadata())
peer.insert(KEY_PEER_FILES, torrent->info().filesForPiece(pi.downloadingPieceIndex()).join('\n'));
if (resolvePeerCountries)
{
peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower();

9
src/webui/api/torrentscontroller.cpp

@ -594,9 +594,12 @@ void TorrentsController::pieceHashesAction() @@ -594,9 +594,12 @@ void TorrentsController::pieceHashesAction()
throw APIError(APIErrorType::NotFound);
QJsonArray pieceHashes;
const QVector<QByteArray> hashes = torrent->info().pieceHashes();
for (const QByteArray &hash : hashes)
pieceHashes.append(QString(hash.toHex()));
if (torrent->hasMetadata())
{
const QVector<QByteArray> hashes = torrent->info().pieceHashes();
for (const QByteArray &hash : hashes)
pieceHashes.append(QString(hash.toHex()));
}
setResult(pieceHashes);
}

Loading…
Cancel
Save