mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-01-23 04:54:18 +00:00
Merge pull request #15852 from glassez/torrent-info
Improve torrent content handling
This commit is contained in:
commit
5347897b7d
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
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 ¶ms)
|
||||
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 ¶ms)
|
||||
@ -2046,8 +2051,6 @@ bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &par
|
||||
|
||||
bool Session::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams ¶ms)
|
||||
{
|
||||
if (!torrentInfo.isValid()) return false;
|
||||
|
||||
return addTorrent_impl(torrentInfo, params);
|
||||
}
|
||||
|
||||
@ -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
|
||||
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;
|
||||
|
||||
if (hasMetadata)
|
||||
{
|
||||
const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
|
||||
|
||||
if (torrentInfo.isPrivate())
|
||||
return false;
|
||||
|
||||
// merge trackers and web seeds
|
||||
torrent->addTrackers(hasMetadata ? metadata.trackers() : magnetUri.trackers());
|
||||
torrent->addUrlSeeds(hasMetadata ? metadata.urlSeeds() : magnetUri.urlSeeds());
|
||||
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
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
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)
|
||||
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)
|
||||
// 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)
|
||||
|
||||
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;
|
||||
|
@ -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
Normal file
70
src/base/bittorrent/torrentcontentlayout.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
Q_ENUM_NS(TorrentContentLayout)
|
||||
}
|
||||
|
||||
TorrentContentLayout detectContentLayout(const QStringList &filePaths);
|
||||
void applyContentLayout(QStringList &filePaths, TorrentContentLayout contentLayout, const QString &rootFolder = {});
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
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
|
||||
|
||||
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<
|
||||
// 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
|
||||
{
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
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)
|
||||
// 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
|
||||
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()
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
@ -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
|
||||
|
||||
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;
|
||||
|
||||
|
@ -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)
|
||||
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)
|
||||
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
|
||||
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
|
||||
QString TorrentInfo::name() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
|
||||
return QString::fromStdString(m_nativeInfo->orig_files().name());
|
||||
}
|
||||
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
return !rootFolder().isEmpty();
|
||||
}
|
||||
|
||||
void TorrentInfo::setContentLayout(const TorrentContentLayout layout)
|
||||
TorrentContentLayout TorrentInfo::contentLayout() const
|
||||
{
|
||||
switch (layout)
|
||||
{
|
||||
case TorrentContentLayout::Original:
|
||||
setContentLayout(defaultContentLayout());
|
||||
break;
|
||||
case TorrentContentLayout::Subfolder:
|
||||
if (rootFolder().isEmpty())
|
||||
addRootFolder();
|
||||
break;
|
||||
case TorrentContentLayout::NoSubfolder:
|
||||
if (!rootFolder().isEmpty())
|
||||
stripRootFolder();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isValid())
|
||||
return TorrentContentLayout::Original;
|
||||
|
||||
void TorrentInfo::stripRootFolder()
|
||||
{
|
||||
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);
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
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
|
||||
|
||||
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
|
||||
// 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)
|
||||
: 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)
|
||||
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)
|
||||
|
||||
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()
|
||||
// Determine torrent size
|
||||
qlonglong torrentSize = 0;
|
||||
|
||||
if (m_hasMetadata)
|
||||
if (hasMetadata())
|
||||
{
|
||||
if (m_contentModel)
|
||||
{
|
||||
@ -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)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
bool AddNewTorrentDialog::hasMetadata() const
|
||||
{
|
||||
return m_torrentInfo.isValid();
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::populateSavePathComboBox()
|
||||
{
|
||||
m_ui->savePath->clear();
|
||||
@ -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()
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
, 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
|
||||
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
|
||||
return;
|
||||
}
|
||||
|
||||
m_torrentInfo = result.value();
|
||||
m_torrentGuard = std::make_unique<TorrentFileGuard>();
|
||||
|
||||
if (loadTorrentImpl())
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
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;
|
||||
|
@ -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')));
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
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)
|
||||
|
||||
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)
|
||||
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()};
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
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();
|
||||
|
@ -41,7 +41,7 @@ class TorrentContentModelFile;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentInfo;
|
||||
class AbstractFileStorage;
|
||||
}
|
||||
|
||||
class TorrentContentModel final : public QAbstractItemModel
|
||||
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -594,9 +594,12 @@ void TorrentsController::pieceHashesAction()
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
QJsonArray pieceHashes;
|
||||
if (torrent->hasMetadata())
|
||||
{
|
||||
const QVector<QByteArray> hashes = torrent->info().pieceHashes();
|
||||
for (const QByteArray &hash : hashes)
|
||||
pieceHashes.append(QString(hash.toHex()));
|
||||
}
|
||||
|
||||
setResult(pieceHashes);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user